Merge pull request #104 from schungx/master

General code clean-up, bug fixes and improved documentation.
This commit is contained in:
Stephen Chung 2020-03-06 21:12:29 +08:00 committed by GitHub
commit 80051805d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 2162 additions and 1189 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "rhai"
version = "0.10.1"
version = "0.10.2"
edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
description = "Embedded scripting for Rust"
@ -16,3 +16,4 @@ include = [
[features]
debug_msgs = []
no_stdlib = []

224
README.md
View File

@ -1,4 +1,4 @@
# Rhai - embedded scripting for Rust
# Rhai - Embedded Scripting for Rust
Rhai is an embedded scripting language for Rust that gives you a safe and easy way to add scripting to your applications.
@ -7,11 +7,11 @@ Rhai's current feature set:
* Easy integration with Rust functions and data types
* Fairly efficient (1 mil iterations in 0.75 sec on my 5 year old laptop)
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
* Easy-to-use language based on JS+Rust
* Easy-to-use language similar to JS+Rust
* Support for overloaded functions
* No additional dependencies
**Note:** Currently, the version is 0.10.0-alpha1, so the language and APIs may change before they stabilize.*
**Note:** Currently, the version is 0.10.2, so the language and API's may change before they stabilize.
## Installation
@ -19,7 +19,7 @@ You can install Rhai using crates by adding this line to your dependencies:
```toml
[dependencies]
rhai = "0.10.0"
rhai = "0.10.2"
```
or simply:
@ -33,6 +33,16 @@ to use the latest version.
Beware that in order to use pre-releases (alpha and beta) you need to specify the exact version in your `Cargo.toml`.
## Optional Features
### `debug_msgs`
Print debug messages to stdout (using `println!`) related to function registrations and function calls.
### `no_stdlib`
Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities.
## Related
Other cool projects to check out:
@ -90,7 +100,6 @@ cargo run --example rhai_runner scripts/any_script.rhai
To get going with Rhai, you create an instance of the scripting engine and then run eval.
```rust
extern crate rhai;
use rhai::Engine;
fn main() {
@ -108,7 +117,7 @@ You can also evaluate a script file:
if let Ok(result) = engine.eval_file::<i64>("hello_world.rhai") { ... }
```
If you want to repeatedly evaluate a script, you can compile it first into an AST form:
If you want to repeatedly evaluate a script, you can _compile_ it first into an AST (abstract syntax tree) form:
```rust
// Compile to an AST and store it for later evaluations
@ -121,12 +130,25 @@ for _ in 0..42 {
}
```
Compiling a script file into AST is also supported:
Compiling a script file is also supported:
```rust
let ast = Engine::compile_file("hello_world.rhai").unwrap();
```
Rhai also allows you to work _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust.
You do this via `call_fn`, which takes a compiled AST (output from `compile`) and the
function call arguments:
```rust
// Define a function in a script and compile to AST
let ast = Engine::compile("fn hello(x, y) { x.len() + y }")?;
// Evaluate the function in the AST, passing arguments into the script as a tuple
// (beware, arguments must be of the correct types because Rhai does not have built-in type conversions)
let result: i64 = engine.call_fn("hello", ast, (&mut String::from("abc"), &mut 123_i64))?;
```
# Values and types
The following primitive types are supported natively:
@ -144,6 +166,8 @@ All types are treated strictly separate by Rhai, meaning that `i32` and `i64` an
There is a `to_float` function to convert a supported number to an `f64`, and a `to_int` function to convert a supported number to `i64` and that's about it. For other conversions you can register your own conversion functions.
There is also a `type_of` function to detect the type of a value.
```rust
let x = 42;
let y = x * 100.0; // error: cannot multiply i64 with f64
@ -151,7 +175,16 @@ let y = x.to_float() * 100.0; // works
let z = y.to_int() + x; // works
let c = 'X'; // character
print("c is '" + c + "' and its code is " + c.to_int());
print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88"
// Use 'type_of' to get the type of variables
type_of(c) == "char";
type_of(x) == "i64";
y.type_of() == "f64";
if z.type_of() == "string" {
do_something_with_strong(z);
}
```
# Working with functions
@ -159,7 +192,6 @@ print("c is '" + c + "' and its code is " + c.to_int());
Rhai's scripting engine is very lightweight. It gets its ability from the functions in your program. To call these functions, you need to register them with the scripting engine.
```rust
extern crate rhai;
use rhai::{Dynamic, Engine, RegisterFn};
// Normal function
@ -209,7 +241,6 @@ Generic functions can be used in Rhai, but you'll need to register separate inst
```rust
use std::fmt::Display;
extern crate rhai;
use rhai::{Engine, RegisterFn};
fn showit<T: Display>(x: &mut T) -> () {
@ -245,7 +276,6 @@ print(to_int(123)); // what will happen?
Here's an more complete example of working with Rust. First the example, then we'll break it into parts:
```rust
extern crate rhai;
use rhai::{Engine, RegisterFn};
#[derive(Clone)]
@ -335,6 +365,14 @@ if let Ok(result) = engine.eval::<i64>("let x = new_ts(); x.foo()") {
}
```
`type_of` works fine with custom types and returns the name of the type:
```rust
let x = new_ts();
print(x.type_of()); // prints "foo::bar::TestStruct"
```
If you use `register_type_with_name` to register the custom type with a special pretty-print name, `type_of` will return that instead.
# Getters and setters
@ -391,25 +429,40 @@ a.x.change(); // Only a COPY of 'a.x' is changed. 'a.x' is NOT changed.
a.x == 500;
```
# Maintaining state
# Initializing and maintaining state
By default, Rhai treats each engine invocation as a fresh one, persisting only the functions that have been defined but no top-level state. This gives each one a fairly clean starting place. Sometimes, though, you want to continue using the same top-level state from one invocation to the next.
In this example, we thread the same state through multiple invocations:
In this example, we first create a state with a few initialized variables, then thread the same state through multiple invocations:
```rust
extern crate rhai;
use rhai::{Engine, Scope};
fn main() {
let mut engine = Engine::new();
// First create the state
let mut scope = Scope::new();
if let Ok(_) = engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5") { } else { assert!(false); }
// Then push some initialized variables into the state
// NOTE: Remember the default numbers used by Rhai are i64 and f64.
// Better stick to them or it gets hard to work with other variables in the script.
scope.push("y".into(), 42_i64);
scope.push("z".into(), 999_i64);
// First invocation
engine.eval_with_scope::<()>(&mut scope, r"
let x = 4 + 5 - y + z;
y = 1;
").expect("y and z not found?");
// Second invocation using the same state
if let Ok(result) = engine.eval_with_scope::<i64>(&mut scope, "x") {
println!("result: {}", result);
println!("result: {}", result); // should print 966
}
// Variable y is changed in the script
assert_eq!(scope.get_value::<i64>("y").unwrap(), 1);
}
```
@ -498,7 +551,7 @@ fn add(x, y) {
return x + y;
}
print(add(2, 3))
print(add(2, 3));
```
Just like in Rust, you can also use an implicit return.
@ -508,14 +561,70 @@ fn add(x, y) {
x + y
}
print(add(2, 3))
print(add(2, 3));
```
Remember that functions defined in script always take `Dynamic` arguments (i.e. the arguments can be of any type).
Arguments are passed by value, so all functions are _pure_ (i.e. they never modify their arguments).
Furthermore, functions can only be defined at the top level, never inside a block or another function.
```rust
// Top level is OK
fn add(x, y) {
x + y
}
// The following will not compile
fn do_addition(x) {
fn add_y(n) { // functions cannot be defined inside another function
n + y
}
add_y(x)
}
```
## Return
```rust
return;
return 123 + 456;
```
## Errors and Exceptions
```rust
if error != "" {
throw error; // 'throw' takes a string to form the exception text
}
throw; // no exception text
```
All of `Engine`'s evaluation/consuming methods return `Result<T, rhai::EvalAltResult>` with `EvalAltResult` holding error information.
Exceptions thrown via `throw` in the script can be captured by matching `Err(EvalAltResult::ErrorRuntime(reason, position))` with the exception text captured by the `reason` parameter.
```rust
let result = engine.eval::<i64>(&mut scope, r#"
let x = 42;
if x > 0 {
throw x + " is too large!";
}
"#);
println!(result); // prints "Runtime error: 42 is too large! (line 5, position 15)"
```
## Arrays
You can create arrays of values, and then access them with numeric indices.
The following standard functions operate on arrays:
The following functions (defined in the standard library but excluded if you use the `no_stdlib` feature) operate on arrays:
* `push` - inserts an element at the end
* `pop` - removes the last element and returns it (() if empty)
@ -531,9 +640,20 @@ y[1] = 42;
print(y[1]); // prints 42
let foo = [1, 2, 3][0]; // a syntax error for now - cannot index into literals
let foo = ts.list[0]; // a syntax error for now - cannot index into properties
let foo = y[0]; // this works
ts.list = y; // arrays can be assigned completely (by value copy)
let foo = ts.list[1];
foo == 42;
let foo = [1, 2, 3][0];
foo == 1;
fn abc() { [42, 43, 44] }
let foo = abc()[0];
foo == 42;
let foo = y[0];
foo == 1;
y.push(4); // 4 elements
y.push(5); // 5 elements
@ -570,7 +690,7 @@ engine.register_fn("push",
);
```
The type of a Rhai array is `rhai::Array`.
The type of a Rhai array is `rhai::Array`. `type_of()` returns `"array"`.
## For loops
@ -597,17 +717,34 @@ a.x = 500;
a.update();
```
## Numbers
```rust
let x = 123; // i64
let x = 123.4; // f64
let x = 123_456_789; // separators can be put anywhere inside the number
let x = 0x12abcd; // i64 in hex
let x = 0o777; // i64 in oct
let x = 0b1010_1111; // i64 in binary
```
Conversion functions (defined in the standard library but excluded if you use the `no_stdlib` feature):
* `to_int` - converts an `f32` or `f64` to `i64`
* `to_float` - converts an integer type to `f64`
## Strings and Chars
```rust
let name = "Bob";
let middle_initial = 'C';
let last = 'Davis';
let last = "Davis";
let full_name = name + " " + middle_initial + ". " + last;
full_name == "Bob C. Davis";
// String building with different types
// String building with different types (not available if 'no_stdlib' features is used)
let age = 42;
let record = full_name + ": age " + age;
record == "Bob C. Davis: age 42";
@ -616,16 +753,27 @@ record == "Bob C. Davis: age 42";
let c = record[4];
c == 'C';
let c = "foo"[0]; // a syntax error for now - cannot index into literals
let c = ts.s[0]; // a syntax error for now - cannot index into properties
let c = record[0]; // this works
ts.s = record;
let c = ts.s[4];
c == 'C';
let c = "foo"[0];
c == 'f';
let c = ("foo" + "bar")[5];
c == 'r';
// Escape sequences in strings
record += " \u2764\n"; // escape sequence of '❤' in Unicode
record == "Bob C. Davis: age 42 ❤\n"; // '\n' = new-line
// Unlike Rust, Rhai strings can be modified
record[4] = 'Z';
record == "Bob Z. Davis: age 42";
record[4] = '\x58'; // 0x58 = 'X'
record == "Bob X. Davis: age 42 ❤\n";
```
The following standard functions operate on strings:
The following standard functions (defined in the standard library but excluded if you use the `no_stdlib` feature) operate on strings:
* `len` - returns the number of characters (not number of bytes) in the string
* `pad` - pads the string with an character until a specified number of characters
@ -641,6 +789,7 @@ full_name.len() == 14;
full_name.trim();
full_name.len() == 12;
full_name == "Bob C. Davis";
full_name.pad(15, '$');
full_name.len() == 15;
@ -674,8 +823,17 @@ debug("world!"); // prints "world!" to stdout using debug formatting
```rust
// Any function that takes a &str argument can be used to override print and debug
engine.on_print(|x: &str| println!("hello: {}", x));
engine.on_debug(|x: &str| println!("DEBUG: {}", x));
engine.on_print(|x| println!("hello: {}", x));
engine.on_debug(|x| println!("DEBUG: {}", x));
// Redirect logging output to somewhere else
let mut log: Vec<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

7
TODO
View File

@ -1,7 +0,0 @@
pre 1.0:
- basic threads
- stdlib
1.0:
- decide on postfix/prefix operators
- advanced threads + actors
- more literals

View File

@ -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<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_name(&self) -> String;
/// Get the name of this type.
fn type_name(&self) -> &'static str;
/// Convert into `Dynamic`.
fn into_dynamic(&self) -> Dynamic;
/// This type may only be implemented by `rhai`.
@ -16,20 +23,15 @@ pub trait Any: StdAny {
fn _closed(&self) -> _Private;
}
impl<T> Any for T
where
T: Clone + StdAny + ?Sized,
{
#[inline]
impl<T: Clone + std::any::Any + ?Sized> Any for T {
fn type_id(&self) -> TypeId {
TypeId::of::<T>()
}
fn type_name(&self) -> String {
type_name::<T>().to_string()
fn type_name(&self) -> &'static str {
type_name::<T>()
}
#[inline]
fn into_dynamic(&self) -> Dynamic {
Box::new(self.clone())
}
@ -40,20 +42,16 @@ where
}
impl Variant {
//#[inline]
// fn into_dynamic(&self) -> Box<Variant> {
// Any::into_dynamic(self)
// }
#[inline]
pub fn is<T: Any>(&self) -> bool {
/// Is this `Variant` a specific type?
pub(crate) fn is<T: Any>(&self) -> bool {
let t = TypeId::of::<T>();
let boxed = <Variant as Any>::type_id(self);
t == boxed
}
#[inline]
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
/// Get a reference of a specific type to the `Variant`.
pub(crate) fn downcast_ref<T: Any>(&self) -> Option<&T> {
if self.is::<T>() {
unsafe { Some(&*(self as *const Variant as *const T)) }
} else {
@ -61,8 +59,8 @@ impl Variant {
}
}
#[inline]
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
/// Get a mutable reference of a specific type to the `Variant`.
pub(crate) fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
if self.is::<T>() {
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<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 {
/// 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> {
if self.is::<T>() {
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;

View File

@ -1,9 +1,96 @@
use crate::any::{Any, AnyExt, Dynamic};
use crate::engine::{Engine, EvalAltResult, FnIntExt, FnSpec, Scope};
use crate::parser::{lex, parse, ParseError, Position, AST};
use crate::call::FuncArgs;
use crate::engine::{Engine, FnAny, FnIntExt, FnSpec};
use crate::error::ParseError;
use crate::fn_register::RegisterFn;
use crate::parser::{lex, parse, Position, AST};
use crate::result::EvalAltResult;
use crate::scope::Scope;
use std::any::TypeId;
use std::sync::Arc;
impl Engine {
impl<'a> Engine<'a> {
pub(crate) fn register_fn_raw(
&mut self,
fn_name: &str,
args: Option<Vec<TypeId>>,
f: Box<FnAny>,
) {
debug_println!(
"Register function: {} for {} parameter(s)",
fn_name,
if let Some(a) = &args {
format!("{}", a.len())
} else {
"no".to_string()
}
);
let spec = FnSpec {
name: fn_name.to_string().into(),
args,
};
self.external_functions
.insert(spec, Arc::new(FnIntExt::Ext(f)));
}
/// Register a custom type for use with the `Engine`.
/// The type must be `Clone`.
pub fn register_type<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,
callback: impl Fn(&mut T) -> U + 'static,
) {
let get_name = "get$".to_string() + name;
self.register_fn(&get_name, callback);
}
/// Register a setter function for a member of a registered type with the `Engine`.
pub fn register_set<T: Any + Clone, U: Any + Clone>(
&mut self,
name: &str,
callback: impl Fn(&mut T, U) -> () + 'static,
) {
let set_name = "set$".to_string() + name;
self.register_fn(&set_name, callback);
}
/// Shorthand for registering both getter and setter functions
/// of a registered type with the `Engine`.
pub fn register_get_set<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
pub fn compile(input: &str) -> Result<AST, ParseError> {
let tokens = lex(input);
@ -16,12 +103,12 @@ impl Engine {
use std::io::prelude::*;
let mut f = File::open(filename)
.map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?;
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?;
let mut contents = String::new();
f.read_to_string(&mut contents)
.map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))
.and_then(|_| Self::compile(&contents).map_err(EvalAltResult::ErrorParsing))
}
@ -31,12 +118,12 @@ impl Engine {
use std::io::prelude::*;
let mut f = File::open(filename)
.map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?;
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?;
let mut contents = String::new();
f.read_to_string(&mut contents)
.map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))
.and_then(|_| self.eval::<T>(&contents))
}
@ -68,40 +155,44 @@ impl Engine {
scope: &mut Scope,
ast: &AST,
) -> Result<T, EvalAltResult> {
let AST(os, fns) = ast;
let AST(statements, functions) = ast;
fns.iter().for_each(|f| {
self.script_fns.insert(
functions.iter().for_each(|f| {
self.script_functions.insert(
FnSpec {
ident: f.name.clone(),
name: f.name.clone().into(),
args: None,
},
Arc::new(FnIntExt::Int(f.clone())),
);
});
let result = os
let result = statements
.iter()
.try_fold(Box::new(()) as Dynamic, |_, o| self.eval_stmt(scope, o));
.try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o));
self.script_fns.clear(); // Clean up engine
self.script_functions.clear(); // Clean up engine
match result {
Err(EvalAltResult::Return(out, pos)) => Ok(*out.downcast::<T>().map_err(|a| {
let name = self.map_type_name((*a).type_name());
EvalAltResult::ErrorMismatchOutputType(name, pos)
})?),
Err(EvalAltResult::Return(out, pos)) => out.downcast::<T>().map(|v| *v).map_err(|a| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).to_string(),
pos,
)
}),
Ok(out) => Ok(*out.downcast::<T>().map_err(|a| {
let name = self.map_type_name((*a).type_name());
EvalAltResult::ErrorMismatchOutputType(name, Position::eof())
})?),
Ok(out) => out.downcast::<T>().map(|v| *v).map_err(|a| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).to_string(),
Position::eof(),
)
}),
Err(err) => Err(err),
}
}
/// Evaluate a file, but only return errors, if there are any.
/// Evaluate a file, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need
/// to keep track of possible errors
pub fn consume_file(&mut self, filename: &str) -> Result<(), EvalAltResult> {
@ -109,23 +200,23 @@ impl Engine {
use std::io::prelude::*;
let mut f = File::open(filename)
.map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?;
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?;
let mut contents = String::new();
f.read_to_string(&mut contents)
.map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))
.and_then(|_| self.consume(&contents))
}
/// Evaluate a string, but only return errors, if there are any.
/// Evaluate a string, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need
/// to keep track of possible errors
pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> {
self.consume_with_scope(&mut Scope::new(), input)
}
/// Evaluate a string with own scope, but only return errors, if there are any.
/// Evaluate a string with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need
/// to keep track of possible errors
pub fn consume_with_scope(
@ -137,40 +228,116 @@ impl Engine {
parse(&mut tokens.peekable())
.map_err(|err| EvalAltResult::ErrorParsing(err))
.and_then(|AST(ref os, ref fns)| {
for f in fns {
// FIX - Why are functions limited to 6 parameters?
if f.params.len() > 6 {
return Ok(());
}
self.script_fns.insert(
.and_then(|AST(ref statements, ref functions)| {
for f in functions {
self.script_functions.insert(
FnSpec {
ident: f.name.clone(),
name: f.name.clone().into(),
args: None,
},
Arc::new(FnIntExt::Int(f.clone())),
);
}
let val = os
let val = statements
.iter()
.try_fold(Box::new(()) as Dynamic, |_, o| self.eval_stmt(scope, o))
.try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o))
.map(|_| ());
self.script_fns.clear(); // Clean up engine
self.script_functions.clear(); // Clean up engine
val
})
}
/// Overrides `on_print`
pub fn on_print(&mut self, callback: impl Fn(&str) + 'static) {
/// Call a script function defined in a compiled AST.
///
/// # Example
///
/// ```rust
/// # use rhai::{Engine, EvalAltResult};
/// # fn main() -> Result<(), EvalAltResult> {
/// let mut engine = Engine::new();
///
/// let ast = Engine::compile("fn add(x, y) { x.len() + y }")?;
///
/// let result: i64 = engine.call_fn("add", ast, (&mut String::from("abc"), &mut 123_i64))?;
///
/// assert_eq!(result, 126);
/// # Ok(())
/// # }
/// ```
pub fn call_fn<'f, A: FuncArgs<'f>, T: Any + Clone>(
&mut self,
name: &str,
ast: AST,
args: A,
) -> Result<T, EvalAltResult> {
let pos = Default::default();
ast.1.iter().for_each(|f| {
self.script_functions.insert(
FnSpec {
name: f.name.clone().into(),
args: None,
},
Arc::new(FnIntExt::Int(f.clone())),
);
});
let result = self
.call_fn_raw(name, args.into_vec(), None, pos)
.and_then(|b| {
b.downcast().map(|b| *b).map_err(|a| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).into(),
pos,
)
})
});
self.script_functions.clear(); // Clean up engine
result
}
/// Override default action of `print` (print to stdout using `println!`)
///
/// # Example
///
/// ```rust
/// # use rhai::Engine;
/// let mut result = String::from("");
/// {
/// let mut engine = Engine::new();
///
/// // Override action of 'print' function
/// engine.on_print(|s| result.push_str(s));
/// engine.consume("print(40 + 2);").unwrap();
/// }
/// assert_eq!(result, "42");
/// ```
pub fn on_print(&mut self, callback: impl FnMut(&str) + 'a) {
self.on_print = Box::new(callback);
}
/// Overrides `on_debug`
pub fn on_debug(&mut self, callback: impl Fn(&str) + 'static) {
/// Override default action of `debug` (print to stdout using `println!`)
///
/// # Example
///
/// ```rust
/// # use rhai::Engine;
/// let mut result = String::from("");
/// {
/// let mut engine = Engine::new();
///
/// // Override action of 'debug' function
/// engine.on_debug(|s| result.push_str(s));
/// engine.consume(r#"debug("hello");"#).unwrap();
/// }
/// assert_eq!(result, "\"hello\"");
/// ```
pub fn on_debug(&mut self, callback: impl FnMut(&str) + 'a) {
self.on_debug = Box::new(callback);
}
}

View File

@ -1,4 +1,6 @@
use crate::{any::Any, Array, Dynamic, Engine, RegisterDynamicFn, RegisterFn};
use crate::any::Any;
use crate::engine::{Array, Engine};
use crate::fn_register::RegisterFn;
use std::fmt::{Debug, Display};
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub};
@ -34,6 +36,7 @@ macro_rules! reg_func1 {
)
}
#[cfg(not(feature = "no_stdlib"))]
macro_rules! reg_func2x {
($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => (
$(
@ -42,6 +45,7 @@ macro_rules! reg_func2x {
)
}
#[cfg(not(feature = "no_stdlib"))]
macro_rules! reg_func2y {
($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => (
$(
@ -50,6 +54,7 @@ macro_rules! reg_func2y {
)
}
#[cfg(not(feature = "no_stdlib"))]
macro_rules! reg_func3 {
($self:expr, $x:expr, $op:expr, $v:ty, $w:ty, $r:ty, $( $y:ty ),*) => (
$(
@ -58,9 +63,9 @@ macro_rules! reg_func3 {
)
}
impl Engine {
/// Register the built-in library.
pub(crate) fn register_builtins(&mut self) {
impl Engine<'_> {
/// Register the core built-in library.
pub(crate) fn register_core_lib(&mut self) {
fn add<T: Add>(x: T, y: T) -> <T as Add>::Output {
x + y
}
@ -103,9 +108,6 @@ impl Engine {
fn not(x: bool) -> bool {
!x
}
fn concat(x: String, y: String) -> String {
x + &y
}
fn binary_and<T: BitAnd>(x: T, y: T) -> <T as BitAnd>::Output {
x & y
}
@ -133,9 +135,6 @@ impl Engine {
fn pow_f64_i64(x: f64, y: i64) -> f64 {
x.powi(y as i32)
}
fn unit_eq(_a: (), _b: ()) -> bool {
true
}
reg_op!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64);
reg_op!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64);
@ -171,13 +170,51 @@ impl Engine {
reg_un!(self, "-", neg, i8, i16, i32, i64, f32, f64);
reg_un!(self, "!", not, bool);
self.register_fn("+", concat);
self.register_fn("==", unit_eq);
self.register_fn("+", |x: String, y: String| x + &y); // String + String
self.register_fn("==", |_: (), _: ()| true); // () == ()
// self.register_fn("[]", idx);
// FIXME? Registering array lookups are a special case because we want to return boxes
// directly let ent = self.fns.entry("[]".to_string()).or_insert_with(Vec::new);
// (*ent).push(FnType::ExternalFn2(Box::new(idx)));
// Register print and debug
fn print_debug<T: Debug>(x: T) -> String {
format!("{:?}", x)
}
fn print<T: Display>(x: T) -> String {
format!("{}", x)
}
reg_func1!(self, "print", print, String, i8, u8, i16, u16);
reg_func1!(self, "print", print, String, i32, i64, u32, u64);
reg_func1!(self, "print", print, String, f32, f64, bool, char, String);
reg_func1!(self, "print", print_debug, String, Array);
self.register_fn("print", || "".to_string());
self.register_fn("print", |_: ()| "".to_string());
reg_func1!(self, "debug", print_debug, String, i8, u8, i16, u16);
reg_func1!(self, "debug", print_debug, String, i32, i64, u32, u64);
reg_func1!(self, "debug", print_debug, String, f32, f64, bool, char);
reg_func1!(self, "debug", print_debug, String, String, Array, ());
// Register array iterator
self.register_iterator::<Array, _>(|a| {
Box::new(a.downcast_ref::<Array>().unwrap().clone().into_iter())
});
// Register range function
self.register_iterator::<Range<i64>, _>(|a| {
Box::new(
a.downcast_ref::<Range<i64>>()
.unwrap()
.clone()
.map(|n| n.into_dynamic()),
)
});
self.register_fn("range", |i1: i64, i2: i64| (i1..i2));
}
/// Register the built-in library.
#[cfg(not(feature = "no_stdlib"))]
pub(crate) fn register_stdlib(&mut self) {
use crate::fn_register::RegisterDynamicFn;
// Register conversion functions
self.register_fn("to_float", |x: i8| x as f64);
@ -202,26 +239,6 @@ impl Engine {
self.register_fn("to_int", |ch: char| ch as i64);
// Register print and debug
fn print_debug<T: Debug>(x: T) -> String {
format!("{:?}", x)
}
fn print<T: Display>(x: T) -> String {
format!("{}", x)
}
reg_func1!(self, "print", print, String, i8, u8, i16, u16);
reg_func1!(self, "print", print, String, i32, i64, u32, u64);
reg_func1!(self, "print", print, String, f32, f64, bool, char, String);
reg_func1!(self, "print", print_debug, String, Array);
self.register_fn("print", || "".to_string());
self.register_fn("print", |_: ()| "".to_string());
reg_func1!(self, "debug", print_debug, String, i8, u8, i16, u16);
reg_func1!(self, "debug", print_debug, String, i32, i64, u32, u64);
reg_func1!(self, "debug", print_debug, String, f32, f64, bool, char);
reg_func1!(self, "debug", print_debug, String, String, Array, ());
// Register array utility functions
fn push<T: Any>(list: &mut Array, item: T) {
list.push(Box::new(item));
@ -244,13 +261,12 @@ impl Engine {
reg_func3!(self, "pad", pad, &mut Array, i64, (), bool, char);
reg_func3!(self, "pad", pad, &mut Array, i64, (), String, Array, ());
self.register_dynamic_fn("pop", |list: &mut Array| list.pop().unwrap_or(Box::new(())));
self.register_dynamic_fn("shift", |list: &mut Array| {
if list.len() > 0 {
list.remove(0)
} else {
Box::new(())
}
self.register_dynamic_fn("pop", |list: &mut Array| {
list.pop().unwrap_or_else(|| ().into_dynamic())
});
self.register_dynamic_fn("shift", |list: &mut Array| match list.len() {
0 => ().into_dynamic(),
_ => list.remove(0),
});
self.register_fn("len", |list: &mut Array| list.len() as i64);
self.register_fn("clear", |list: &mut Array| list.clear());
@ -315,22 +331,5 @@ impl Engine {
chars.iter().for_each(|&ch| s.push(ch));
}
});
// Register array iterator
self.register_iterator::<Array, _>(|a| {
Box::new(a.downcast_ref::<Array>().unwrap().clone().into_iter())
});
// Register range function
self.register_iterator::<Range<i64>, _>(|a| {
Box::new(
a.downcast_ref::<Range<i64>>()
.unwrap()
.clone()
.map(|n| Box::new(n) as Dynamic),
)
});
self.register_fn("range", |i1: i64, i2: i64| (i1..i2));
}
}

View File

@ -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<T> {
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;

File diff suppressed because it is too large Load Diff

147
src/error.rs Normal file
View File

@ -0,0 +1,147 @@
use crate::parser::Position;
use std::char;
use std::error::Error;
use std::fmt;
/// Error when tokenizing the script text.
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub enum LexError {
/// An unexpected character is encountered when tokenizing the script text.
UnexpectedChar(char),
/// A string literal is not terminated before a new-line or EOF.
UnterminatedString,
/// An string/character/numeric escape sequence is in an invalid format.
MalformedEscapeSequence(String),
/// An numeric literal is in an invalid format.
MalformedNumber(String),
/// An character literal is in an invalid format.
MalformedChar(String),
/// Error in the script text.
InputError(String),
}
impl Error for LexError {
fn description(&self) -> &str {
match *self {
Self::UnexpectedChar(_) => "Unexpected character",
Self::UnterminatedString => "Open string is not terminated",
Self::MalformedEscapeSequence(_) => "Unexpected values in escape sequence",
Self::MalformedNumber(_) => "Unexpected characters in number",
Self::MalformedChar(_) => "Char constant not a single character",
Self::InputError(_) => "Input error",
}
}
}
impl fmt::Display for LexError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnexpectedChar(c) => write!(f, "Unexpected '{}'", c),
Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s),
Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s),
Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s),
Self::InputError(s) => write!(f, "{}", s),
_ => write!(f, "{}", self.description()),
}
}
}
/// Type of error encountered when parsing a script.
#[derive(Debug, PartialEq, Clone)]
pub enum ParseErrorType {
/// Error in the script text. Wrapped value is the error message.
BadInput(String),
/// The script ends prematurely.
InputPastEndOfFile,
/// An unknown operator is encountered. Wrapped value is the operator.
UnknownOperator(String),
/// An open `(` is missing the corresponding closing `)`.
MissingRightParen(String),
/// Expecting `(` but not finding one.
MissingLeftBrace,
/// An open `{` is missing the corresponding closing `}`.
MissingRightBrace(String),
/// An open `[` is missing the corresponding closing `]`.
MissingRightBracket(String),
/// An expression in function call arguments `()` has syntax error.
MalformedCallExpr,
/// An expression in indexing brackets `[]` has syntax error.
MalformedIndexExpr,
/// Missing a variable name after the `let` keyword.
VarExpectsIdentifier,
/// Defining a function `fn` in an appropriate place (e.g. inside another function).
WrongFnDefinition,
/// Missing a function name after the `fn` keyword.
FnMissingName,
/// A function definition is missing the parameters list. Wrapped value is the function name.
FnMissingParams(String),
}
/// Error when parsing a script.
#[derive(Debug, PartialEq, Clone)]
pub struct ParseError(ParseErrorType, Position);
impl ParseError {
/// Create a new `ParseError`.
pub(crate) fn new(err: ParseErrorType, pos: Position) -> Self {
Self(err, pos)
}
/// Get the parse error.
pub fn error_type(&self) -> &ParseErrorType {
&self.0
}
/// Get the location in the script of the error.
pub fn position(&self) -> Position {
self.1
}
}
impl Error for ParseError {
fn description(&self) -> &str {
match self.0 {
ParseErrorType::BadInput(ref p) => p,
ParseErrorType::InputPastEndOfFile => "Script is incomplete",
ParseErrorType::UnknownOperator(_) => "Unknown operator",
ParseErrorType::MissingRightParen(_) => "Expecting ')'",
ParseErrorType::MissingLeftBrace => "Expecting '{'",
ParseErrorType::MissingRightBrace(_) => "Expecting '}'",
ParseErrorType::MissingRightBracket(_) => "Expecting ']'",
ParseErrorType::MalformedCallExpr => "Invalid expression in function call arguments",
ParseErrorType::MalformedIndexExpr => "Invalid index in indexing expression",
ParseErrorType::VarExpectsIdentifier => "Expecting name of a variable",
ParseErrorType::FnMissingName => "Expecting name in function declaration",
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
ParseErrorType::WrongFnDefinition => "Function definitions must be at top level and cannot be inside a block or another function",
}
}
fn cause(&self) -> Option<&dyn Error> {
None
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
ParseErrorType::BadInput(ref s) => write!(f, "{}", s)?,
ParseErrorType::UnknownOperator(ref s) => write!(f, "{}: '{}'", self.description(), s)?,
ParseErrorType::FnMissingParams(ref s) => {
write!(f, "Expecting parameters for function '{}'", s)?
}
ParseErrorType::MissingRightParen(ref s)
| ParseErrorType::MissingRightBrace(ref s)
| ParseErrorType::MissingRightBracket(ref s) => {
write!(f, "{} for {}", self.description(), s)?
}
_ => write!(f, "{}", self.description())?,
}
if !self.1.is_eof() {
write!(f, " ({})", self.1)
} else {
write!(f, " at the end of the script but there is no more input")
}
}
}

View File

@ -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::<i64>("add(40, 2)") {
/// println!("Answer: {}", result); // prints 42
/// }
/// ```
pub trait RegisterFn<FN, ARGS, RET> {
/// 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::<i64>("get_an_any(42)") {
/// println!("Answer: {}", result); // prints 42
/// }
/// ```
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);
}
@ -15,8 +61,8 @@ pub struct Ref<A>(A);
pub struct Mut<A>(A);
macro_rules! count_args {
() => {0usize};
($head:ident $($tail:ident)*) => {1usize + count_args!($($tail)*)};
() => { 0_usize };
( $head:ident $($tail:ident)* ) => { 1_usize + count_args!($($tail)*) };
}
macro_rules! def_register {
@ -28,65 +74,63 @@ macro_rules! def_register {
$($par: Any + Clone,)*
FN: Fn($($param),*) -> RET + 'static,
RET: Any
> RegisterFn<FN, ($($mark,)*), RET> for Engine
> RegisterFn<FN, ($($mark,)*), RET> for Engine<'_>
{
fn register_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string();
let fun = move |mut args: FnCallArgs, pos: Position| {
// Check for length at the beginning to avoid
// per-element bound checks.
// Check for length at the beginning to avoid per-element bound checks.
const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS {
return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, pos));
}
Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos))
} else {
#[allow(unused_variables, unused_mut)]
let mut drain = args.drain(..);
$(
// Downcast every element, return in case of a type mismatch
let $par = ((*drain.next().unwrap()).downcast_mut() as Option<&mut $par>).unwrap();
let $par = (drain.next().unwrap().downcast_mut() as Option<&mut $par>).unwrap();
)*
// Call the user-supplied function using ($clone) to
// potentially clone the value, otherwise pass the reference.
let r = f($(($clone)($par)),*);
Ok(Box::new(r) as Dynamic)
}
};
self.register_fn_raw(name.into(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
}
}
impl<
$($par: Any + Clone,)*
FN: Fn($($param),*) -> Dynamic + 'static,
> RegisterDynamicFn<FN, ($($mark,)*)> for Engine
> RegisterDynamicFn<FN, ($($mark,)*)> for Engine<'_>
{
fn register_dynamic_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string();
let fun = move |mut args: FnCallArgs, pos: Position| {
// Check for length at the beginning to avoid
// per-element bound checks.
// Check for length at the beginning to avoid per-element bound checks.
const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS {
return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, pos));
}
Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos))
} else {
#[allow(unused_variables, unused_mut)]
let mut drain = args.drain(..);
$(
// Downcast every element, return in case of a type mismatch
let $par = ((*drain.next().unwrap()).downcast_mut() as Option<&mut $par>).unwrap();
let $par = (drain.next().unwrap().downcast_mut() as Option<&mut $par>).unwrap();
)*
// Call the user-supplied function using ($clone) to
// potentially clone the value, otherwise pass the reference.
Ok(f($(($clone)($par)),*))
}
};
self.register_fn_raw(name.into(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
}
}
@ -106,4 +150,4 @@ macro_rules! def_register {
}
#[cfg_attr(rustfmt, rustfmt_skip)]
def_register!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
def_register!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);

View File

@ -34,9 +34,24 @@
// needs to be here, because order matters for macros
macro_rules! debug_println {
() => (#[cfg(feature = "debug_msgs")] {print!("\n")});
($fmt:expr) => (#[cfg(feature = "debug_msgs")] {print!(concat!($fmt, "\n"))});
($fmt:expr, $($arg:tt)*) => (#[cfg(feature = "debug_msgs")] {print!(concat!($fmt, "\n"), $($arg)*)});
() => (
#[cfg(feature = "debug_msgs")]
{
print!("\n");
}
);
($fmt:expr) => (
#[cfg(feature = "debug_msgs")]
{
print!(concat!($fmt, "\n"));
}
);
($fmt:expr, $($arg:tt)*) => (
#[cfg(feature = "debug_msgs")]
{
print!(concat!($fmt, "\n"), $($arg)*);
}
);
}
mod any;
@ -44,10 +59,17 @@ mod api;
mod builtin;
mod call;
mod engine;
mod error;
mod fn_register;
mod parser;
mod result;
mod scope;
pub use any::Dynamic;
pub use engine::{Array, Engine, EvalAltResult, Scope};
pub use any::{Any, AnyExt, Dynamic, Variant};
pub use call::FuncArgs;
pub use engine::{Array, Engine};
pub use error::{ParseError, ParseErrorType};
pub use fn_register::{RegisterDynamicFn, RegisterFn};
pub use parser::{ParseError, ParseErrorType, AST};
pub use parser::{Position, AST};
pub use result::EvalAltResult;
pub use scope::Scope;

File diff suppressed because it is too large Load Diff

163
src/result.rs Normal file
View File

@ -0,0 +1,163 @@
use std::error::Error;
use crate::any::Dynamic;
use crate::error::ParseError;
use crate::parser::Position;
/// Evaluation result.
///
/// All wrapped `Position` values represent the location in the script where the error occurs.
#[derive(Debug)]
pub enum EvalAltResult {
/// Syntax error.
ErrorParsing(ParseError),
/// Call to an unknown function. Wrapped value is the name of the function.
ErrorFunctionNotFound(String, Position),
/// Function call has incorrect number of arguments.
/// Wrapped values are the name of the function, the number of parameters required
/// and the actual number of arguments passed.
ErrorFunctionArgsMismatch(String, usize, usize, Position),
/// Non-boolean operand encountered for boolean operator. Wrapped value is the operator.
ErrorBooleanArgMismatch(String, Position),
/// Array access out-of-bounds.
/// Wrapped values are the current number of elements in the array and the index number.
ErrorArrayBounds(usize, i64, Position),
/// String indexing out-of-bounds.
/// Wrapped values are the current number of characters in the string and the index number.
ErrorStringBounds(usize, i64, Position),
/// Trying to index into a type that is not an array and not a string.
ErrorIndexingType(Position),
/// Trying to index into an array or string with an index that is not `i64`.
ErrorIndexExpr(Position),
/// The guard expression in an `if` statement does not return a boolean value.
ErrorIfGuard(Position),
/// The `for` statement encounters a type that is not an iterator.
ErrorFor(Position),
/// Usage of an unknown variable. Wrapped value is the name of the variable.
ErrorVariableNotFound(String, Position),
/// Assignment to an inappropriate LHS (left-hand-side) expression.
ErrorAssignmentToUnknownLHS(Position),
/// Returned type is not the same as the required output type.
/// Wrapped value is the type of the actual result.
ErrorMismatchOutputType(String, Position),
/// Error reading from a script file. Wrapped value is the path of the script file.
ErrorReadingScriptFile(String, std::io::Error),
/// Inappropriate member access.
ErrorDotExpr(Position),
/// Arithmetic error encountered. Wrapped value is the error message.
ErrorArithmetic(String, Position),
/// Run-time error encountered. Wrapped value is the error message.
ErrorRuntime(String, Position),
/// Internal use: Breaking out of loops.
LoopBreak,
/// Not an error: Value returned from a script via the `return` keyword.
/// Wrapped value is the result value.
Return(Dynamic, Position),
}
impl Error for EvalAltResult {
fn description(&self) -> &str {
match self {
Self::ErrorParsing(p) => p.description(),
Self::ErrorFunctionNotFound(_, _) => "Function not found",
Self::ErrorFunctionArgsMismatch(_, _, _, _) => {
"Function call with wrong number of arguments"
}
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
Self::ErrorIndexExpr(_) => "Indexing into an array or string expects an integer index",
Self::ErrorIndexingType(_) => "Indexing can only be performed on an array or a string",
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
"Array access expects non-negative index"
}
Self::ErrorArrayBounds(max, _, _) if *max == 0 => "Access of empty array",
Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds",
Self::ErrorStringBounds(_, index, _) if *index < 0 => {
"Indexing a string expects a non-negative index"
}
Self::ErrorStringBounds(max, _, _) if *max == 0 => "Indexing of empty string",
Self::ErrorStringBounds(_, _, _) => "String index out of bounds",
Self::ErrorIfGuard(_) => "If guard expects boolean expression",
Self::ErrorFor(_) => "For loop expects array or range",
Self::ErrorVariableNotFound(_, _) => "Variable not found",
Self::ErrorAssignmentToUnknownLHS(_) => {
"Assignment to an unsupported left-hand side expression"
}
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file",
Self::ErrorDotExpr(_) => "Malformed dot expression",
Self::ErrorArithmetic(_, _) => "Arithmetic error",
Self::ErrorRuntime(_, _) => "Runtime error",
Self::LoopBreak => "[Not Error] Breaks out of loop",
Self::Return(_, _) => "[Not Error] Function returns value",
}
}
fn cause(&self) -> Option<&dyn Error> {
None
}
}
impl std::fmt::Display for EvalAltResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let desc = self.description();
match self {
Self::ErrorFunctionNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
Self::ErrorVariableNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
Self::ErrorIndexingType(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorIndexExpr(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorIfGuard(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorAssignmentToUnknownLHS(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
Self::ErrorDotExpr(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorArithmetic(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
Self::ErrorRuntime(s, pos) if s.is_empty() => write!(f, "{} ({})", desc, pos),
Self::ErrorRuntime(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
Self::LoopBreak => write!(f, "{}", desc),
Self::Return(_, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorReadingScriptFile(filename, err) => {
write!(f, "{} '{}': {}", desc, filename, err)
}
Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p),
Self::ErrorFunctionArgsMismatch(fun, need, n, pos) => write!(
f,
"Function '{}' expects {} argument(s) but {} found ({})",
fun, need, n, pos
),
Self::ErrorBooleanArgMismatch(op, pos) => {
write!(f, "{} operator expects boolean operands ({})", op, pos)
}
Self::ErrorArrayBounds(_, index, pos) if *index < 0 => {
write!(f, "{}: {} < 0 ({})", desc, index, pos)
}
Self::ErrorArrayBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos),
Self::ErrorArrayBounds(max, index, pos) => write!(
f,
"Array index {} is out of bounds: only {} element{} in the array ({})",
index,
max,
if *max > 1 { "s" } else { "" },
pos
),
Self::ErrorStringBounds(_, index, pos) if *index < 0 => {
write!(f, "{}: {} < 0 ({})", desc, index, pos)
}
Self::ErrorStringBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos),
Self::ErrorStringBounds(max, index, pos) => write!(
f,
"String index {} is out of bounds: only {} character{} in the string ({})",
index,
max,
if *max > 1 { "s" } else { "" },
pos
),
}
}
}
impl From<ParseError> for EvalAltResult {
fn from(err: ParseError) -> Self {
Self::ErrorParsing(err)
}
}

116
src/scope.rs Normal file
View File

@ -0,0 +1,116 @@
use crate::any::{Any, Dynamic};
/// A type containing information about current scope.
/// Useful for keeping state between `Engine` runs
///
/// # Example
///
/// ```rust
/// use rhai::{Engine, Scope};
///
/// let mut engine = Engine::new();
/// let mut my_scope = Scope::new();
///
/// assert!(engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;").is_ok());
/// assert_eq!(engine.eval_with_scope::<i64>(&mut my_scope, "x + 1").unwrap(), 6);
/// ```
///
/// When searching for variables, newly-added variables are found before similarly-named but older variables,
/// allowing for automatic _shadowing_ of variables.
pub struct Scope(Vec<(String, Dynamic)>);
impl Scope {
/// Create a new Scope.
pub fn new() -> Self {
Self(Vec::new())
}
/// Empty the Scope.
pub fn clear(&mut self) {
self.0.clear();
}
/// Get the number of variables inside the Scope.
pub fn len(&self) -> usize {
self.0.len()
}
/// Add (push) a new variable to the Scope.
pub fn push<T: Any>(&mut self, key: String, value: T) {
self.0.push((key, Box::new(value)));
}
/// Add (push) a new variable to the Scope.
pub(crate) fn push_dynamic(&mut self, key: String, value: Dynamic) {
self.0.push((key, value));
}
/// Remove (pop) the last variable from the Scope.
pub fn pop(&mut self) -> Option<(String, Dynamic)> {
self.0.pop()
}
/// Truncate (rewind) the Scope to a previous size.
pub fn rewind(&mut self, size: usize) {
self.0.truncate(size);
}
/// Find a variable in the Scope, starting from the last.
pub fn get(&self, key: &str) -> Option<(usize, String, Dynamic)> {
self.0
.iter()
.enumerate()
.rev() // Always search a Scope in reverse order
.find(|(_, (name, _))| name == key)
.map(|(i, (name, value))| (i, name.clone(), value.clone()))
}
/// Get the value of a variable in the Scope, starting from the last.
pub fn get_value<T: Any + Clone>(&self, key: &str) -> Option<T> {
self.0
.iter()
.enumerate()
.rev() // Always search a Scope in reverse order
.find(|(_, (name, _))| name == key)
.and_then(|(_, (_, value))| value.downcast_ref::<T>())
.map(|value| value.clone())
}
/// Get a mutable reference to a variable in the Scope.
pub(crate) fn get_mut(&mut self, key: &str, index: usize) -> &mut Dynamic {
let entry = self.0.get_mut(index).expect("invalid index in Scope");
assert_eq!(entry.0, key, "incorrect key at Scope entry");
&mut entry.1
}
/// Get a mutable reference to a variable in the Scope and downcast it to a specific type
pub(crate) fn get_mut_by_type<T: Any + Clone>(&mut self, key: &str, index: usize) -> &mut T {
self.get_mut(key, index)
.downcast_mut::<T>()
.expect("wrong type cast")
}
/// Get an iterator to variables in the Scope.
pub fn iter(&self) -> impl Iterator<Item = (&str, &Dynamic)> {
self.0
.iter()
.rev() // Always search a Scope in reverse order
.map(|(key, value)| (key.as_str(), value))
}
/// Get a mutable iterator to variables in the Scope.
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut Dynamic)> {
self.0
.iter_mut()
.rev() // Always search a Scope in reverse order
.map(|(key, value)| (key.as_str(), value))
}
}
impl std::iter::Extend<(String, Dynamic)> for Scope {
fn extend<T: IntoIterator<Item = (String, Dynamic)>>(&mut self, iter: T) {
self.0.extend(iter);
}
}

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();
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("child", TestParent::get_child, TestParent::set_child);
@ -90,5 +90,10 @@ fn test_big_get_set() -> Result<(), EvalAltResult> {
500
);
assert_eq!(
engine.eval::<String>("let a = new_tp(); a.type_of()")?,
"TestParent"
);
Ok(())
}

View File

@ -5,13 +5,21 @@ fn test_string() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
assert_eq!(
engine.eval::<String>("\"Test string: \\u2764\"")?,
engine.eval::<String>(r#""Test string: \u2764""#)?,
"Test string: ❤".to_string()
);
assert_eq!(
engine.eval::<String>("\"foo\" + \"bar\"")?,
engine.eval::<String>(r#""Test string: \x58""#)?,
"Test string: X".to_string()
);
assert_eq!(
engine.eval::<String>(r#""foo" + "bar""#)?,
"foobar".to_string()
);
assert_eq!(
engine.eval::<String>(r#""foo" + 123.4556"#)?,
"foo123.4556".to_string()
);
Ok(())
}

18
tests/throw.rs Normal file
View File

@ -0,0 +1,18 @@
use rhai::{Engine, EvalAltResult};
#[test]
fn test_throw() {
let mut engine = Engine::new();
match engine.eval::<i64>(r#"if true { throw "hello" }"#) {
Ok(_) => panic!("not an error"),
Err(EvalAltResult::ErrorRuntime(s, _)) if s == "hello" => (),
Err(err) => panic!("wrong error: {}", err),
}
match engine.eval::<i64>(r#"throw;"#) {
Ok(_) => panic!("not an error"),
Err(EvalAltResult::ErrorRuntime(s, _)) if s == "" => (),
Err(err) => panic!("wrong error: {}", err),
}
}

17
tests/types.rs Normal file
View File

@ -0,0 +1,17 @@
use rhai::{Engine, EvalAltResult};
#[test]
fn test_type_of() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
assert_eq!(engine.eval::<String>("type_of(60 + 5)")?, "i64");
assert_eq!(engine.eval::<String>("type_of(1.0 + 2.0)")?, "f64");
assert_eq!(
engine.eval::<String>(r#"type_of([1.0, 2, "hello"])"#)?,
"array"
);
assert_eq!(engine.eval::<String>(r#"type_of("hello")"#)?, "string");
assert_eq!(engine.eval::<String>("let x = 123; x.type_of()")?, "i64");
Ok(())
}

View File

@ -14,3 +14,38 @@ fn test_var_scope() -> Result<(), EvalAltResult> {
Ok(())
}
#[test]
fn test_scope_eval() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
// First create the state
let mut scope = Scope::new();
// Then push some initialized variables into the state
// NOTE: Remember the default numbers used by Rhai are i64 and f64.
// Better stick to them or it gets hard to work with other variables in the script.
scope.push("y".into(), 42_i64);
scope.push("z".into(), 999_i64);
// First invocation
engine
.eval_with_scope::<()>(
&mut scope,
r"
let x = 4 + 5 - y + z;
y = 1;
",
)
.expect("y and z not found?");
// Second invocation using the same state
if let Ok(result) = engine.eval_with_scope::<i64>(&mut scope, "x") {
println!("result: {}", result); // should print 966
}
// Variable y is changed in the script
assert_eq!(scope.get_value::<i64>("y").unwrap(), 1);
Ok(())
}