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] [package]
name = "rhai" name = "rhai"
version = "0.10.1" version = "0.10.2"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"
@ -16,3 +16,4 @@ include = [
[features] [features]
debug_msgs = [] 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. 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 * Easy integration with Rust functions and data types
* Fairly efficient (1 mil iterations in 0.75 sec on my 5 year old laptop) * 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) * 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 * Support for overloaded functions
* No additional dependencies * 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 ## Installation
@ -19,7 +19,7 @@ You can install Rhai using crates by adding this line to your dependencies:
```toml ```toml
[dependencies] [dependencies]
rhai = "0.10.0" rhai = "0.10.2"
``` ```
or simply: 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`. 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 ## Related
Other cool projects to check out: 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. 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() {
@ -108,7 +117,7 @@ You can also evaluate a script file:
if let Ok(result) = engine.eval_file::<i64>("hello_world.rhai") { ... } 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 ```rust
// Compile to an AST and store it for later evaluations // 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 ```rust
let ast = Engine::compile_file("hello_world.rhai").unwrap(); 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 # Values and types
The following primitive types are supported natively: 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 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 ```rust
let x = 42; let x = 42;
let y = x * 100.0; // error: cannot multiply i64 with f64 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 z = y.to_int() + x; // works
let c = 'X'; // character 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 # 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. 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
@ -209,7 +241,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) -> () {
@ -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: 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)]
@ -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 # 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; 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. 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 ```rust
extern crate rhai;
use rhai::{Engine, Scope}; use rhai::{Engine, Scope};
fn main() { fn main() {
let mut engine = Engine::new(); let mut engine = Engine::new();
// First create the state
let mut scope = Scope::new(); 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") { 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; return x + y;
} }
print(add(2, 3)) print(add(2, 3));
``` ```
Just like in Rust, you can also use an implicit return. Just like in Rust, you can also use an implicit return.
@ -508,14 +561,70 @@ fn add(x, y) {
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 ## Arrays
You can create arrays of values, and then access them with numeric indices. 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 * `push` - inserts an element at the end
* `pop` - removes the last element and returns it (() if empty) * `pop` - removes the last element and returns it (() if empty)
@ -531,9 +640,20 @@ 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];
let foo = y[0]; // this works 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(4); // 4 elements
y.push(5); // 5 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 ## For loops
@ -597,17 +717,34 @@ a.x = 500;
a.update(); 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 ## Strings and Chars
```rust ```rust
let name = "Bob"; let name = "Bob";
let middle_initial = 'C'; let middle_initial = 'C';
let last = 'Davis'; let last = "Davis";
let full_name = name + " " + middle_initial + ". " + last; let full_name = name + " " + middle_initial + ". " + last;
full_name == "Bob C. Davis"; 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 age = 42;
let record = full_name + ": age " + age; let record = full_name + ": age " + age;
record == "Bob C. Davis: age 42"; record == "Bob C. Davis: age 42";
@ -616,16 +753,27 @@ 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 = record[0]; // this works 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 // Unlike Rust, Rhai strings can be modified
record[4] = 'Z'; record[4] = '\x58'; // 0x58 = 'X'
record == "Bob Z. Davis: age 42"; 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 * `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 * `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.trim();
full_name.len() == 12; full_name.len() == 12;
full_name == "Bob C. Davis";
full_name.pad(15, '$'); full_name.pad(15, '$');
full_name.len() == 15; full_name.len() == 15;
@ -674,8 +823,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

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; 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;
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; fn into_dynamic(&self) -> Dynamic;
/// This type may only be implemented by `rhai`. /// This type may only be implemented by `rhai`.
@ -16,20 +23,15 @@ 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>()
} }
fn type_name(&self) -> String { fn type_name(&self) -> &'static str {
type_name::<T>().to_string() 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,9 +1,96 @@
use crate::any::{Any, AnyExt, Dynamic}; use crate::any::{Any, AnyExt, Dynamic};
use crate::engine::{Engine, EvalAltResult, FnIntExt, FnSpec, Scope}; 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 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: {} 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 /// 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);
@ -16,12 +103,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))
} }
@ -31,12 +118,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))
} }
@ -68,40 +155,44 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<T, EvalAltResult> { ) -> Result<T, EvalAltResult> {
let AST(os, fns) = ast; let AST(statements, functions) = ast;
fns.iter().for_each(|f| { functions.iter().for_each(|f| {
self.script_fns.insert( self.script_functions.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())),
); );
}); });
let result = os let result = statements
.iter() .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 { match result {
Err(EvalAltResult::Return(out, pos)) => Ok(*out.downcast::<T>().map_err(|a| { Err(EvalAltResult::Return(out, pos)) => out.downcast::<T>().map(|v| *v).map_err(|a| {
let name = self.map_type_name((*a).type_name()); EvalAltResult::ErrorMismatchOutputType(
EvalAltResult::ErrorMismatchOutputType(name, pos) self.map_type_name((*a).type_name()).to_string(),
})?), pos,
)
}),
Ok(out) => Ok(*out.downcast::<T>().map_err(|a| { Ok(out) => out.downcast::<T>().map(|v| *v).map_err(|a| {
let name = self.map_type_name((*a).type_name()); EvalAltResult::ErrorMismatchOutputType(
EvalAltResult::ErrorMismatchOutputType(name, Position::eof()) self.map_type_name((*a).type_name()).to_string(),
})?), Position::eof(),
)
}),
Err(err) => Err(err), 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 /// 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> {
@ -109,23 +200,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(
@ -137,40 +228,116 @@ impl Engine {
parse(&mut tokens.peekable()) parse(&mut tokens.peekable())
.map_err(|err| EvalAltResult::ErrorParsing(err)) .map_err(|err| EvalAltResult::ErrorParsing(err))
.and_then(|AST(ref os, ref fns)| { .and_then(|AST(ref statements, ref functions)| {
for f in fns { for f in functions {
// FIX - Why are functions limited to 6 parameters? self.script_functions.insert(
if f.params.len() > 6 {
return Ok(());
}
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())),
); );
} }
let val = os let val = statements
.iter() .iter()
.try_fold(Box::new(()) as Dynamic, |_, o| self.eval_stmt(scope, o)) .try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o))
.map(|_| ()); .map(|_| ());
self.script_fns.clear(); // Clean up engine self.script_functions.clear(); // Clean up engine
val val
}) })
} }
/// 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_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); 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, 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::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};
@ -34,6 +36,7 @@ macro_rules! reg_func1 {
) )
} }
#[cfg(not(feature = "no_stdlib"))]
macro_rules! reg_func2x { macro_rules! reg_func2x {
($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( ($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 { macro_rules! reg_func2y {
($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( ($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 { macro_rules! reg_func3 {
($self:expr, $x:expr, $op:expr, $v:ty, $w:ty, $r:ty, $( $y:ty ),*) => ( ($self:expr, $x:expr, $op:expr, $v:ty, $w:ty, $r:ty, $( $y:ty ),*) => (
$( $(
@ -58,9 +63,9 @@ macro_rules! reg_func3 {
) )
} }
impl Engine { impl Engine<'_> {
/// Register the built-in library. /// Register the core built-in library.
pub(crate) fn register_builtins(&mut self) { pub(crate) fn register_core_lib(&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 {
x + y x + y
} }
@ -103,9 +108,6 @@ impl Engine {
fn not(x: bool) -> bool { fn not(x: bool) -> bool {
!x !x
} }
fn concat(x: String, y: String) -> String {
x + &y
}
fn binary_and<T: BitAnd>(x: T, y: T) -> <T as BitAnd>::Output { fn binary_and<T: BitAnd>(x: T, y: T) -> <T as BitAnd>::Output {
x & y x & y
} }
@ -133,9 +135,6 @@ impl Engine {
fn pow_f64_i64(x: f64, y: i64) -> f64 { fn pow_f64_i64(x: f64, y: i64) -> f64 {
x.powi(y as i32) 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, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64);
reg_op!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64); reg_op!(self, "-", 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, "-", neg, i8, i16, i32, i64, f32, f64);
reg_un!(self, "!", not, bool); reg_un!(self, "!", not, bool);
self.register_fn("+", concat); self.register_fn("+", |x: String, y: String| x + &y); // String + String
self.register_fn("==", unit_eq); self.register_fn("==", |_: (), _: ()| true); // () == ()
// self.register_fn("[]", idx); // Register print and debug
// FIXME? Registering array lookups are a special case because we want to return boxes fn print_debug<T: Debug>(x: T) -> String {
// directly let ent = self.fns.entry("[]".to_string()).or_insert_with(Vec::new); format!("{:?}", x)
// (*ent).push(FnType::ExternalFn2(Box::new(idx))); }
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 // Register conversion functions
self.register_fn("to_float", |x: i8| x as f64); 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); 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 // Register array utility functions
fn push<T: Any>(list: &mut Array, item: T) { fn push<T: Any>(list: &mut Array, item: T) {
list.push(Box::new(item)); 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, (), bool, char);
reg_func3!(self, "pad", pad, &mut Array, i64, (), String, Array, ()); 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("pop", |list: &mut Array| {
self.register_dynamic_fn("shift", |list: &mut Array| { list.pop().unwrap_or_else(|| ().into_dynamic())
if list.len() > 0 { });
list.remove(0) self.register_dynamic_fn("shift", |list: &mut Array| match list.len() {
} else { 0 => ().into_dynamic(),
Box::new(()) _ => list.remove(0),
}
}); });
self.register_fn("len", |list: &mut Array| list.len() as i64); self.register_fn("len", |list: &mut Array| list.len() as i64);
self.register_fn("clear", |list: &mut Array| list.clear()); self.register_fn("clear", |list: &mut Array| list.clear());
@ -315,22 +331,5 @@ impl Engine {
chars.iter().for_each(|&ch| s.push(ch)); 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}; 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;

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 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);
} }
@ -15,8 +61,8 @@ pub struct Ref<A>(A);
pub struct Mut<A>(A); pub struct Mut<A>(A);
macro_rules! count_args { macro_rules! count_args {
() => {0usize}; () => { 0_usize };
($head:ident $($tail:ident)*) => {1usize + count_args!($($tail)*)}; ( $head:ident $($tail:ident)* ) => { 1_usize + count_args!($($tail)*) };
} }
macro_rules! def_register { macro_rules! def_register {
@ -28,65 +74,63 @@ 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();
let fun = move |mut args: FnCallArgs, pos: Position| { let fun = move |mut args: FnCallArgs, pos: Position| {
// Check for length at the beginning to avoid // Check for length at the beginning to avoid per-element bound checks.
// per-element bound checks.
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS { 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)] #[allow(unused_variables, unused_mut)]
let mut drain = args.drain(..); let mut drain = args.drain(..);
$( $(
// Downcast every element, return in case of a type mismatch // 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 // Call the user-supplied function using ($clone) to
// potentially clone the value, otherwise pass the reference. // potentially clone the value, otherwise pass the reference.
let r = f($(($clone)($par)),*); let r = f($(($clone)($par)),*);
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();
let fun = move |mut args: FnCallArgs, pos: Position| { let fun = move |mut args: FnCallArgs, pos: Position| {
// Check for length at the beginning to avoid // Check for length at the beginning to avoid per-element bound checks.
// per-element bound checks.
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS { 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)] #[allow(unused_variables, unused_mut)]
let mut drain = args.drain(..); let mut drain = args.drain(..);
$( $(
// Downcast every element, return in case of a type mismatch // 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 // Call the user-supplied function using ($clone) to
// potentially clone the value, otherwise pass the reference. // potentially clone the value, otherwise pass the reference.
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));
} }
} }
@ -106,4 +150,4 @@ macro_rules! def_register {
} }
#[cfg_attr(rustfmt, rustfmt_skip)] #[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 // needs to be here, because order matters for macros
macro_rules! debug_println { macro_rules! debug_println {
() => (#[cfg(feature = "debug_msgs")] {print!("\n")}); () => (
($fmt:expr) => (#[cfg(feature = "debug_msgs")] {print!(concat!($fmt, "\n"))}); #[cfg(feature = "debug_msgs")]
($fmt:expr, $($arg:tt)*) => (#[cfg(feature = "debug_msgs")] {print!(concat!($fmt, "\n"), $($arg)*)}); {
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; mod any;
@ -44,10 +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;
pub use any::Dynamic; pub use any::{Any, AnyExt, Dynamic, Variant};
pub use engine::{Array, Engine, EvalAltResult, Scope}; 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;

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

View File

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