Code refactor, bug fixes, code docs.

This commit is contained in:
Stephen Chung 2020-03-04 22:00:01 +08:00
parent b4d56accd4
commit bb56a7a843
14 changed files with 949 additions and 676 deletions

View File

@ -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. To get going with Rhai, you create an instance of the scripting engine and then run eval.
```rust ```rust
extern crate rhai;
use rhai::Engine; use rhai::Engine;
fn main() { 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. 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 ```rust
extern crate rhai;
use rhai::{Dynamic, Engine, RegisterFn}; use rhai::{Dynamic, Engine, RegisterFn};
// Normal function // Normal function
@ -220,7 +218,6 @@ Generic functions can be used in Rhai, but you'll need to register separate inst
```rust ```rust
use std::fmt::Display; use std::fmt::Display;
extern crate rhai;
use rhai::{Engine, RegisterFn}; use rhai::{Engine, RegisterFn};
fn showit<T: Display>(x: &mut T) -> () { fn showit<T: Display>(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: Here's an more complete example of working with Rust. First the example, then we'll break it into parts:
```rust ```rust
extern crate rhai;
use rhai::{Engine, RegisterFn}; use rhai::{Engine, RegisterFn};
#[derive(Clone)] #[derive(Clone)]
@ -353,6 +349,8 @@ let x = new_ts();
print(x.type_of()); // prints "foo::bar::TestStruct" 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 # 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. 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: In this example, we first create a state with a few initialized variables, then thread the same state through multiple invocations:
```rust ```rust
extern crate rhai;
use rhai::{Engine, Scope}; use rhai::{Engine, Scope};
fn main() { fn main() {
@ -620,8 +617,12 @@ y[1] = 42;
print(y[1]); // prints 42 print(y[1]); // prints 42
let foo = [1, 2, 3][0]; // a syntax error for now - cannot index into literals ts.list = y; // arrays can be assigned completely (by value copy)
let foo = ts.list[0]; // a syntax error for now - cannot index into properties 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 let foo = y[0]; // this works
y.push(4); // 4 elements y.push(4); // 4 elements
@ -722,9 +723,11 @@ record == "Bob C. Davis: age 42";
let c = record[4]; let c = record[4];
c == 'C'; c == 'C';
let c = "foo"[0]; // a syntax error for now - cannot index into literals ts.s = record;
let c = ts.s[0]; // a syntax error for now - cannot index into properties let c = ts.s[4]; // indexing into properties is ok
let c = record[0]; // this works c == 'C';
let c = "foo"[0]; // a syntax error (for now) - cannot index into literals
// Escape sequences in strings // Escape sequences in strings
record += " \u2764\n"; // escape sequence of '❤' in Unicode record += " \u2764\n"; // escape sequence of '❤' in Unicode
@ -785,8 +788,17 @@ debug("world!"); // prints "world!" to stdout using debug formatting
```rust ```rust
// Any function that takes a &str argument can be used to override print and debug // 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_print(|x| println!("hello: {}", x));
engine.on_debug(|x: &str| println!("DEBUG: {}", x)); engine.on_debug(|x| println!("DEBUG: {}", x));
// Redirect logging output to somewhere else
let mut log: Vec<String> = 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 ## Comments

View File

@ -1,14 +1,21 @@
use std::any::{type_name, Any as StdAny, TypeId}; use std::any::{type_name, TypeId};
use std::fmt; use std::fmt;
/// An raw value of any type.
pub type Variant = dyn Any; pub type Variant = dyn Any;
/// A boxed dynamic type containing any value.
pub type Dynamic = Box<Variant>; pub type Dynamic = Box<Variant>;
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_id(&self) -> TypeId;
/// Get the name of this type.
fn type_name(&self) -> &'static str; fn type_name(&self) -> &'static str;
/// Convert into `Dynamic`.
fn into_dynamic(&self) -> Dynamic; fn into_dynamic(&self) -> Dynamic;
/// This type may only be implemented by `rhai`. /// This type may only be implemented by `rhai`.
@ -16,11 +23,7 @@ pub trait Any: StdAny {
fn _closed(&self) -> _Private; fn _closed(&self) -> _Private;
} }
impl<T> Any for T impl<T: Clone + std::any::Any + ?Sized> Any for T {
where
T: Clone + StdAny + ?Sized,
{
#[inline]
fn type_id(&self) -> TypeId { fn type_id(&self) -> TypeId {
TypeId::of::<T>() TypeId::of::<T>()
} }
@ -29,7 +32,6 @@ where
type_name::<T>() type_name::<T>()
} }
#[inline]
fn into_dynamic(&self) -> Dynamic { fn into_dynamic(&self) -> Dynamic {
Box::new(self.clone()) Box::new(self.clone())
} }
@ -40,20 +42,16 @@ where
} }
impl Variant { impl Variant {
//#[inline] /// Is this `Variant` a specific type?
// fn into_dynamic(&self) -> Box<Variant> { pub(crate) fn is<T: Any>(&self) -> bool {
// Any::into_dynamic(self)
// }
#[inline]
pub fn is<T: Any>(&self) -> bool {
let t = TypeId::of::<T>(); let t = TypeId::of::<T>();
let boxed = <Variant as Any>::type_id(self); let boxed = <Variant as Any>::type_id(self);
t == boxed t == boxed
} }
#[inline] /// Get a reference of a specific type to the `Variant`.
pub fn downcast_ref<T: Any>(&self) -> Option<&T> { pub(crate) fn downcast_ref<T: Any>(&self) -> Option<&T> {
if self.is::<T>() { if self.is::<T>() {
unsafe { Some(&*(self as *const Variant as *const T)) } unsafe { Some(&*(self as *const Variant as *const T)) }
} else { } else {
@ -61,8 +59,8 @@ impl Variant {
} }
} }
#[inline] /// Get a mutable reference of a specific type to the `Variant`.
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> { pub(crate) fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
if self.is::<T>() { if self.is::<T>() {
unsafe { Some(&mut *(self as *mut Variant as *mut T)) } unsafe { Some(&mut *(self as *mut Variant as *mut T)) }
} else { } 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 { impl fmt::Debug for Variant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("?") 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 { pub trait AnyExt: Sized {
/// Get a copy of a `Dynamic` value as a specific type.
fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self>; fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self>;
/// This type may only be implemented by `rhai`.
#[doc(hidden)]
fn _closed(&self) -> _Private;
} }
impl AnyExt for Dynamic { 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::<u32>().unwrap(), 42);
/// ```
fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self> { fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self> {
if self.is::<T>() { if self.is::<T>() {
unsafe { unsafe {
@ -98,9 +113,13 @@ impl AnyExt for Dynamic {
Err(self) 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. /// be implemented by this crate.
#[doc(hidden)] #[doc(hidden)]
pub struct _Private; pub struct _Private;

View File

@ -1,10 +1,95 @@
use crate::any::{Any, AnyExt}; use crate::any::{Any, AnyExt, Dynamic};
use crate::engine::{Engine, EvalAltResult, FnIntExt, FnSpec}; use crate::call::FuncArgs;
use crate::parser::{lex, parse, ParseError, Position, AST}; 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 crate::scope::Scope;
use std::any::TypeId;
use std::sync::Arc; use std::sync::Arc;
impl Engine { impl<'a> Engine<'a> {
pub(crate) fn register_fn_raw(
&mut self,
fn_name: &str,
args: Option<Vec<TypeId>>,
f: Box<FnAny>,
) {
debug_println!(
"Register function: {} ({})",
fn_name,
args.iter()
.map(|x| (*x).type_name())
.map(|name| self.map_type_name(name))
.collect::<Vec<_>>()
.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<T: Any + Clone>(&mut self) {
self.register_type_with_name::<T>(std::any::type_name::<T>());
}
/// 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<T: Any + Clone>(&mut self, type_name: &str) {
// Add the pretty-print type name into the map
self.type_names.insert(
std::any::type_name::<T>().to_string(),
type_name.to_string(),
);
}
/// Register an iterator adapter for a type with the `Engine`.
pub fn register_iterator<T: Any, F>(&mut self, f: F)
where
F: Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static,
{
self.type_iterators.insert(TypeId::of::<T>(), Arc::new(f));
}
/// Register a getter function for a member of a registered type with the `Engine`.
pub fn register_get<T: Any + Clone, U: Any + Clone>(
&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<T: Any + Clone, U: Any + Clone>(
&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<T: Any + Clone, U: Any + Clone>(
&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 /// Compile a string into an AST
pub fn compile(input: &str) -> Result<AST, ParseError> { pub fn compile(input: &str) -> Result<AST, ParseError> {
let tokens = lex(input); let tokens = lex(input);
@ -17,12 +102,12 @@ impl Engine {
use std::io::prelude::*; use std::io::prelude::*;
let mut f = File::open(filename) 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(); let mut contents = String::new();
f.read_to_string(&mut contents) 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)) .and_then(|_| Self::compile(&contents).map_err(EvalAltResult::ErrorParsing))
} }
@ -32,12 +117,12 @@ impl Engine {
use std::io::prelude::*; use std::io::prelude::*;
let mut f = File::open(filename) 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(); let mut contents = String::new();
f.read_to_string(&mut contents) 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::<T>(&contents)) .and_then(|_| self.eval::<T>(&contents))
} }
@ -74,7 +159,7 @@ impl Engine {
fns.iter().for_each(|f| { fns.iter().for_each(|f| {
self.script_fns.insert( self.script_fns.insert(
FnSpec { FnSpec {
ident: f.name.clone(), name: f.name.clone().into(),
args: None, args: None,
}, },
Arc::new(FnIntExt::Int(f.clone())), 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 /// Useful for when you don't need the result, but still need
/// to keep track of possible errors /// to keep track of possible errors
pub fn consume_file(&mut self, filename: &str) -> Result<(), EvalAltResult> { pub fn consume_file(&mut self, filename: &str) -> Result<(), EvalAltResult> {
@ -114,23 +199,23 @@ impl Engine {
use std::io::prelude::*; use std::io::prelude::*;
let mut f = File::open(filename) 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(); let mut contents = String::new();
f.read_to_string(&mut contents) 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)) .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 /// Useful for when you don't need the result, but still need
/// to keep track of possible errors /// to keep track of possible errors
pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> { pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> {
self.consume_with_scope(&mut Scope::new(), input) self.consume_with_scope(&mut Scope::new(), 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 /// Useful for when you don't need the result, but still need
/// to keep track of possible errors /// to keep track of possible errors
pub fn consume_with_scope( pub fn consume_with_scope(
@ -146,7 +231,7 @@ impl Engine {
for f in fns { for f in fns {
self.script_fns.insert( self.script_fns.insert(
FnSpec { FnSpec {
ident: f.name.clone(), name: f.name.clone().into(),
args: None, args: None,
}, },
Arc::new(FnIntExt::Int(f.clone())), Arc::new(FnIntExt::Int(f.clone())),
@ -164,13 +249,94 @@ impl Engine {
}) })
} }
/// Overrides `on_print` /// Call a script function defined in a compiled AST.
pub fn on_print(&mut self, callback: impl Fn(&str) + 'static) { ///
/// # 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<T, EvalAltResult> {
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); self.on_print = Box::new(callback);
} }
/// Overrides `on_debug` /// Override default action of `debug` (print to stdout using `println!`)
pub fn on_debug(&mut self, callback: impl Fn(&str) + 'static) { ///
/// # 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); self.on_debug = Box::new(callback);
} }
} }

View File

@ -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::fmt::{Debug, Display};
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}; 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. /// Register the built-in library.
pub(crate) fn register_builtins(&mut self) { pub(crate) fn register_builtins(&mut self) {
fn add<T: Add>(x: T, y: T) -> <T as Add>::Output { fn add<T: Add>(x: T, y: T) -> <T as Add>::Output {

View File

@ -3,13 +3,27 @@
use crate::any::{Any, Variant}; 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>; 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<T> {
fn into_vec(self) -> Vec<&'a mut Variant> {
self.iter_mut().map(|x| x as &mut Variant).collect()
}
}
macro_rules! impl_args { macro_rules! impl_args {
($($p:ident),*) => { ($($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> { fn into_vec(self) -> Vec<&'a mut Variant> {
let ($($p,)*) = self; let ($($p,)*) = self;

View File

@ -1,148 +1,34 @@
use std::any::TypeId; use std::any::TypeId;
use std::borrow::Cow;
use std::cmp::{PartialEq, PartialOrd}; use std::cmp::{PartialEq, PartialOrd};
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error;
use std::sync::Arc; use std::sync::Arc;
use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::any::{Any, AnyExt, Dynamic, Variant};
use crate::call::FunArgs; use crate::parser::{Expr, FnDef, Position, Stmt};
use crate::fn_register::RegisterFn; use crate::result::EvalAltResult;
use crate::parser::{Expr, FnDef, ParseError, Position, Stmt};
use crate::scope::Scope; use crate::scope::Scope;
/// An dynamic array of `Dynamic` values.
pub type Array = Vec<Dynamic>; pub type Array = Vec<Dynamic>;
pub type FnCallArgs<'a> = Vec<&'a mut Variant>; pub type FnCallArgs<'a> = Vec<&'a mut Variant>;
const KEYWORD_PRINT: &'static str = "print"; const KEYWORD_PRINT: &'static str = "print";
const KEYWORD_DEBUG: &'static str = "debug"; const KEYWORD_DEBUG: &'static str = "debug";
const KEYWORD_TYPE_OF: &'static str = "type_of"; 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)] #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct FnSpec { pub struct FnSpec<'a> {
pub ident: String, pub name: Cow<'a, str>,
pub args: Option<Vec<TypeId>>, pub args: Option<Vec<TypeId>>,
} }
type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>; type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
/// Rhai's engine type. This is what you use to run Rhai scripts /// Rhai main scripting engine.
/// ///
/// ```rust /// ```rust
/// extern crate rhai;
/// use rhai::Engine; /// use rhai::Engine;
/// ///
/// fn main() { /// fn main() {
@ -153,17 +39,17 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
/// } /// }
/// } /// }
/// ``` /// ```
pub struct Engine { pub struct Engine<'a> {
/// A hashmap containing all compiled functions known to the engine /// A hashmap containing all compiled functions known to the engine
fns: HashMap<FnSpec, Arc<FnIntExt>>, pub(crate) fns: HashMap<FnSpec<'a>, Arc<FnIntExt>>,
/// A hashmap containing all script-defined functions /// A hashmap containing all script-defined functions
pub(crate) script_fns: HashMap<FnSpec, Arc<FnIntExt>>, pub(crate) script_fns: HashMap<FnSpec<'a>, Arc<FnIntExt>>,
/// A hashmap containing all iterators known to the engine /// A hashmap containing all iterators known to the engine
type_iterators: HashMap<TypeId, Arc<IteratorFn>>, pub(crate) type_iterators: HashMap<TypeId, Arc<IteratorFn>>,
type_names: HashMap<String, String>, pub(crate) type_names: HashMap<String, String>,
pub(crate) on_print: Box<dyn Fn(&str)>, pub(crate) on_print: Box<dyn FnMut(&str) + 'a>,
pub(crate) on_debug: Box<dyn Fn(&str)>, pub(crate) on_debug: Box<dyn FnMut(&str) + 'a>,
} }
pub enum FnIntExt { pub enum FnIntExt {
@ -173,38 +59,19 @@ pub enum FnIntExt {
pub type FnAny = dyn Fn(FnCallArgs, Position) -> Result<Dynamic, EvalAltResult>; pub type FnAny = dyn Fn(FnCallArgs, Position) -> Result<Dynamic, EvalAltResult>;
impl Engine { impl Engine<'_> {
pub fn call_fn<'a, I, A, T>(&self, ident: I, args: A) -> Result<T, EvalAltResult>
where
I: Into<String>,
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,
)
})
})
}
/// Universal method for calling functions, that are either /// Universal method for calling functions, that are either
/// registered with the `Engine` or written in Rhai /// registered with the `Engine` or written in Rhai
fn call_fn_raw( pub(crate) fn call_fn_raw(
&self, &mut self,
ident: String, fn_name: &str,
args: FnCallArgs, args: FnCallArgs,
def_value: Option<&Dynamic>, def_value: Option<&Dynamic>,
pos: Position, pos: Position,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
debug_println!( debug_println!(
"Calling {}({})", "Calling function: {} ({})",
ident, fn_name,
args.iter() args.iter()
.map(|x| (*x).type_name()) .map(|x| (*x).type_name())
.map(|name| self.map_type_name(name)) .map(|name| self.map_type_name(name))
@ -212,17 +79,24 @@ impl Engine {
.join(", ") .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), // First search in script-defined functions (can override built-in),
// then in built-in's // then in built-in's
let fn_def = self.script_fns.get(&spec).or_else(|| { let fn_def = self
.script_fns
.get(&spec)
.or_else(|| {
spec.args = Some(args.iter().map(|a| Any::type_id(&**a)).collect()); spec.args = Some(args.iter().map(|a| Any::type_id(&**a)).collect());
self.fns.get(&spec) self.fns.get(&spec)
}); })
.map(|f| f.clone());
if let Some(f) = fn_def { if let Some(f) = fn_def {
match **f { match *f {
FnIntExt::Ext(ref f) => { FnIntExt::Ext(ref f) => {
let r = f(args, pos); let r = f(args, pos);
@ -230,25 +104,24 @@ impl Engine {
return r; return r;
} }
let callback = match spec.ident.as_str() { let callback = match spec.name.as_ref() {
KEYWORD_PRINT => &self.on_print, KEYWORD_PRINT => self.on_print.as_mut(),
KEYWORD_DEBUG => &self.on_debug, KEYWORD_DEBUG => self.on_debug.as_mut(),
_ => return r, _ => return r,
}; };
Ok(callback( Ok(callback(
r.unwrap() &r.unwrap()
.downcast::<String>() .downcast::<String>()
.map(|x| *x) .map(|s| *s)
.unwrap_or("error: not a string".into()) .unwrap_or("error: not a string".into()),
.as_str(),
) )
.into_dynamic()) .into_dynamic())
} }
FnIntExt::Int(ref f) => { FnIntExt::Int(ref f) => {
if f.params.len() != args.len() { if f.params.len() != args.len() {
return Err(EvalAltResult::ErrorFunctionArgsMismatch( return Err(EvalAltResult::ErrorFunctionArgsMismatch(
spec.ident, spec.name.into(),
f.params.len(), f.params.len(),
args.len(), args.len(),
pos, 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 Ok(self
.map_type_name(args[0].type_name()) .map_type_name(args[0].type_name())
.to_string() .to_string()
@ -286,72 +159,14 @@ impl Engine {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Err(EvalAltResult::ErrorFunctionNotFound( Err(EvalAltResult::ErrorFunctionNotFound(
format!("{} ({})", spec.ident, types_list.join(", ")), format!("{} ({})", spec.name, types_list.join(", ")),
pos, pos,
)) ))
} }
} }
pub(crate) fn register_fn_raw(
&mut self,
ident: String,
args: Option<Vec<TypeId>>,
f: Box<FnAny>,
) {
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<T: Any>(&mut self) {
// currently a no-op, exists for future extensibility
}
/// Register an iterator adapter for a type.
pub fn register_iterator<T: Any, F>(&mut self, f: F)
where
F: Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static,
{
self.type_iterators.insert(TypeId::of::<T>(), Arc::new(f));
}
/// Register a get function for a member of a registered type
pub fn register_get<T: Any + Clone, U: Any + Clone>(
&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<T: Any + Clone, U: Any + Clone>(
&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<T: Any + Clone, U: Any + Clone>(
&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( fn get_dot_val_helper(
&self, &mut self,
scope: &mut Scope, scope: &mut Scope,
this_ptr: &mut Variant, this_ptr: &mut Variant,
dot_rhs: &Expr, dot_rhs: &Expr,
@ -369,78 +184,44 @@ impl Engine {
.chain(args.iter_mut().map(|b| b.as_mut())) .chain(args.iter_mut().map(|b| b.as_mut()))
.collect(); .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) => { 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) => { Expr::Index(id, idx_expr, pos) => {
let idx = self let idx = *self
.eval_expr(scope, idx_raw)? .eval_expr(scope, idx_expr)?
.downcast_ref::<i64>() .downcast::<i64>()
.map(|i| *i) .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))?;
.ok_or(EvalAltResult::ErrorIndexExpr(idx_raw.position()))?;
let get_fn_name = "get$".to_string() + id; let get_fn_name = format!("get${}", id);
let val = self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?;
let mut val = self.call_fn_raw(get_fn_name, vec![this_ptr], None, *pos)?; Self::get_indexed_value(val, idx, *pos).map(|(v, _)| v)
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 { Expr::Dot(inner_lhs, inner_rhs) => match inner_lhs.as_ref() {
s.chars() Expr::Identifier(id, pos) => {
.nth(idx as usize) let get_fn_name = format!("get${}", id);
.map(|ch| ch.into_dynamic())
.ok_or_else(|| { self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
EvalAltResult::ErrorStringBounds(s.chars().count(), idx, *pos) .and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), inner_rhs))
}
Expr::Index(id, idx_expr, pos) => {
let idx = *self
.eval_expr(scope, idx_expr)?
.downcast::<i64>()
.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)
}) })
} else {
Err(EvalAltResult::ErrorStringBounds(
s.chars().count(),
idx,
*pos,
))
}
} else {
Err(EvalAltResult::ErrorIndexing(*pos))
}
}
Expr::Dot(inner_lhs, inner_rhs) => match **inner_lhs {
Expr::Identifier(ref id, pos) => {
let get_fn_name = "get$".to_string() + id;
let value = self
.call_fn_raw(get_fn_name, vec![this_ptr], None, pos)
.and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), inner_rhs))?;
// TODO - Should propagate changes back in this scenario:
//
// fn update(p) { p = something_else; }
// obj.prop.update();
//
// Right now, a copy of the object's property value is mutated, but not propagated
// back to the property via $set.
Ok(value)
}
Expr::Index(_, _, pos) => {
// TODO - Handle Expr::Index for these scenarios:
//
// let x = obj.prop[2].x;
// obj.prop[3] = 42;
//
Err(EvalAltResult::ErrorDotExpr(pos))
} }
_ => Err(EvalAltResult::ErrorDotExpr(inner_lhs.position())), _ => Err(EvalAltResult::ErrorDotExpr(inner_lhs.position())),
}, },
@ -452,17 +233,53 @@ impl Engine {
fn search_scope<T>( fn search_scope<T>(
scope: &Scope, scope: &Scope,
id: &str, id: &str,
map: impl FnOnce(&Variant) -> Result<T, EvalAltResult>, map: impl FnOnce(Dynamic) -> Result<T, EvalAltResult>,
begin: Position, begin: Position,
) -> Result<(usize, T), EvalAltResult> { ) -> Result<(usize, T), EvalAltResult> {
scope scope
.get(id) .get(id)
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin)) .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( fn get_indexed_value(
&self, val: Dynamic,
idx: i64,
pos: Position,
) -> Result<(Dynamic, bool), EvalAltResult> {
if val.is::<Array>() {
let arr = val.downcast::<Array>().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::<String>() {
let s = val.downcast::<String>().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, scope: &mut Scope,
id: &str, id: &str,
idx: &Expr, idx: &Expr,
@ -473,46 +290,13 @@ impl Engine {
.downcast::<i64>() .downcast::<i64>()
.map_err(|_| EvalAltResult::ErrorIndexExpr(idx.position()))?; .map_err(|_| EvalAltResult::ErrorIndexExpr(idx.position()))?;
let mut is_array = false;
Self::search_scope( Self::search_scope(
scope, scope,
id, id,
|val| { |val| Self::get_indexed_value(val, idx, begin),
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))
}
},
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) { fn str_replace_char(s: &mut String, idx: usize, new_ch: char) {
@ -532,15 +316,14 @@ impl Engine {
} }
fn get_dot_val( fn get_dot_val(
&self, &mut self,
scope: &mut Scope, scope: &mut Scope,
dot_lhs: &Expr, dot_lhs: &Expr,
dot_rhs: &Expr, dot_rhs: &Expr,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
match dot_lhs { match dot_lhs {
Expr::Identifier(id, pos) => { Expr::Identifier(id, pos) => {
let (sc_idx, mut target) = let (sc_idx, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
Self::search_scope(scope, id, |x| Ok(x.into_dynamic()), *pos)?;
let value = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs); 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 // In case the expression mutated `target`, we need to reassign it because
@ -550,9 +333,9 @@ impl Engine {
value value
} }
Expr::Index(id, idx_raw, pos) => { Expr::Index(id, idx_expr, pos) => {
let (is_array, sc_idx, idx, mut target) = 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); 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 // In case the expression mutated `target`, we need to reassign it because
@ -576,31 +359,36 @@ impl Engine {
} }
fn set_dot_val_helper( fn set_dot_val_helper(
&self, &mut self,
this_ptr: &mut Variant, this_ptr: &mut Variant,
dot_rhs: &Expr, dot_rhs: &Expr,
mut source_val: Dynamic, mut source_val: Dynamic,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
match dot_rhs { match dot_rhs {
Expr::Identifier(id, pos) => { 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::Dot(inner_lhs, inner_rhs) => match inner_lhs.as_ref() {
Expr::Identifier(ref id, 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)
.and_then(|mut v| { .and_then(|mut v| {
self.set_dot_val_helper(v.as_mut(), inner_rhs, source_val) self.set_dot_val_helper(v.as_mut(), inner_rhs, source_val)
.map(|_| v) // Discard Ok return value .map(|_| v) // Discard Ok return value
}) })
.and_then(|mut v| { .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(inner_lhs.position())),
@ -611,7 +399,7 @@ impl Engine {
} }
fn set_dot_val( fn set_dot_val(
&self, &mut self,
scope: &mut Scope, scope: &mut Scope,
dot_lhs: &Expr, dot_lhs: &Expr,
dot_rhs: &Expr, dot_rhs: &Expr,
@ -619,8 +407,7 @@ impl Engine {
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
match dot_lhs { match dot_lhs {
Expr::Identifier(id, pos) => { Expr::Identifier(id, pos) => {
let (sc_idx, mut target) = let (sc_idx, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
Self::search_scope(scope, id, |x| Ok(x.into_dynamic()), *pos)?;
let value = self.set_dot_val_helper(target.as_mut(), dot_rhs, source_val); 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 // In case the expression mutated `target`, we need to reassign it because
@ -630,9 +417,9 @@ impl Engine {
value value
} }
Expr::Index(id, idx_raw, pos) => { Expr::Index(id, iex_expr, pos) => {
let (is_array, sc_idx, idx, mut target) = 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); 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 // 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<Dynamic, EvalAltResult> { fn eval_expr(&mut self, scope: &mut Scope, expr: &Expr) -> Result<Dynamic, EvalAltResult> {
match expr { match expr {
Expr::IntegerConstant(i, _) => Ok((*i).into_dynamic()), Expr::IntegerConstant(i, _) => Ok((*i).into_dynamic()),
Expr::FloatConstant(i, _) => Ok((*i).into_dynamic()), Expr::FloatConstant(i, _) => Ok((*i).into_dynamic()),
@ -666,40 +453,35 @@ impl Engine {
.map(|(_, _, val)| val) .map(|(_, _, val)| val)
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.clone(), *pos)), .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.clone(), *pos)),
Expr::Index(id, idx_raw, pos) => self Expr::Index(id, idx_expr, pos) => self
.indexed_value(scope, id, idx_raw, *pos) .eval_index_expr(scope, id, idx_expr, *pos)
.map(|(_, _, _, x)| x), .map(|(_, _, _, x)| x),
Expr::Assignment(ref id, rhs) => { Expr::Assignment(ref id, rhs) => {
let rhs_val = self.eval_expr(scope, rhs)?; let rhs_val = self.eval_expr(scope, rhs)?;
match **id { match id.as_ref() {
Expr::Identifier(ref name, pos) => { Expr::Identifier(name, pos) => {
if let Some((idx, _, _)) = scope.get(name) { if let Some((idx, _, _)) = scope.get(name) {
*scope.get_mut(name, idx) = rhs_val; *scope.get_mut(name, idx) = rhs_val;
Ok(().into_dynamic()) Ok(().into_dynamic())
} else { } else {
Err(EvalAltResult::ErrorVariableNotFound(name.clone(), pos)) Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos))
} }
} }
Expr::Index(ref id, ref idx_raw, pos) => { Expr::Index(id, idx_expr, pos) => {
let idx_pos = idx_raw.position(); let idx_pos = idx_expr.position();
let idx = *match self.eval_expr(scope, &idx_raw)?.downcast_ref::<i64>() { let idx = *match self.eval_expr(scope, &idx_expr)?.downcast::<i64>() {
Some(x) => x, Ok(x) => x,
_ => return Err(EvalAltResult::ErrorIndexExpr(idx_pos)), _ => return Err(EvalAltResult::ErrorIndexExpr(idx_pos)),
}; };
let variable = &mut scope let val = match scope.get(id) {
.iter_mut() Some((idx, _, _)) => scope.get_mut(id, idx),
.rev() _ => {
.filter(|(name, _)| id == name) return Err(EvalAltResult::ErrorVariableNotFound(id.clone(), *pos))
.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 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) self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val)
} }
@ -744,7 +526,9 @@ impl Engine {
Expr::Array(contents, _) => { Expr::Array(contents, _) => {
let mut arr = Vec::new(); let mut arr = Vec::new();
contents.iter().try_for_each(|item| { contents
.iter()
.try_for_each::<_, Result<_, EvalAltResult>>(|item| {
let arg = self.eval_expr(scope, item)?; let arg = self.eval_expr(scope, item)?;
arr.push(arg); arr.push(arg);
Ok(()) Ok(())
@ -753,17 +537,19 @@ impl Engine {
Ok(Box::new(arr)) Ok(Box::new(arr))
} }
Expr::FunctionCall(fn_name, args, def_value, pos) => self.call_fn_raw( Expr::FunctionCall(fn_name, args, def_value, pos) => {
fn_name.into(), let mut args = args
args.iter() .iter()
.map(|expr| self.eval_expr(scope, expr)) .map(|expr| self.eval_expr(scope, expr))
.collect::<Result<Array, _>>()? .collect::<Result<Array, _>>()?;
.iter_mut()
.map(|b| b.as_mut()) self.call_fn_raw(
.collect(), fn_name,
args.iter_mut().map(|b| b.as_mut()).collect(),
def_value.as_ref(), def_value.as_ref(),
*pos, *pos,
), )
}
Expr::And(lhs, rhs) => Ok(Box::new( Expr::And(lhs, rhs) => Ok(Box::new(
*self *self
@ -802,7 +588,7 @@ impl Engine {
} }
pub(crate) fn eval_stmt( pub(crate) fn eval_stmt(
&self, &mut self,
scope: &mut Scope, scope: &mut Scope,
stmt: &Stmt, stmt: &Stmt,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
@ -913,10 +699,9 @@ impl Engine {
Stmt::ReturnWithVal(Some(a), false, pos) => { Stmt::ReturnWithVal(Some(a), false, pos) => {
let val = self.eval_expr(scope, a)?; let val = self.eval_expr(scope, a)?;
Err(EvalAltResult::ErrorRuntime( Err(EvalAltResult::ErrorRuntime(
(val.downcast_ref() as Option<&String>) val.downcast::<String>()
.map(|s| s.as_ref()) .map(|s| *s)
.unwrap_or("") .unwrap_or("".to_string()),
.to_string(),
*pos, *pos,
)) ))
} }
@ -941,27 +726,27 @@ impl Engine {
} }
/// Make a new 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 // User-friendly names for built-in types
let type_names = [ let type_names = [
("alloc::string::String", "string"), (type_name::<String>(), "string"),
( (type_name::<Array>(), "array"),
"alloc::vec::Vec<alloc::boxed::Box<dyn rhai::any::Any>>", (type_name::<Dynamic>(), "dynamic"),
"array",
),
("alloc::boxed::Box<dyn rhai::any::Any>", "dynamic"),
] ]
.iter() .iter()
.map(|(k, v)| (k.to_string(), v.to_string())) .map(|(k, v)| (k.to_string(), v.to_string()))
.collect(); .collect();
// Create the new scripting Engine
let mut engine = Engine { let mut engine = Engine {
fns: HashMap::new(), fns: HashMap::new(),
script_fns: HashMap::new(), script_fns: HashMap::new(),
type_iterators: HashMap::new(), type_iterators: HashMap::new(),
type_names, type_names,
on_print: Box::new(|x: &str| println!("{}", x)), on_print: Box::new(|x| println!("{}", x)), // default print/debug implementations
on_debug: Box::new(|x: &str| println!("{}", x)), on_debug: Box::new(|x| println!("{}", x)),
}; };
engine.register_builtins(); engine.register_builtins();

142
src/error.rs Normal file
View File

@ -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")
}
}
}

View File

@ -1,13 +1,59 @@
use std::any::TypeId; use std::any::TypeId;
use crate::any::{Any, Dynamic}; use crate::any::{Any, Dynamic};
use crate::engine::{Engine, EvalAltResult, FnCallArgs}; use crate::engine::{Engine, FnCallArgs};
use crate::parser::Position; 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::<i64>("add(40, 2)") {
/// println!("Answer: {}", result); // prints 42
/// }
/// ```
pub trait RegisterFn<FN, ARGS, RET> { pub trait RegisterFn<FN, ARGS, RET> {
/// Register a custom function with the `Engine`.
fn register_fn(&mut self, name: &str, f: FN); 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::<i64>("get_an_any(42)") {
/// println!("Answer: {}", result); // prints 42
/// }
/// ```
pub trait RegisterDynamicFn<FN, ARGS> { pub trait RegisterDynamicFn<FN, ARGS> {
/// Register a custom function returning `Dynamic` values with the `Engine`.
fn register_dynamic_fn(&mut self, name: &str, f: FN); fn register_dynamic_fn(&mut self, name: &str, f: FN);
} }
@ -28,7 +74,7 @@ macro_rules! def_register {
$($par: Any + Clone,)* $($par: Any + Clone,)*
FN: Fn($($param),*) -> RET + 'static, FN: Fn($($param),*) -> RET + 'static,
RET: Any RET: Any
> RegisterFn<FN, ($($mark,)*), RET> for Engine > RegisterFn<FN, ($($mark,)*), RET> for Engine<'_>
{ {
fn register_fn(&mut self, name: &str, f: FN) { fn register_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string(); let fn_name = name.to_string();
@ -54,14 +100,14 @@ macro_rules! def_register {
Ok(Box::new(r) as Dynamic) 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< impl<
$($par: Any + Clone,)* $($par: Any + Clone,)*
FN: Fn($($param),*) -> Dynamic + 'static, FN: Fn($($param),*) -> Dynamic + 'static,
> RegisterDynamicFn<FN, ($($mark,)*)> for Engine > RegisterDynamicFn<FN, ($($mark,)*)> for Engine<'_>
{ {
fn register_dynamic_fn(&mut self, name: &str, f: FN) { fn register_dynamic_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string(); let fn_name = name.to_string();
@ -86,7 +132,7 @@ macro_rules! def_register {
Ok(f($(($clone)($par)),*)) 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));
} }
} }

View File

@ -59,12 +59,17 @@ mod api;
mod builtin; mod builtin;
mod call; mod call;
mod engine; mod engine;
mod error;
mod fn_register; mod fn_register;
mod parser; mod parser;
mod result;
mod scope; mod scope;
pub use any::Dynamic; pub use any::{Any, AnyExt, Dynamic, Variant};
pub use engine::{Array, Engine, EvalAltResult}; pub use call::FuncArgs;
pub use engine::{Array, Engine};
pub use error::{ParseError, ParseErrorType};
pub use fn_register::{RegisterDynamicFn, RegisterFn}; 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; pub use scope::Scope;

View File

@ -1,67 +1,13 @@
use crate::Dynamic; use crate::any::Dynamic;
use crate::error::{LexError, ParseError, ParseErrorType};
use std::char; use std::char;
use std::error::Error;
use std::fmt;
use std::iter::Peekable; use std::iter::Peekable;
use std::str::Chars; 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; 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; type PERR = ParseErrorType;
/// A location (line number + character position) in the input script.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position { pub struct Position {
line: usize, line: usize,
@ -69,22 +15,41 @@ pub struct Position {
} }
impl Position { impl Position {
pub fn new() -> Self { /// Create a new `Position`.
Self { line: 1, pos: 0 } pub fn new(line: usize, position: usize) -> Self {
Self {
line,
pos: position,
}
} }
pub fn line(&self) -> usize { /// Get the line number (1-based), or `None` if EOF.
self.line pub fn line(&self) -> Option<usize> {
match self.line {
0 => None,
x => Some(x),
}
} }
pub fn position(&self) -> usize { /// Get the character position (1-based), or `None` if at beginning of a line.
self.pos pub fn position(&self) -> Option<usize> {
match self.pos {
0 => None,
x => Some(x),
}
} }
/// Advance by one character position.
pub(crate) fn advance(&mut self) { pub(crate) fn advance(&mut self) {
self.pos += 1; 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) { pub(crate) fn rewind(&mut self) {
if self.pos == 0 { if self.pos == 0 {
panic!("cannot rewind at position 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) { pub(crate) fn new_line(&mut self) {
self.line += 1; self.line += 1;
self.pos = 0; self.pos = 0;
} }
pub fn eof() -> Self { /// Create a `Position` at EOF.
pub(crate) fn eof() -> Self {
Self { line: 0, pos: 0 } Self { line: 0, pos: 0 }
} }
/// Is the `Position` at EOF?
pub fn is_eof(&self) -> bool { pub fn is_eof(&self) -> bool {
self.line == 0 self.line == 0
} }
} }
impl Default for Position {
fn default() -> Self {
Self::new(1, 0)
}
}
impl std::fmt::Display for Position { 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() { if self.is_eof() {
write!(f, "EOF") write!(f, "EOF")
} else { } else {
@ -118,7 +92,7 @@ impl std::fmt::Display for Position {
} }
impl std::fmt::Debug 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() { if self.is_eof() {
write!(f, "(EOF)") write!(f, "(EOF)")
} else { } else {
@ -127,65 +101,7 @@ impl std::fmt::Debug for Position {
} }
} }
#[derive(Debug, PartialEq, Clone)] /// Compiled AST (abstract syntax tree) of a Rhai script.
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")
}
}
}
pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<FnDef>); pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<FnDef>);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -312,7 +228,7 @@ pub enum Token {
PowerOfAssign, PowerOfAssign,
For, For,
In, In,
LexErr(LexError), LexError(LexError),
} }
impl Token { impl Token {
@ -324,7 +240,7 @@ impl Token {
FloatConstant(ref s) => s.to_string().into(), FloatConstant(ref s) => s.to_string().into(),
Identifier(ref s) => s.to_string().into(), Identifier(ref s) => s.to_string().into(),
CharConstant(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 { ref token => (match token {
StringConst(_) => "string", StringConst(_) => "string",
@ -450,37 +366,19 @@ impl Token {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn is_bin_op(&self) -> bool { pub fn is_binary_op(&self) -> bool {
use self::Token::*; use self::Token::*;
match *self { match *self {
RightBrace | RightBrace | RightParen | RightBracket | Plus | Minus | Multiply | Divide | Comma
RightParen | | Equals | LessThan | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo
RightBracket | | EqualsTo | NotEqualsTo | Pipe | Or | Ampersand | And | PowerOf => true,
Plus |
Minus |
Multiply |
Divide |
Comma |
// Period | <- does period count?
Equals |
LessThan |
GreaterThan |
LessThanEqualsTo |
GreaterThanEqualsTo |
EqualsTo |
NotEqualsTo |
Pipe |
Or |
Ampersand |
And |
PowerOf => true,
_ => false, _ => false,
} }
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn is_un_op(&self) -> bool { pub fn is_unary_op(&self) -> bool {
use self::Token::*; use self::Token::*;
match *self { match *self {
@ -738,7 +636,7 @@ impl<'a> TokenIterator<'a> {
if let Ok(val) = i64::from_str_radix(&out, radix) { if let Ok(val) = i64::from_str_radix(&out, radix) {
Token::IntegerConstant(val) Token::IntegerConstant(val)
} else { } else {
Token::LexErr(LERR::MalformedNumber(result.iter().collect())) Token::LexError(LERR::MalformedNumber(result.iter().collect()))
}, },
pos, pos,
)); ));
@ -752,7 +650,7 @@ impl<'a> TokenIterator<'a> {
} else if let Ok(val) = out.parse::<f64>() { } else if let Ok(val) = out.parse::<f64>() {
Token::FloatConstant(val) Token::FloatConstant(val)
} else { } else {
Token::LexErr(LERR::MalformedNumber(result.iter().collect())) Token::LexError(LERR::MalformedNumber(result.iter().collect()))
}, },
pos, pos,
)); ));
@ -797,7 +695,7 @@ impl<'a> TokenIterator<'a> {
'"' => { '"' => {
return match self.parse_string_const('"') { return match self.parse_string_const('"') {
Ok(out) => Some((Token::StringConst(out), pos)), 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('\'') { '\'' => match self.parse_string_const('\'') {
@ -807,17 +705,17 @@ impl<'a> TokenIterator<'a> {
return Some(( return Some((
if let Some(first_char) = chars.next() { if let Some(first_char) = chars.next() {
if chars.count() != 0 { if chars.count() != 0 {
Token::LexErr(LERR::MalformedChar(format!("'{}'", result))) Token::LexError(LERR::MalformedChar(format!("'{}'", result)))
} else { } else {
Token::CharConstant(first_char) Token::CharConstant(first_char)
} }
} else { } else {
Token::LexErr(LERR::MalformedChar(format!("'{}'", result))) Token::LexError(LERR::MalformedChar(format!("'{}'", result)))
}, },
pos, 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::LeftBrace, pos)),
'}' => return Some((Token::RightBrace, pos)), '}' => return Some((Token::RightBrace, pos)),
@ -873,7 +771,7 @@ impl<'a> TokenIterator<'a> {
while let Some(c) = self.char_stream.next() { while let Some(c) = self.char_stream.next() {
match c { match c {
'\n' => { '\n' => {
self.advance(); self.new_line();
break; break;
} }
_ => self.advance(), _ => self.advance(),
@ -900,7 +798,7 @@ impl<'a> TokenIterator<'a> {
} }
self.advance(); self.advance();
} }
'\n' => self.advance(), '\n' => self.new_line(),
_ => (), _ => (),
} }
@ -1070,7 +968,7 @@ impl<'a> TokenIterator<'a> {
)) ))
} }
x if x.is_whitespace() => (), 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<'_> { pub fn lex(input: &str) -> TokenIterator<'_> {
TokenIterator { TokenIterator {
last: Token::LexErr(LERR::InputError("".into())), last: Token::LexError(LERR::InputError("".into())),
pos: Position { line: 1, pos: 0 }, pos: Position::new(1, 0),
char_stream: input.chars().peekable(), char_stream: input.chars().peekable(),
} }
} }
@ -1145,7 +1043,7 @@ fn parse_paren_expr<'a>(
match input.next() { match input.next() {
Some((Token::RightParen, _)) => Ok(expr), 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)); return Ok(Expr::FunctionCall(id, args, None, begin));
} }
Some(&(Token::Comma, _)) => (), Some(&(Token::Comma, _)) => (),
Some(&(_, pos)) => return Err(ParseError(PERR::MalformedCallExpr, pos)), Some(&(_, pos)) => return Err(ParseError::new(PERR::MalformedCallExpr, pos)),
None => return Err(ParseError(PERR::MalformedCallExpr, Position::eof())), None => return Err(ParseError::new(PERR::MalformedCallExpr, Position::eof())),
} }
input.next(); input.next();
@ -1189,13 +1087,10 @@ fn parse_index_expr<'a>(
input.next(); input.next();
return Ok(Expr::Index(id, Box::new(idx), begin)); return Ok(Expr::Index(id, Box::new(idx), begin));
} }
Some(&(_, pos)) => return Err(ParseError(PERR::MalformedIndexExpr, pos)), Some(&(_, pos)) => return Err(ParseError::new(PERR::MalformedIndexExpr, pos)),
None => return Err(ParseError(PERR::MalformedIndexExpr, Position::eof())), None => return Err(ParseError::new(PERR::MalformedIndexExpr, Position::eof())),
}, },
Err(mut err) => { Err(err) => return Err(ParseError::new(PERR::MalformedIndexExpr, err.position())),
err.0 = PERR::MalformedIndexExpr;
return Err(err);
}
} }
} }
@ -1205,13 +1100,13 @@ fn parse_ident_expr<'a>(
begin: Position, begin: Position,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
match input.peek() { match input.peek() {
Some(&(Token::LeftParen, pos)) => { Some(&(Token::LeftParen, _)) => {
input.next(); input.next();
parse_call_expr(id, input, pos) parse_call_expr(id, input, begin)
} }
Some(&(Token::LeftBracket, pos)) => { Some(&(Token::LeftBracket, _)) => {
input.next(); input.next();
parse_index_expr(id, input, pos) parse_index_expr(id, input, begin)
} }
Some(_) => Ok(Expr::Identifier(id, begin)), Some(_) => Ok(Expr::Identifier(id, begin)),
None => Ok(Expr::Identifier(id, Position::eof())), None => Ok(Expr::Identifier(id, Position::eof())),
@ -1247,8 +1142,8 @@ fn parse_array_expr<'a>(
input.next(); input.next();
Ok(Expr::Array(arr, begin)) Ok(Expr::Array(arr, begin))
} }
Some(&(_, pos)) => Err(ParseError(PERR::MissingRightBracket, pos)), Some(&(_, pos)) => Err(ParseError::new(PERR::MissingRightBracket, pos)),
None => Err(ParseError(PERR::MissingRightBracket, Position::eof())), None => Err(ParseError::new(PERR::MissingRightBracket, Position::eof())),
} }
} }
@ -1263,12 +1158,14 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
Some((Token::LeftBracket, pos)) => parse_array_expr(input, pos), Some((Token::LeftBracket, pos)) => parse_array_expr(input, pos),
Some((Token::True, pos)) => Ok(Expr::True(pos)), Some((Token::True, pos)) => Ok(Expr::True(pos)),
Some((Token::False, pos)) => Ok(Expr::False(pos)), Some((Token::False, pos)) => Ok(Expr::False(pos)),
Some((Token::LexErr(le), pos)) => Err(ParseError(PERR::BadInput(le.to_string()), pos)), Some((Token::LexError(le), pos)) => {
Some((token, pos)) => Err(ParseError( Err(ParseError::new(PERR::BadInput(le.to_string()), pos))
}
Some((token, pos)) => Err(ParseError::new(
PERR::BadInput(format!("Unexpected '{}'", token.syntax())), PERR::BadInput(format!("Unexpected '{}'", token.syntax())),
pos, 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 => { token => {
return Err(ParseError( return Err(ParseError::new(
PERR::UnknownOperator(token.syntax().to_string()), PERR::UnknownOperator(token.syntax().to_string()),
pos, pos,
)) ))
@ -1597,14 +1494,14 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
let name = match input.next() { let name = match input.next() {
Some((Token::Identifier(s), _)) => s, Some((Token::Identifier(s), _)) => s,
Some((_, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier, pos)), Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)),
None => return Err(ParseError(PERR::VarExpectsIdentifier, Position::eof())), None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())),
}; };
match input.next() { match input.next() {
Some((Token::In, _)) => {} Some((Token::In, _)) => {}
Some((_, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier, pos)), Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)),
None => return Err(ParseError(PERR::VarExpectsIdentifier, Position::eof())), None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())),
} }
let expr = parse_expr(input)?; let expr = parse_expr(input)?;
@ -1617,13 +1514,13 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
fn parse_var<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> { fn parse_var<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
let pos = match input.next() { let pos = match input.next() {
Some((_, tok_pos)) => tok_pos, 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() { let name = match input.next() {
Some((Token::Identifier(s), _)) => s, Some((Token::Identifier(s), _)) => s,
Some((_, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier, pos)), Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)),
None => return Err(ParseError(PERR::VarExpectsIdentifier, Position::eof())), None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())),
}; };
match input.peek() { match input.peek() {
@ -1639,8 +1536,8 @@ fn parse_var<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
fn parse_block<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> { fn parse_block<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
match input.peek() { match input.peek() {
Some(&(Token::LeftBrace, _)) => (), Some(&(Token::LeftBrace, _)) => (),
Some(&(_, pos)) => return Err(ParseError(PERR::MissingLeftBrace, pos)), Some(&(_, pos)) => return Err(ParseError::new(PERR::MissingLeftBrace, pos)),
None => return Err(ParseError(PERR::MissingLeftBrace, Position::eof())), None => return Err(ParseError::new(PERR::MissingLeftBrace, Position::eof())),
} }
input.next(); input.next();
@ -1649,7 +1546,7 @@ fn parse_block<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
match input.peek() { match input.peek() {
Some(&(Token::RightBrace, _)) => (), // empty block Some(&(Token::RightBrace, _)) => (), // 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() { while input.peek().is_some() {
@ -1673,8 +1570,8 @@ fn parse_block<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
input.next(); input.next();
Ok(Stmt::Block(statements)) Ok(Stmt::Block(statements))
} }
Some(&(_, pos)) => Err(ParseError(PERR::MissingRightBrace, pos)), Some(&(_, pos)) => Err(ParseError::new(PERR::MissingRightBrace, pos)),
None => Err(ParseError(PERR::MissingRightBrace, Position::eof())), None => Err(ParseError::new(PERR::MissingRightBrace, Position::eof())),
} }
} }
@ -1722,21 +1619,26 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseError> { fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseError> {
let pos = match input.next() { let pos = match input.next() {
Some((_, tok_pos)) => tok_pos, 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() { let name = match input.next() {
Some((Token::Identifier(s), _)) => s, Some((Token::Identifier(s), _)) => s,
Some((_, pos)) => return Err(ParseError(PERR::FnMissingName, pos)), Some((_, pos)) => return Err(ParseError::new(PERR::FnMissingName, pos)),
None => return Err(ParseError(PERR::FnMissingName, Position::eof())), None => return Err(ParseError::new(PERR::FnMissingName, Position::eof())),
}; };
match input.peek() { match input.peek() {
Some(&(Token::LeftParen, _)) => { Some(&(Token::LeftParen, _)) => {
input.next(); input.next();
} }
Some(&(_, pos)) => return Err(ParseError(PERR::FnMissingParams(name), pos)), Some(&(_, pos)) => return Err(ParseError::new(PERR::FnMissingParams(name), pos)),
None => return Err(ParseError(PERR::FnMissingParams(name), Position::eof())), None => {
return Err(ParseError::new(
PERR::FnMissingParams(name),
Position::eof(),
))
}
} }
let mut params = Vec::new(); let mut params = Vec::new();
@ -1752,8 +1654,8 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
Some((Token::Identifier(s), _)) => { Some((Token::Identifier(s), _)) => {
params.push(s); params.push(s);
} }
Some((_, pos)) => return Err(ParseError(PERR::MalformedCallExpr, pos)), Some((_, pos)) => return Err(ParseError::new(PERR::MalformedCallExpr, pos)),
None => return Err(ParseError(PERR::MalformedCallExpr, Position::eof())), None => return Err(ParseError::new(PERR::MalformedCallExpr, Position::eof())),
} }
}, },
} }

153
src/result.rs Normal file
View File

@ -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<ParseError> for EvalAltResult {
fn from(err: ParseError) -> Self {
Self::ErrorParsing(err)
}
}

View File

@ -3,6 +3,8 @@ use crate::any::{Any, Dynamic};
/// A type containing information about current scope. /// A type containing information about current scope.
/// Useful for keeping state between `Engine` runs /// Useful for keeping state between `Engine` runs
/// ///
/// # Example
///
/// ```rust /// ```rust
/// use rhai::{Engine, Scope}; /// use rhai::{Engine, Scope};
/// ///
@ -13,8 +15,8 @@ use crate::any::{Any, Dynamic};
/// assert_eq!(engine.eval_with_scope::<i64>(&mut my_scope, "x + 1").unwrap(), 6); /// assert_eq!(engine.eval_with_scope::<i64>(&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)>); pub struct Scope(Vec<(String, Dynamic)>);
impl Scope { impl Scope {
@ -58,7 +60,7 @@ impl Scope {
self.0 self.0
.iter() .iter()
.enumerate() .enumerate()
.rev() .rev() // Always search a Scope in reverse order
.find(|(_, (n, _))| n == key) .find(|(_, (n, _))| n == key)
.map(|(i, (n, v))| (i, n.clone(), v.clone())) .map(|(i, (n, v))| (i, n.clone(), v.clone()))
} }
@ -68,10 +70,10 @@ impl Scope {
self.0 self.0
.iter() .iter()
.enumerate() .enumerate()
.rev() .rev() // Always search a Scope in reverse order
.find(|(_, (n, _))| n == key) .find(|(_, (n, _))| n == key)
.map(|(_, (_, v))| v.downcast_ref() as Option<&T>) .and_then(|(_, (_, v))| v.downcast_ref::<T>())
.map(|v| v.unwrap().clone()) .map(|v| v.clone())
} }
/// Get a mutable reference to a variable in the Scope. /// Get a mutable reference to a variable in the Scope.
@ -86,13 +88,19 @@ impl Scope {
} }
/// Get an iterator to variables in the Scope. /// Get an iterator to variables in the Scope.
pub fn iter(&self) -> std::slice::Iter<(String, Dynamic)> { pub fn iter(&self) -> impl Iterator<Item = (&str, &Dynamic)> {
self.0.iter() 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. /// Get a mutable iterator to variables in the Scope.
pub(crate) fn iter_mut(&mut self) -> std::slice::IterMut<(String, Dynamic)> { pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut Dynamic)> {
self.0.iter_mut() self.0
.iter_mut()
.rev() // Always search a Scope in reverse order
.map(|(key, val)| (key.as_str(), val))
} }
} }

14
tests/engine.rs Normal file
View File

@ -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(())
}

View File

@ -78,7 +78,7 @@ fn test_big_get_set() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.register_type::<TestChild>(); engine.register_type::<TestChild>();
engine.register_type::<TestParent>(); engine.register_type_with_name::<TestParent>("TestParent");
engine.register_get_set("x", TestChild::get_x, TestChild::set_x); engine.register_get_set("x", TestChild::get_x, TestChild::set_x);
engine.register_get_set("child", TestParent::get_child, TestParent::set_child); engine.register_get_set("child", TestParent::get_child, TestParent::set_child);
@ -90,5 +90,10 @@ fn test_big_get_set() -> Result<(), EvalAltResult> {
500 500
); );
assert_eq!(
engine.eval::<String>("let a = new_tp(); a.type_of()")?,
"TestParent"
);
Ok(()) Ok(())
} }