Merge pull request #104 from schungx/master
General code clean-up, bug fixes and improved documentation.
This commit is contained in:
commit
80051805d1
@ -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
224
README.md
@ -1,4 +1,4 @@
|
||||
# Rhai - embedded scripting for Rust
|
||||
# Rhai - Embedded Scripting for Rust
|
||||
|
||||
Rhai is an embedded scripting language for Rust that gives you a safe and easy way to add scripting to your applications.
|
||||
|
||||
@ -7,11 +7,11 @@ Rhai's current feature set:
|
||||
* Easy integration with Rust functions and data types
|
||||
* Fairly efficient (1 mil iterations in 0.75 sec on my 5 year old laptop)
|
||||
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
|
||||
* Easy-to-use language based on JS+Rust
|
||||
* Easy-to-use language similar to JS+Rust
|
||||
* Support for overloaded functions
|
||||
* No additional dependencies
|
||||
|
||||
**Note:** Currently, the version is 0.10.0-alpha1, so the language and APIs may change before they stabilize.*
|
||||
**Note:** Currently, the version is 0.10.2, so the language and API's may change before they stabilize.
|
||||
|
||||
## Installation
|
||||
|
||||
@ -19,7 +19,7 @@ You can install Rhai using crates by adding this line to your dependencies:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
rhai = "0.10.0"
|
||||
rhai = "0.10.2"
|
||||
```
|
||||
|
||||
or simply:
|
||||
@ -33,6 +33,16 @@ to use the latest version.
|
||||
|
||||
Beware that in order to use pre-releases (alpha and beta) you need to specify the exact version in your `Cargo.toml`.
|
||||
|
||||
## Optional Features
|
||||
|
||||
### `debug_msgs`
|
||||
|
||||
Print debug messages to stdout (using `println!`) related to function registrations and function calls.
|
||||
|
||||
### `no_stdlib`
|
||||
|
||||
Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities.
|
||||
|
||||
## Related
|
||||
|
||||
Other cool projects to check out:
|
||||
@ -90,7 +100,6 @@ cargo run --example rhai_runner scripts/any_script.rhai
|
||||
To get going with Rhai, you create an instance of the scripting engine and then run eval.
|
||||
|
||||
```rust
|
||||
extern crate rhai;
|
||||
use rhai::Engine;
|
||||
|
||||
fn main() {
|
||||
@ -108,7 +117,7 @@ You can also evaluate a script file:
|
||||
if let Ok(result) = engine.eval_file::<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
7
TODO
@ -1,7 +0,0 @@
|
||||
pre 1.0:
|
||||
- basic threads
|
||||
- stdlib
|
||||
1.0:
|
||||
- decide on postfix/prefix operators
|
||||
- advanced threads + actors
|
||||
- more literals
|
75
src/any.rs
75
src/any.rs
@ -1,14 +1,21 @@
|
||||
use std::any::{type_name, Any as StdAny, TypeId};
|
||||
use std::any::{type_name, TypeId};
|
||||
use std::fmt;
|
||||
|
||||
/// An raw value of any type.
|
||||
pub type Variant = dyn Any;
|
||||
|
||||
/// A boxed dynamic type containing any value.
|
||||
pub type Dynamic = Box<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;
|
||||
|
253
src/api.rs
253
src/api.rs
@ -1,9 +1,96 @@
|
||||
use crate::any::{Any, AnyExt, Dynamic};
|
||||
use crate::engine::{Engine, EvalAltResult, FnIntExt, FnSpec, Scope};
|
||||
use crate::parser::{lex, parse, ParseError, Position, AST};
|
||||
use crate::call::FuncArgs;
|
||||
use crate::engine::{Engine, FnAny, FnIntExt, FnSpec};
|
||||
use crate::error::ParseError;
|
||||
use crate::fn_register::RegisterFn;
|
||||
use crate::parser::{lex, parse, Position, AST};
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::scope::Scope;
|
||||
use std::any::TypeId;
|
||||
use std::sync::Arc;
|
||||
|
||||
impl Engine {
|
||||
impl<'a> Engine<'a> {
|
||||
pub(crate) fn register_fn_raw(
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
args: Option<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);
|
||||
}
|
||||
}
|
||||
|
119
src/builtin.rs
119
src/builtin.rs
@ -1,4 +1,6 @@
|
||||
use crate::{any::Any, Array, Dynamic, Engine, RegisterDynamicFn, RegisterFn};
|
||||
use crate::any::Any;
|
||||
use crate::engine::{Array, Engine};
|
||||
use crate::fn_register::RegisterFn;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub};
|
||||
|
||||
@ -34,6 +36,7 @@ macro_rules! reg_func1 {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_stdlib"))]
|
||||
macro_rules! reg_func2x {
|
||||
($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => (
|
||||
$(
|
||||
@ -42,6 +45,7 @@ macro_rules! reg_func2x {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_stdlib"))]
|
||||
macro_rules! reg_func2y {
|
||||
($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => (
|
||||
$(
|
||||
@ -50,6 +54,7 @@ macro_rules! reg_func2y {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_stdlib"))]
|
||||
macro_rules! reg_func3 {
|
||||
($self:expr, $x:expr, $op:expr, $v:ty, $w:ty, $r:ty, $( $y:ty ),*) => (
|
||||
$(
|
||||
@ -58,9 +63,9 @@ macro_rules! reg_func3 {
|
||||
)
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
/// Register the built-in library.
|
||||
pub(crate) fn register_builtins(&mut self) {
|
||||
impl Engine<'_> {
|
||||
/// Register the core built-in library.
|
||||
pub(crate) fn register_core_lib(&mut self) {
|
||||
fn add<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));
|
||||
}
|
||||
}
|
||||
|
18
src/call.rs
18
src/call.rs
@ -3,13 +3,27 @@
|
||||
|
||||
use crate::any::{Any, Variant};
|
||||
|
||||
pub trait FunArgs<'a> {
|
||||
/// Trait that represent arguments to a function call.
|
||||
pub trait FuncArgs<'a> {
|
||||
/// Convert to a `Vec` of `Variant` arguments.
|
||||
fn into_vec(self) -> Vec<&'a mut Variant>;
|
||||
}
|
||||
|
||||
impl<'a> FuncArgs<'a> for Vec<&'a mut Variant> {
|
||||
fn into_vec(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Any> FuncArgs<'a> for &'a mut Vec<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;
|
||||
|
990
src/engine.rs
990
src/engine.rs
File diff suppressed because it is too large
Load Diff
147
src/error.rs
Normal file
147
src/error.rs
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
)*
|
||||
|
||||
// Call the user-supplied function using ($clone) to
|
||||
// potentially clone the value, otherwise pass the reference.
|
||||
let r = f($(($clone)($par)),*);
|
||||
Ok(Box::new(r) as Dynamic)
|
||||
}
|
||||
|
||||
#[allow(unused_variables, unused_mut)]
|
||||
let mut drain = args.drain(..);
|
||||
$(
|
||||
// Downcast every element, return in case of a type mismatch
|
||||
let $par = ((*drain.next().unwrap()).downcast_mut() as Option<&mut $par>).unwrap();
|
||||
)*
|
||||
|
||||
// Call the user-supplied function using ($clone) to
|
||||
// potentially clone the value, otherwise pass the reference.
|
||||
let r = f($(($clone)($par)),*);
|
||||
Ok(Box::new(r) as Dynamic)
|
||||
};
|
||||
self.register_fn_raw(name.into(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
|
||||
self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
$($par: Any + Clone,)*
|
||||
FN: Fn($($param),*) -> Dynamic + 'static,
|
||||
> RegisterDynamicFn<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();
|
||||
)*
|
||||
|
||||
// Call the user-supplied function using ($clone) to
|
||||
// potentially clone the value, otherwise pass the reference.
|
||||
Ok(f($(($clone)($par)),*))
|
||||
}
|
||||
|
||||
#[allow(unused_variables, unused_mut)]
|
||||
let mut drain = args.drain(..);
|
||||
$(
|
||||
// Downcast every element, return in case of a type mismatch
|
||||
let $par = ((*drain.next().unwrap()).downcast_mut() as Option<&mut $par>).unwrap();
|
||||
)*
|
||||
|
||||
// Call the user-supplied function using ($clone) to
|
||||
// potentially clone the value, otherwise pass the reference.
|
||||
Ok(f($(($clone)($par)),*))
|
||||
};
|
||||
self.register_fn_raw(name.into(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
|
||||
self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,4 +150,4 @@ macro_rules! def_register {
|
||||
}
|
||||
|
||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
||||
def_register!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
|
||||
def_register!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
|
||||
|
34
src/lib.rs
34
src/lib.rs
@ -34,9 +34,24 @@
|
||||
|
||||
// needs to be here, because order matters for macros
|
||||
macro_rules! debug_println {
|
||||
() => (#[cfg(feature = "debug_msgs")] {print!("\n")});
|
||||
($fmt:expr) => (#[cfg(feature = "debug_msgs")] {print!(concat!($fmt, "\n"))});
|
||||
($fmt:expr, $($arg:tt)*) => (#[cfg(feature = "debug_msgs")] {print!(concat!($fmt, "\n"), $($arg)*)});
|
||||
() => (
|
||||
#[cfg(feature = "debug_msgs")]
|
||||
{
|
||||
print!("\n");
|
||||
}
|
||||
);
|
||||
($fmt:expr) => (
|
||||
#[cfg(feature = "debug_msgs")]
|
||||
{
|
||||
print!(concat!($fmt, "\n"));
|
||||
}
|
||||
);
|
||||
($fmt:expr, $($arg:tt)*) => (
|
||||
#[cfg(feature = "debug_msgs")]
|
||||
{
|
||||
print!(concat!($fmt, "\n"), $($arg)*);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
mod any;
|
||||
@ -44,10 +59,17 @@ mod api;
|
||||
mod builtin;
|
||||
mod call;
|
||||
mod engine;
|
||||
mod error;
|
||||
mod fn_register;
|
||||
mod parser;
|
||||
mod result;
|
||||
mod scope;
|
||||
|
||||
pub use any::Dynamic;
|
||||
pub use engine::{Array, Engine, EvalAltResult, Scope};
|
||||
pub use any::{Any, AnyExt, Dynamic, Variant};
|
||||
pub use call::FuncArgs;
|
||||
pub use engine::{Array, Engine};
|
||||
pub use error::{ParseError, ParseErrorType};
|
||||
pub use fn_register::{RegisterDynamicFn, RegisterFn};
|
||||
pub use parser::{ParseError, ParseErrorType, AST};
|
||||
pub use parser::{Position, AST};
|
||||
pub use result::EvalAltResult;
|
||||
pub use scope::Scope;
|
||||
|
981
src/parser.rs
981
src/parser.rs
File diff suppressed because it is too large
Load Diff
163
src/result.rs
Normal file
163
src/result.rs
Normal 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
116
src/scope.rs
Normal 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
14
tests/engine.rs
Normal 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(())
|
||||
}
|
@ -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(())
|
||||
}
|
||||
|
@ -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
18
tests/throw.rs
Normal 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
17
tests/types.rs
Normal 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(())
|
||||
}
|
@ -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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user