diff --git a/README.md b/README.md index a0fb6836..35a5acbc 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,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() { @@ -170,7 +169,6 @@ if z.type_of() == "string" { 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 @@ -220,7 +218,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) -> () { @@ -256,7 +253,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)] @@ -353,6 +349,8 @@ 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 Similarly, you can work with members of your custom types. This works by registering a 'get' or a 'set' function for working with your struct. @@ -415,7 +413,6 @@ By default, Rhai treats each engine invocation as a fresh one, persisting only t 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() { @@ -620,8 +617,12 @@ 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 +ts.list = y; // arrays can be assigned completely (by value copy) +let foo = ts.list[1]; // indexing into properties is ok +foo == 42; + +let foo = [1, 2, 3][0]; // a syntax error (for now) - cannot index into literals +let foo = abc()[0]; // a syntax error (for now) - cannot index into function call return values let foo = y[0]; // this works y.push(4); // 4 elements @@ -722,9 +723,11 @@ 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]; // indexing into properties is ok +c == 'C'; + +let c = "foo"[0]; // a syntax error (for now) - cannot index into literals // Escape sequences in strings record += " \u2764\n"; // escape sequence of '❤' in Unicode @@ -785,8 +788,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/src/any.rs b/src/any.rs index 232423fd..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; + /// 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,11 +23,7 @@ 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::() } @@ -29,7 +32,6 @@ where 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 99054a31..49cadfcd 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,10 +1,95 @@ -use crate::any::{Any, AnyExt}; -use crate::engine::{Engine, EvalAltResult, FnIntExt, FnSpec}; -use crate::parser::{lex, parse, ParseError, Position, AST}; +use crate::any::{Any, AnyExt, Dynamic}; +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: {} ({})", + fn_name, + args.iter() + .map(|x| (*x).type_name()) + .map(|name| self.map_type_name(name)) + .collect::>() + .join(", ") + ); + + let spec = FnSpec { + name: fn_name.to_string().into(), + args, + }; + + self.fns.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, + get_fn: impl Fn(&mut T) -> U + 'static, + ) { + let get_name = "get$".to_string() + name; + self.register_fn(&get_name, get_fn); + } + + /// Register a setter function for a member of a registered type with the `Engine`. + 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 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); @@ -17,12 +102,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)) } @@ -32,12 +117,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)) } @@ -74,7 +159,7 @@ impl Engine { fns.iter().for_each(|f| { self.script_fns.insert( FnSpec { - ident: f.name.clone(), + name: f.name.clone().into(), args: None, }, Arc::new(FnIntExt::Int(f.clone())), @@ -106,7 +191,7 @@ impl Engine { } } - /// 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> { @@ -114,23 +199,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( @@ -146,7 +231,7 @@ impl Engine { for f in fns { self.script_fns.insert( FnSpec { - ident: f.name.clone(), + name: f.name.clone().into(), args: None, }, Arc::new(FnIntExt::Int(f.clone())), @@ -164,13 +249,94 @@ impl Engine { }) } - /// 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_fns.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_fns.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 538a6f5b..42d736a3 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -1,4 +1,6 @@ -use crate::{any::Any, Array, Engine, RegisterDynamicFn, RegisterFn}; +use crate::any::Any; +use crate::engine::{Array, Engine}; +use crate::fn_register::{RegisterDynamicFn, RegisterFn}; use std::fmt::{Debug, Display}; use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}; @@ -58,7 +60,7 @@ macro_rules! reg_func3 { ) } -impl Engine { +impl Engine<'_> { /// Register the built-in library. pub(crate) fn register_builtins(&mut self) { fn add(x: T, y: T) -> ::Output { 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 dfdf0309..4f4396b0 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,148 +1,34 @@ 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>; const KEYWORD_PRINT: &'static str = "print"; const KEYWORD_DEBUG: &'static str = "debug"; const KEYWORD_TYPE_OF: &'static str = "type_of"; -#[derive(Debug)] -pub enum EvalAltResult { - ErrorParsing(ParseError), - ErrorFunctionNotFound(String, Position), - ErrorFunctionArgsMismatch(String, usize, 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), - ErrorRuntime(String, Position), - LoopBreak, - Return(Dynamic, Position), -} - -impl Error for EvalAltResult { - fn description(&self) -> &str { - match self { - Self::ErrorParsing(p) => p.description(), - Self::ErrorFunctionNotFound(_, _) => "Function not found", - Self::ErrorFunctionArgsMismatch(_, _, _, _) => { - "Function call with wrong number of arguments" - } - Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands", - Self::ErrorIndexExpr(_) => "Indexing into an array or string expects an integer index", - Self::ErrorIndexing(_) => "Indexing can only be performed on an array or a string", - Self::ErrorArrayBounds(_, index, _) if *index < 0 => { - "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::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::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::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::ErrorCantOpenScriptFile(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, "{} (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(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() { @@ -153,17 +39,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) fns: HashMap, Arc>, /// A hashmap containing all script-defined functions - pub(crate) script_fns: HashMap>, + pub(crate) script_fns: 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 { @@ -173,38 +59,19 @@ pub enum FnIntExt { pub type FnAny = dyn Fn(FnCallArgs, Position) -> Result; -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::new(); - - self.call_fn_raw(ident.into(), 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, - ) - }) - }) - } - +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!( - "Calling {}({})", - ident, + "Calling function: {} ({})", + fn_name, args.iter() .map(|x| (*x).type_name()) .map(|name| self.map_type_name(name)) @@ -212,17 +79,24 @@ impl Engine { .join(", ") ); - let mut spec = FnSpec { ident, args: None }; + let mut spec = FnSpec { + 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_fns + .get(&spec) + .or_else(|| { + spec.args = Some(args.iter().map(|a| Any::type_id(&**a)).collect()); + self.fns.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); @@ -230,25 +104,24 @@ impl Engine { return r; } - let callback = match spec.ident.as_str() { - KEYWORD_PRINT => &self.on_print, - KEYWORD_DEBUG => &self.on_debug, + let callback = match spec.name.as_ref() { + KEYWORD_PRINT => self.on_print.as_mut(), + KEYWORD_DEBUG => self.on_debug.as_mut(), _ => return r, }; Ok(callback( - r.unwrap() + &r.unwrap() .downcast::() - .map(|x| *x) - .unwrap_or("error: not a string".into()) - .as_str(), + .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.ident, + spec.name.into(), f.params.len(), args.len(), pos, @@ -270,7 +143,7 @@ impl Engine { } } } - } else if spec.ident == KEYWORD_TYPE_OF && args.len() == 1 { + } else if spec.name == KEYWORD_TYPE_OF && args.len() == 1 { Ok(self .map_type_name(args[0].type_name()) .to_string() @@ -286,72 +159,14 @@ impl Engine { .collect::>(); Err(EvalAltResult::ErrorFunctionNotFound( - format!("{} ({})", spec.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); - } - fn get_dot_val_helper( - &self, + &mut self, scope: &mut Scope, this_ptr: &mut Variant, dot_rhs: &Expr, @@ -369,78 +184,44 @@ 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) } 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()))?; + Expr::Index(id, idx_expr, pos) => { + let idx = *self + .eval_expr(scope, idx_expr)? + .downcast::() + .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))?; - 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| ch.into_dynamic()) - .ok_or_else(|| { - EvalAltResult::ErrorStringBounds(s.chars().count(), idx, *pos) - }) - } else { - Err(EvalAltResult::ErrorStringBounds( - s.chars().count(), - idx, - *pos, - )) - } - } else { - Err(EvalAltResult::ErrorIndexing(*pos)) - } + let get_fn_name = format!("get${}", id); + let val = self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?; + Self::get_indexed_value(val, idx, *pos).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))?; + Expr::Dot(inner_lhs, inner_rhs) => match inner_lhs.as_ref() { + 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. - - Ok(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)) } - Expr::Index(_, _, pos) => { - // TODO - Handle Expr::Index for these scenarios: - // - // let x = obj.prop[2].x; - // obj.prop[3] = 42; - // - Err(EvalAltResult::ErrorDotExpr(pos)) + Expr::Index(id, idx_expr, pos) => { + let idx = *self + .eval_expr(scope, idx_expr)? + .downcast::() + .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))?; + + let get_fn_name = format!("get${}", id); + let val = self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?; + Self::get_indexed_value(val, idx, *pos).and_then(|(mut v, _)| { + self.get_dot_val_helper(scope, v.as_mut(), inner_rhs) + }) } _ => Err(EvalAltResult::ErrorDotExpr(inner_lhs.position())), }, @@ -452,17 +233,53 @@ impl Engine { fn search_scope( scope: &Scope, id: &str, - map: impl FnOnce(&Variant) -> Result, + map: impl FnOnce(Dynamic) -> Result, begin: Position, ) -> Result<(usize, T), EvalAltResult> { scope .get(id) .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin)) - .and_then(move |(idx, _, val)| map(val.as_ref()).map(|v| (idx, v))) + .and_then(move |(idx, _, val)| map(val).map(|v| (idx, v))) } - fn indexed_value( - &self, + fn get_indexed_value( + val: Dynamic, + idx: i64, + pos: Position, + ) -> Result<(Dynamic, bool), EvalAltResult> { + if val.is::() { + let arr = val.downcast::().unwrap(); + + if idx >= 0 { + arr.get(idx as usize) + .cloned() + .map(|v| (v, true)) + .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::().unwrap(); + + if idx >= 0 { + s.chars() + .nth(idx as usize) + .map(|ch| (ch.into_dynamic(), false)) + .ok_or_else(|| EvalAltResult::ErrorStringBounds(s.chars().count(), idx, pos)) + } else { + Err(EvalAltResult::ErrorStringBounds( + s.chars().count(), + idx, + pos, + )) + } + } else { + Err(EvalAltResult::ErrorIndexing(pos)) + } + } + + fn eval_index_expr( + &mut self, scope: &mut Scope, id: &str, idx: &Expr, @@ -473,46 +290,13 @@ impl Engine { .downcast::() .map_err(|_| EvalAltResult::ErrorIndexExpr(idx.position()))?; - let mut is_array = false; - Self::search_scope( scope, id, - |val| { - if let Some(arr) = val.downcast_ref() as Option<&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_ref() as Option<&String> { - is_array = false; - - if idx >= 0 { - s.chars() - .nth(idx as usize) - .map(|ch| ch.into_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)) - } - }, + |val| Self::get_indexed_value(val, idx, begin), begin, ) - .map(|(idx_sc, val)| (is_array, idx_sc, idx as usize, val)) + .map(|(idx_sc, (val, is_array))| (is_array, idx_sc, idx as usize, val)) } fn str_replace_char(s: &mut String, idx: usize, new_ch: char) { @@ -532,15 +316,14 @@ impl Engine { } fn get_dot_val( - &self, + &mut self, scope: &mut Scope, dot_lhs: &Expr, dot_rhs: &Expr, ) -> Result { match dot_lhs { Expr::Identifier(id, pos) => { - let (sc_idx, mut target) = - Self::search_scope(scope, id, |x| Ok(x.into_dynamic()), *pos)?; + let (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 @@ -550,9 +333,9 @@ impl Engine { value } - Expr::Index(id, idx_raw, pos) => { + Expr::Index(id, idx_expr, pos) => { let (is_array, sc_idx, idx, mut target) = - self.indexed_value(scope, id, idx_raw, *pos)?; + self.eval_index_expr(scope, id, idx_expr, *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 @@ -576,31 +359,36 @@ impl Engine { } fn set_dot_val_helper( - &self, + &mut self, this_ptr: &mut Variant, dot_rhs: &Expr, mut source_val: Dynamic, ) -> Result { match dot_rhs { 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; + Expr::Dot(inner_lhs, inner_rhs) => match inner_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) .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())), @@ -611,7 +399,7 @@ impl Engine { } fn set_dot_val( - &self, + &mut self, scope: &mut Scope, dot_lhs: &Expr, dot_rhs: &Expr, @@ -619,8 +407,7 @@ impl Engine { ) -> Result { match dot_lhs { Expr::Identifier(id, pos) => { - let (sc_idx, mut target) = - Self::search_scope(scope, id, |x| Ok(x.into_dynamic()), *pos)?; + let (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 @@ -630,9 +417,9 @@ impl Engine { value } - Expr::Index(id, idx_raw, pos) => { + Expr::Index(id, iex_expr, pos) => { let (is_array, sc_idx, idx, mut target) = - self.indexed_value(scope, id, idx_raw, *pos)?; + self.eval_index_expr(scope, id, iex_expr, *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 @@ -654,7 +441,7 @@ impl Engine { } } - fn eval_expr(&self, scope: &mut Scope, expr: &Expr) -> Result { + fn eval_expr(&mut self, scope: &mut Scope, expr: &Expr) -> Result { match expr { Expr::IntegerConstant(i, _) => Ok((*i).into_dynamic()), Expr::FloatConstant(i, _) => Ok((*i).into_dynamic()), @@ -666,40 +453,35 @@ impl Engine { .map(|(_, _, val)| val) .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.clone(), *pos)), - Expr::Index(id, idx_raw, pos) => self - .indexed_value(scope, id, idx_raw, *pos) + Expr::Index(id, idx_expr, pos) => self + .eval_index_expr(scope, id, idx_expr, *pos) .map(|(_, _, _, x)| x), Expr::Assignment(ref id, rhs) => { let rhs_val = self.eval_expr(scope, rhs)?; - match **id { - Expr::Identifier(ref name, pos) => { + match id.as_ref() { + Expr::Identifier(name, pos) => { if let Some((idx, _, _)) = scope.get(name) { *scope.get_mut(name, idx) = rhs_val; Ok(().into_dynamic()) } else { - Err(EvalAltResult::ErrorVariableNotFound(name.clone(), pos)) + Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos)) } } - Expr::Index(ref id, ref idx_raw, pos) => { - let idx_pos = idx_raw.position(); + Expr::Index(id, idx_expr, pos) => { + let idx_pos = idx_expr.position(); - let idx = *match self.eval_expr(scope, &idx_raw)?.downcast_ref::() { - Some(x) => x, + let idx = *match self.eval_expr(scope, &idx_expr)?.downcast::() { + Ok(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)), + let val = match scope.get(id) { + Some((idx, _, _)) => scope.get_mut(id, idx), + _ => { + return Err(EvalAltResult::ErrorVariableNotFound(id.clone(), *pos)) + } }; if let Some(arr) = val.downcast_mut() as Option<&mut Array> { @@ -731,7 +513,7 @@ impl Engine { } } - Expr::Dot(ref dot_lhs, ref dot_rhs) => { + Expr::Dot(dot_lhs, dot_rhs) => { self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val) } @@ -744,26 +526,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 @@ -802,7 +588,7 @@ impl Engine { } pub(crate) fn eval_stmt( - &self, + &mut self, scope: &mut Scope, stmt: &Stmt, ) -> Result { @@ -913,10 +699,9 @@ impl Engine { Stmt::ReturnWithVal(Some(a), false, pos) => { let val = self.eval_expr(scope, a)?; Err(EvalAltResult::ErrorRuntime( - (val.downcast_ref() as Option<&String>) - .map(|s| s.as_ref()) - .unwrap_or("") - .to_string(), + val.downcast::() + .map(|s| *s) + .unwrap_or("".to_string()), *pos, )) } @@ -941,27 +726,27 @@ impl Engine { } /// 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(), 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(|x| println!("{}", x)), // default print/debug implementations + on_debug: Box::new(|x| println!("{}", x)), }; engine.register_builtins(); diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..6314a031 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,142 @@ +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, + /// Expecting `(` but not finding one. + MissingLeftBrace, + /// An open `{` is missing the corresponding closing `}`. + MissingRightBrace, + /// An open `[` is missing the corresponding closing `]`. + MissingRightBracket, + /// 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, "Missing parameters for function '{}'", 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 4e28b05f..ac73060b 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); } @@ -28,7 +74,7 @@ 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(); @@ -54,14 +100,14 @@ macro_rules! def_register { 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(); @@ -86,7 +132,7 @@ macro_rules! def_register { 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)); } } diff --git a/src/lib.rs b/src/lib.rs index 7ceebfd8..5c947e95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,12 +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}; +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 94f67cad..ad68bc77 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,67 +1,13 @@ -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)] -pub enum LexError { - UnexpectedChar(char), - UnterminatedString, - MalformedEscapeSequence(String), - MalformedNumber(String), - MalformedChar(String), - InputError(String), -} - 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::InputError(_) => "Input error", - } - } -} - -impl fmt::Display for LexError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - LERR::UnexpectedChar(c) => write!(f, "Unexpected '{}'", c), - LERR::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s), - LERR::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s), - LERR::MalformedChar(s) => write!(f, "Invalid character: '{}'", s), - LERR::InputError(s) => write!(f, "{}", s), - _ => write!(f, "{}", self.description()), - } - } -} - -#[derive(Debug, PartialEq, Clone)] -pub enum ParseErrorType { - BadInput(String), - InputPastEndOfFile, - UnknownOperator(String), - MissingRightParen, - MissingLeftBrace, - MissingRightBrace, - MissingRightBracket, - MalformedCallExpr, - MalformedIndexExpr, - VarExpectsIdentifier, - WrongFnDefinition, - FnMissingName, - FnMissingParams(String), -} - type PERR = ParseErrorType; +/// A location (line number + character position) in the input script. #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub struct Position { line: usize, @@ -69,22 +15,41 @@ pub struct Position { } impl Position { - pub fn new() -> Self { - Self { line: 1, pos: 0 } + /// Create a new `Position`. + pub fn new(line: usize, position: usize) -> Self { + Self { + line, + pos: position, + } } - pub fn line(&self) -> usize { - self.line + /// Get the line number (1-based), or `None` if EOF. + pub fn line(&self) -> Option { + match self.line { + 0 => None, + x => Some(x), + } } - pub fn position(&self) -> usize { - self.pos + /// 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; } + /// 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) { if self.pos == 0 { panic!("cannot rewind at position 0"); @@ -93,22 +58,31 @@ impl Position { } } + /// 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 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.is_eof() { write!(f, "EOF") } else { @@ -118,7 +92,7 @@ impl std::fmt::Display for Position { } impl std::fmt::Debug for Position { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.is_eof() { write!(f, "(EOF)") } else { @@ -127,65 +101,7 @@ impl std::fmt::Debug for Position { } } -#[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.position() - } - 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", - PERR::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 { - PERR::BadInput(ref s) => write!(f, "{}", s)?, - PERR::UnknownOperator(ref s) => write!(f, "{}: '{}'", self.description(), s)?, - PERR::FnMissingParams(ref s) => write!(f, "Missing parameters for function '{}'", s)?, - _ => write!(f, "{}", self.description())?, - } - - if !self.is_eof() { - write!(f, " ({})", self.1) - } else { - write!(f, " at the end of the script but there is no more input") - } - } -} - +/// Compiled AST (abstract syntax tree) of a Rhai script. pub struct AST(pub(crate) Vec, pub(crate) Vec); #[derive(Debug, Clone)] @@ -312,7 +228,7 @@ pub enum Token { PowerOfAssign, For, In, - LexErr(LexError), + LexError(LexError), } impl Token { @@ -324,7 +240,7 @@ impl Token { FloatConstant(ref s) => s.to_string().into(), Identifier(ref s) => s.to_string().into(), CharConstant(ref s) => s.to_string().into(), - LexErr(ref err) => err.to_string().into(), + LexError(ref err) => err.to_string().into(), ref token => (match token { StringConst(_) => "string", @@ -450,37 +366,19 @@ 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 { @@ -738,7 +636,7 @@ impl<'a> TokenIterator<'a> { if let Ok(val) = i64::from_str_radix(&out, radix) { Token::IntegerConstant(val) } else { - Token::LexErr(LERR::MalformedNumber(result.iter().collect())) + Token::LexError(LERR::MalformedNumber(result.iter().collect())) }, pos, )); @@ -752,7 +650,7 @@ impl<'a> TokenIterator<'a> { } else if let Ok(val) = out.parse::() { Token::FloatConstant(val) } else { - Token::LexErr(LERR::MalformedNumber(result.iter().collect())) + Token::LexError(LERR::MalformedNumber(result.iter().collect())) }, pos, )); @@ -797,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('\'') { @@ -807,17 +705,17 @@ impl<'a> TokenIterator<'a> { return Some(( if let Some(first_char) = chars.next() { if chars.count() != 0 { - Token::LexErr(LERR::MalformedChar(format!("'{}'", result))) + Token::LexError(LERR::MalformedChar(format!("'{}'", result))) } else { Token::CharConstant(first_char) } } else { - Token::LexErr(LERR::MalformedChar(format!("'{}'", result))) + 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)), @@ -873,7 +771,7 @@ impl<'a> TokenIterator<'a> { while let Some(c) = self.char_stream.next() { match c { '\n' => { - self.advance(); + self.new_line(); break; } _ => self.advance(), @@ -900,7 +798,7 @@ impl<'a> TokenIterator<'a> { } self.advance(); } - '\n' => self.advance(), + '\n' => self.new_line(), _ => (), } @@ -1070,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)), } } @@ -1092,8 +990,8 @@ impl<'a> Iterator for TokenIterator<'a> { pub fn lex(input: &str) -> TokenIterator<'_> { TokenIterator { - last: Token::LexErr(LERR::InputError("".into())), - pos: Position { line: 1, pos: 0 }, + last: Token::LexError(LERR::InputError("".into())), + pos: Position::new(1, 0), char_stream: input.chars().peekable(), } } @@ -1145,7 +1043,7 @@ fn parse_paren_expr<'a>( match input.next() { Some((Token::RightParen, _)) => Ok(expr), - _ => Err(ParseError(PERR::MissingRightParen, Position::eof())), + _ => Err(ParseError::new(PERR::MissingRightParen, Position::eof())), } } @@ -1170,8 +1068,8 @@ 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::MalformedCallExpr, pos)), + None => return Err(ParseError::new(PERR::MalformedCallExpr, Position::eof())), } input.next(); @@ -1189,13 +1087,10 @@ fn parse_index_expr<'a>( 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())), + Some(&(_, pos)) => return Err(ParseError::new(PERR::MalformedIndexExpr, pos)), + None => return Err(ParseError::new(PERR::MalformedIndexExpr, Position::eof())), }, - Err(mut err) => { - err.0 = PERR::MalformedIndexExpr; - return Err(err); - } + Err(err) => return Err(ParseError::new(PERR::MalformedIndexExpr, err.position())), } } @@ -1205,13 +1100,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(id, input, begin) } Some(_) => Ok(Expr::Identifier(id, begin)), None => Ok(Expr::Identifier(id, Position::eof())), @@ -1247,8 +1142,8 @@ 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, pos)), + None => Err(ParseError::new(PERR::MissingRightBracket, Position::eof())), } } @@ -1263,12 +1158,14 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result parse_array_expr(input, pos), Some((Token::True, pos)) => Ok(Expr::True(pos)), Some((Token::False, pos)) => Ok(Expr::False(pos)), - Some((Token::LexErr(le), pos)) => Err(ParseError(PERR::BadInput(le.to_string()), pos)), - Some((token, pos)) => Err(ParseError( + 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(PERR::InputPastEndOfFile, Position::eof())), + None => Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())), } } @@ -1535,7 +1432,7 @@ fn parse_binary_op<'a>( ) } token => { - return Err(ParseError( + return Err(ParseError::new( PERR::UnknownOperator(token.syntax().to_string()), pos, )) @@ -1597,14 +1494,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)?; @@ -1617,13 +1514,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() { @@ -1639,8 +1536,8 @@ 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(); @@ -1649,7 +1546,7 @@ fn parse_block<'a>(input: &mut Peekable>) -> Result (), // empty block - Some(&(Token::Fn, pos)) => return Err(ParseError(PERR::WrongFnDefinition, pos)), + Some(&(Token::Fn, pos)) => return Err(ParseError::new(PERR::WrongFnDefinition, pos)), _ => { while input.peek().is_some() { @@ -1673,8 +1570,8 @@ fn parse_block<'a>(input: &mut Peekable>) -> Result Err(ParseError(PERR::MissingRightBrace, pos)), - None => Err(ParseError(PERR::MissingRightBrace, Position::eof())), + Some(&(_, pos)) => Err(ParseError::new(PERR::MissingRightBrace, pos)), + None => Err(ParseError::new(PERR::MissingRightBrace, Position::eof())), } } @@ -1722,21 +1619,26 @@ 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(name), pos)), - None => return Err(ParseError(PERR::FnMissingParams(name), 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(); @@ -1752,8 +1654,8 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result { 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())), } }, } diff --git a/src/result.rs b/src/result.rs new file mode 100644 index 00000000..eb69c985 --- /dev/null +++ b/src/result.rs @@ -0,0 +1,153 @@ +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. + ErrorIndexing(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::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::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::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::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, "{} (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) + } + } + } +} + +impl From for EvalAltResult { + fn from(err: ParseError) -> Self { + Self::ErrorParsing(err) + } +} diff --git a/src/scope.rs b/src/scope.rs index 16035f48..a08e912f 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -3,6 +3,8 @@ 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}; /// @@ -13,8 +15,8 @@ use crate::any::{Any, Dynamic}; /// assert_eq!(engine.eval_with_scope::(&mut my_scope, "x + 1").unwrap(), 6); /// ``` /// -/// Between runs, `Engine` only remembers functions when not using own `Scope`. - +/// 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 { @@ -58,7 +60,7 @@ impl Scope { self.0 .iter() .enumerate() - .rev() + .rev() // Always search a Scope in reverse order .find(|(_, (n, _))| n == key) .map(|(i, (n, v))| (i, n.clone(), v.clone())) } @@ -68,10 +70,10 @@ impl Scope { self.0 .iter() .enumerate() - .rev() + .rev() // Always search a Scope in reverse order .find(|(_, (n, _))| n == key) - .map(|(_, (_, v))| v.downcast_ref() as Option<&T>) - .map(|v| v.unwrap().clone()) + .and_then(|(_, (_, v))| v.downcast_ref::()) + .map(|v| v.clone()) } /// Get a mutable reference to a variable in the Scope. @@ -86,13 +88,19 @@ impl Scope { } /// Get an iterator to variables in the Scope. - pub fn iter(&self) -> std::slice::Iter<(String, Dynamic)> { - self.0.iter() + pub fn iter(&self) -> impl Iterator { + self.0 + .iter() + .rev() // Always search a Scope in reverse order + .map(|(key, val)| (key.as_str(), val)) } /// Get a mutable iterator to variables in the Scope. - pub(crate) fn iter_mut(&mut self) -> std::slice::IterMut<(String, Dynamic)> { - self.0.iter_mut() + pub fn iter_mut(&mut self) -> impl Iterator { + self.0 + .iter_mut() + .rev() // Always search a Scope in reverse order + .map(|(key, val)| (key.as_str(), val)) } } 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(()) }