Merge pull request #98 from schungx/master

A whole bunch of enhancements and bump to version 0.10.1.
This commit is contained in:
Stephen Chung 2020-03-03 09:18:28 +08:00 committed by GitHub
commit 659d1f2583
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 2317 additions and 1410 deletions

View File

@ -1,8 +1,8 @@
[package] [package]
name = "rhai" name = "rhai"
version = "0.9.1" version = "0.10.1"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"
homepage = "https://github.com/jonathandturner/rhai" homepage = "https://github.com/jonathandturner/rhai"
repository = "https://github.com/jonathandturner/rhai" repository = "https://github.com/jonathandturner/rhai"

383
README.md
View File

@ -11,57 +11,76 @@ Rhai's current feature set:
* Support for overloaded functions * Support for overloaded functions
* No additional dependencies * No additional dependencies
**Note:** Currently, the version is 0.9.1, so the language and APIs may change before they stabilize.* **Note:** Currently, the version is 0.10.0-alpha1, so the language and APIs may change before they stabilize.*
## Installation ## Installation
You can install Rhai using crates by adding this line to your dependences: You can install Rhai using crates by adding this line to your dependencies:
```toml ```toml
[dependencies] [dependencies]
rhai = "0.8.1" rhai = "0.10.0"
``` ```
or simply:
```toml
[dependencies]
rhai = "*"
```
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`.
## Related ## Related
Other cool projects to check out: Other cool projects to check out:
* [ChaiScript](http://chaiscript.com/) - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being lead by my cousin. * [ChaiScript](http://chaiscript.com/) - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being lead by my cousin.
* You can also check out the list of [scripting languages for Rust](https://github.com/rust-unofficial/awesome-rust#scripting) on [awesome-rust](https://github.com/rust-unofficial/awesome-rust) * You can also check out the list of [scripting languages for Rust](https://github.com/rust-unofficial/awesome-rust#scripting) on [awesome-rust](https://github.com/rust-unofficial/awesome-rust)
## Examples ## Examples
The repository contains several examples in the `examples` folder: The repository contains several examples in the `examples` folder:
- `arrays_and_structs` demonstrates registering a new type to Rhai and the usage of arrays on it
- `custom_types_and_methods` shows how to register a type and methods for it * `arrays_and_structs` demonstrates registering a new type to Rhai and the usage of arrays on it
- `hello` simple example that evaluates an expression and prints the result * `custom_types_and_methods` shows how to register a type and methods for it
- `reuse_scope` evaluates two pieces of code in separate runs, but using a common scope * `hello` simple example that evaluates an expression and prints the result
- `rhai_runner` runs each filename passed to it as a Rhai script * `reuse_scope` evaluates two pieces of code in separate runs, but using a common scope
- `simple_fn` shows how to register a Rust function to a Rhai engine * `rhai_runner` runs each filename passed to it as a Rhai script
- `repl` a simple REPL, see source code for what it can do at the moment * `simple_fn` shows how to register a Rust function to a Rhai engine
* `repl` a simple REPL, see source code for what it can do at the moment
Examples can be run with the following command: Examples can be run with the following command:
```bash ```bash
cargo run --example name cargo run --example name
``` ```
## Example Scripts ## Example Scripts
We also have a few examples scripts that showcase Rhai's features, all stored in the `scripts` folder: We also have a few examples scripts that showcase Rhai's features, all stored in the `scripts` folder:
- `array.rhai` - arrays in Rhai
- `assignment.rhai` - variable declarations * `array.rhai` - arrays in Rhai
- `comments.rhai` - just comments * `assignment.rhai` - variable declarations
- `function_decl1.rhai` - a function without parameters * `comments.rhai` - just comments
- `function_decl2.rhai` - a function with two parameters * `for1.rhai` - for loops
- `function_decl3.rhai` - a function with many parameters * `function_decl1.rhai` - a function without parameters
- `if1.rhai` - if example * `function_decl2.rhai` - a function with two parameters
- `loop.rhai` - endless loop in Rhai, this example emulates a do..while cycle * `function_decl3.rhai` - a function with many parameters
- `op1.rhai` - just a simple addition * `if1.rhai` - if example
- `op2.rhai` - simple addition and multiplication * `loop.rhai` - endless loop in Rhai, this example emulates a do..while cycle
- `op3.rhai` - change evaluation order with parenthesis * `op1.rhai` - just a simple addition
- `speed_test.rhai` - a simple program to measure the speed of Rhai's interpreter * `op2.rhai` - simple addition and multiplication
- `string.rhai`- string operations * `op3.rhai` - change evaluation order with parenthesis
- `while.rhai` - while loop * `speed_test.rhai` - a simple program to measure the speed of Rhai's interpreter
* `string.rhai`- string operations
* `while.rhai` - while loop
To run the scripts, you can either make your own tiny program, or make use of the `rhai_runner` To run the scripts, you can either make your own tiny program, or make use of the `rhai_runner`
example program: example program:
```bash ```bash
cargo run --example rhai_runner scripts/any_script.rhai cargo run --example rhai_runner scripts/any_script.rhai
``` ```
@ -89,18 +108,70 @@ 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:
```rust
// Compile to an AST and store it for later evaluations
let ast = Engine::compile("40 + 2").unwrap();
for _ in 0..42 {
if let Ok(result) = engine.eval_ast::<i64>(&ast) {
println!("Answer: {}", result); // prints 42
}
}
```
Compiling a script file into AST is also supported:
```rust
let ast = Engine::compile_file("hello_world.rhai").unwrap();
```
# Values and types
The following primitive types are supported natively:
* Integer: `i32`, `u32`, `i64` (default), `u64`
* Floating-point: `f32`, `f64` (default)
* Character: `char`
* Boolean: `bool`
* Array: `rhai::Array`
* Dynamic (i.e. can be anything): `rhai::Dynamic`
# Value conversions
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different; you cannot even add them together.
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.
```rust
let x = 42;
let y = x * 100.0; // error: cannot multiply i64 with f64
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());
```
# Working with functions # Working with functions
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; extern crate rhai;
use rhai::{Engine, RegisterFn}; use rhai::{Dynamic, Engine, RegisterFn};
// Normal function
fn add(x: i64, y: i64) -> i64 { fn add(x: i64, y: i64) -> i64 {
x + y x + y
} }
// Function that returns a Dynamic value
fn get_an_any() -> Dynamic {
Box::new(42_i64)
}
fn main() { fn main() {
let mut engine = Engine::new(); let mut engine = Engine::new();
@ -109,6 +180,25 @@ fn main() {
if let Ok(result) = engine.eval::<i64>("add(40, 2)") { if let Ok(result) = engine.eval::<i64>("add(40, 2)") {
println!("Answer: {}", result); // prints 42 println!("Answer: {}", result); // prints 42
} }
// Functions that return Dynamic values must use register_dynamic_fn()
engine.register_dynamic_fn("get_an_any", get_an_any);
if let Ok(result) = engine.eval::<i64>("get_an_any()") {
println!("Answer: {}", result); // prints 42
}
}
```
To return a `Dynamic` value, simply `Box` it and return it.
```rust
fn decide(yes_no: bool) -> Dynamic {
if yes_no {
Box::new(42_i64)
} else {
Box::new("hello world!".to_string()) // remember &str is not supported
}
} }
``` ```
@ -137,6 +227,19 @@ fn main() {
You can also see in this example how you can register multiple functions (or in this case multiple instances of the same function) to the same name in script. This gives you a way to overload functions and call the correct one, based on the types of the arguments, from your script. You can also see in this example how you can register multiple functions (or in this case multiple instances of the same function) to the same name in script. This gives you a way to overload functions and call the correct one, based on the types of the arguments, from your script.
# Override built-in functions
Any similarly-named function defined in a script overrides any built-in function.
```rust
// Override the built-in function 'to_int'
fn to_int(num) {
print("Ha! Gotcha!" + num);
}
print(to_int(123)); // what will happen?
```
# Custom types and methods # Custom types and methods
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:
@ -184,6 +287,7 @@ struct TestStruct {
``` ```
Next, we create a few methods that we'll later use in our scripts. Notice that we register our custom type with the engine. Next, we create a few methods that we'll later use in our scripts. Notice that we register our custom type with the engine.
```rust ```rust
impl TestStruct { impl TestStruct {
fn update(&mut self) { fn update(&mut self) {
@ -210,12 +314,28 @@ engine.register_fn("new_ts", TestStruct::new);
``` ```
Finally, we call our script. The script can see the function and method we registered earlier. We need to get the result back out from script land just as before, this time casting to our custom struct type. Finally, we call our script. The script can see the function and method we registered earlier. We need to get the result back out from script land just as before, this time casting to our custom struct type.
```rust ```rust
if let Ok(result) = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x") { if let Ok(result) = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x") {
println!("result: {}", result.x); // prints 1001 println!("result: {}", result.x); // prints 1001
} }
``` ```
In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method-call on that type because internally they are the same thing: methods on a type is implemented as a functions taking an first argument.
```rust
fn foo(ts: &mut TestStruct) -> i64 {
ts.x
}
engine.register_fn("foo", foo);
if let Ok(result) = engine.eval::<i64>("let x = new_ts(); x.foo()") {
println!("result: {}", result); // prints 1
}
```
# Getters and setters # Getters and setters
Similarly, you can work with members of your custom types. This works by registering a 'get' or a 'set' function for working with your struct. Similarly, you can work with members of your custom types. This works by registering a 'get' or a 'set' function for working with your struct.
@ -254,6 +374,23 @@ if let Ok(result) = engine.eval::<i64>("let a = new_ts(); a.x = 500; a.x") {
} }
``` ```
### WARNING: Gotcha's with Getters
When you _get_ a property, the value is cloned. Any update to it downstream will **NOT** be reflected back to the custom type.
This can introduce subtle bugs. For example:
```rust
fn change(s) {
s = 42;
}
let a = new_ts();
a.x = 500;
a.x.change(); // Only a COPY of 'a.x' is changed. 'a.x' is NOT changed.
a.x == 500;
```
# Maintaining state # 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.
@ -284,35 +421,64 @@ fn main() {
let x = 3; let x = 3;
``` ```
## Operators ## Numeric operators
```rust ```rust
let x = (1 + 2) * (6 - 4) / 2; let x = (1 + 2) * (6 - 4) / 2;
``` ```
## Comparison operators
You can compare most values of the same data type. If you compare two values of _different_ data types, the result is always `false`.
```rust
42 == 42; // true
42 > 42; // false
"hello" > "foo"; // true
"42" == 42; // false
42 == 42.0; // false - i64 is different from f64
```
## Boolean operators
Double boolean operators `&&` and `||` _short-circuit_, meaning that the second operand will not be evaluated if the first one already proves the condition wrong.
Single boolean operators `&` and `|` always evaluate both operands.
```rust
this() || that(); // that() is not evaluated if this() is true
this() && that(); // that() is not evaluated if this() is false
this() | that(); // both this() and that() are evaluated
this() & that(); // both this() and that() are evaluated
```
## If ## If
```rust ```rust
if true { if true {
print("it's true!"); print("It's true!");
} } else if true {
else { print("It's true again!");
} else {
print("It's false!"); print("It's false!");
} }
``` ```
## While ## While
```rust ```rust
let x = 10; let x = 10;
while x > 0 { while x > 0 {
print(x); print(x);
if x == 5 { if x == 5 { break; }
break;
}
x = x - 1; x = x - 1;
} }
``` ```
## Loop ## Loop
```rust ```rust
let x = 10; let x = 10;
@ -344,15 +510,83 @@ fn add(x, y) {
print(add(2, 3)) print(add(2, 3))
``` ```
## 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.
```rust The following standard functions operate on arrays:
let y = [1, 2, 3];
y[1] = 5;
print(y[1]); * `push` - inserts an element at the end
* `pop` - removes the last element and returns it (() if empty)
* `shift` - removes the first element and returns it (() if empty)
* `len` - returns the number of elements
* `pad` - pads the array with an element until a specified length
* `clear` - empties the array
* `truncate` - cuts off the array at exactly a specified length (discarding all subsequent elements)
```rust
let y = [1, 2, 3]; // 3 elements
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
y.push(4); // 4 elements
y.push(5); // 5 elements
print(y.len()); // prints 5
let first = y.shift(); // remove the first element, 4 elements remaining
first == 1;
let last = y.pop(); // remove the last element, 3 elements remaining
last == 5;
print(y.len()); // prints 3
y.pad(10, "hello"); // pad the array up to 10 elements
print(y.len()); // prints 10
y.truncate(5); // truncate the array to 5 elements
print(y.len()); // prints 5
y.clear(); // empty the array
print(y.len()); // prints 0
```
`push` and `pad` are only defined for standard built-in types. If you want to use them with
your own custom type, you need to define a specific override:
```rust
engine.register_fn("push",
|list: &mut Array, item: MyType| list.push(Box::new(item))
);
```
The type of a Rhai array is `rhai::Array`.
## For loops
```rust
let array = [1, 3, 5, 7, 9, 42];
for x in array {
print(x);
if x == 42 { break; }
}
// The range function allows iterating from first..last-1
for x in range(0,50) {
print(x);
if x == 42 { break; }
}
``` ```
## Members and methods ## Members and methods
@ -368,6 +602,80 @@ a.update();
```rust ```rust
let name = "Bob"; let name = "Bob";
let middle_initial = 'C'; let middle_initial = 'C';
let last = 'Davis';
let full_name = name + " " + middle_initial + ". " + last;
full_name == "Bob C. Davis";
// String building with different types
let age = 42;
let record = full_name + ": age " + age;
record == "Bob C. Davis: age 42";
// Strings can be indexed to get a character
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
// Unlike Rust, Rhai strings can be modified
record[4] = 'Z';
record == "Bob Z. Davis: age 42";
```
The following standard functions 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
* `clear` - empties the string
* `truncate` - cuts off the string at exactly a specified number of characters
* `contains` - checks if a certain character or sub-string occurs in the string
* `replace` - replaces a substring with another
* `trim` - trims the string
```rust
let full_name == " Bob C. Davis ";
full_name.len() == 14;
full_name.trim();
full_name.len() == 12;
full_name.pad(15, '$');
full_name.len() == 15;
full_name == "Bob C. Davis$$$";
full_name.truncate(6);
full_name.len() == 6;
full_name == "Bob C.";
full_name.replace("Bob", "John");
full_name.len() == 7;
full_name = "John C.";
full_name.contains('C') == true;
full_name.contains("John") == true;
full_name.clear();
full_name.len() == 0;
```
## Print and Debug
```rust
print("hello"); // prints hello to stdout
print(1 + 2 + 3); // prints 6 to stdout
print("hello" + 42); // prints hello42 to stdout
debug("world!"); // prints "world!" to stdout using debug formatting
```
### Overriding Print and Debug with Callback functions
```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));
``` ```
## Comments ## Comments
@ -412,6 +720,7 @@ The `+=` operator can also be used to build strings:
```rust ```rust
let my_str = "abc"; let my_str = "abc";
my_str += "ABC"; my_str += "ABC";
my_str += 12345;
my_str == "abcABC" my_str == "abcABC12345"
``` ```

4
TODO
View File

@ -1,11 +1,7 @@
pre 1.0: pre 1.0:
- binary ops
- basic threads - basic threads
- stdlib - stdlib
- floats
- REPL (consume functions)
1.0: 1.0:
- decide on postfix/prefix operators - decide on postfix/prefix operators
- ranges, rustic for-loop
- advanced threads + actors - advanced threads + actors
- more literals - more literals

View File

@ -1,34 +1,19 @@
use rhai::{Engine, RegisterFn, Scope}; use rhai::{Engine, RegisterFn, Scope};
use std::fmt::Display;
use std::io::{stdin, stdout, Write}; use std::io::{stdin, stdout, Write};
use std::process::exit; use std::process::exit;
fn showit<T: Display>(x: &mut T) -> () {
println!("{}", x)
}
fn quit() {
exit(0);
}
pub fn main() { pub fn main() {
let mut engine = Engine::new(); let mut engine = Engine::new();
let mut scope = Scope::new(); let mut scope = Scope::new();
engine.register_fn("print", showit as fn(x: &mut i32) -> ()); engine.register_fn("exit", || exit(0));
engine.register_fn("print", showit as fn(x: &mut i64) -> ());
engine.register_fn("print", showit as fn(x: &mut u32) -> ());
engine.register_fn("print", showit as fn(x: &mut u64) -> ());
engine.register_fn("print", showit as fn(x: &mut f32) -> ());
engine.register_fn("print", showit as fn(x: &mut f64) -> ());
engine.register_fn("print", showit as fn(x: &mut bool) -> ());
engine.register_fn("print", showit as fn(x: &mut String) -> ());
engine.register_fn("exit", quit);
loop { loop {
print!("> "); print!("> ");
let mut input = String::new(); let mut input = String::new();
stdout().flush().expect("couldn't flush stdout"); stdout().flush().expect("couldn't flush stdout");
if let Err(e) = stdin().read_line(&mut input) { if let Err(e) = stdin().read_line(&mut input) {
println!("input error: {}", e); println!("input error: {}", e);
} }

View File

@ -1,13 +1,14 @@
use rhai::{Engine, RegisterFn}; use rhai::{Engine, RegisterFn};
fn add(x: i64, y: i64) -> i64 {
x + y
}
fn main() { fn main() {
let mut engine = Engine::new(); let mut engine = Engine::new();
fn add(x: i64, y: i64) -> i64 {
x + y
}
engine.register_fn("add", add); engine.register_fn("add", add);
if let Ok(result) = engine.eval::<i64>("add(40, 2)") { if let Ok(result) = engine.eval::<i64>("add(40, 2)") {
println!("Answer: {}", result); // prints 42 println!("Answer: {}", result); // prints 42
} }

View File

@ -1,12 +1,15 @@
use std::any::{type_name, Any as StdAny, TypeId}; use std::any::{type_name, Any as StdAny, TypeId};
use std::fmt; use std::fmt;
pub type Variant = dyn Any;
pub type Dynamic = Box<Variant>;
pub trait Any: StdAny { pub trait Any: StdAny {
fn type_id(&self) -> TypeId; fn type_id(&self) -> TypeId;
fn type_name(&self) -> String; fn type_name(&self) -> String;
fn box_clone(&self) -> Box<dyn Any>; fn into_dynamic(&self) -> Dynamic;
/// This type may only be implemented by `rhai`. /// This type may only be implemented by `rhai`.
#[doc(hidden)] #[doc(hidden)]
@ -27,7 +30,7 @@ where
} }
#[inline] #[inline]
fn box_clone(&self) -> Box<dyn Any> { fn into_dynamic(&self) -> Dynamic {
Box::new(self.clone()) Box::new(self.clone())
} }
@ -36,15 +39,15 @@ where
} }
} }
impl dyn Any { impl Variant {
//#[inline] //#[inline]
// fn box_clone(&self) -> Box<dyn Any> { // fn into_dynamic(&self) -> Box<Variant> {
// Any::box_clone(self) // Any::into_dynamic(self)
// } // }
#[inline] #[inline]
pub fn is<T: Any>(&self) -> bool { pub fn is<T: Any>(&self) -> bool {
let t = TypeId::of::<T>(); let t = TypeId::of::<T>();
let boxed = <dyn Any as Any>::type_id(self); let boxed = <Variant as Any>::type_id(self);
t == boxed t == boxed
} }
@ -52,7 +55,7 @@ impl dyn Any {
#[inline] #[inline]
pub fn downcast_ref<T: Any>(&self) -> Option<&T> { pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
if self.is::<T>() { if self.is::<T>() {
unsafe { Some(&*(self as *const dyn Any as *const T)) } unsafe { Some(&*(self as *const Variant as *const T)) }
} else { } else {
None None
} }
@ -61,20 +64,20 @@ impl dyn Any {
#[inline] #[inline]
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> { pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
if self.is::<T>() { if self.is::<T>() {
unsafe { Some(&mut *(self as *mut dyn Any as *mut T)) } unsafe { Some(&mut *(self as *mut Variant as *mut T)) }
} else { } else {
None None
} }
} }
} }
impl Clone for Box<dyn Any> { impl Clone for Dynamic {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Any::box_clone(self.as_ref() as &dyn Any) Any::into_dynamic(self.as_ref())
} }
} }
impl fmt::Debug for dyn Any { 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("?")
} }
@ -84,11 +87,11 @@ pub trait AnyExt: Sized {
fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self>; fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self>;
} }
impl AnyExt for Box<dyn Any> { impl AnyExt for Dynamic {
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 {
let raw: *mut dyn Any = Box::into_raw(self); let raw: *mut Variant = Box::into_raw(self);
Ok(Box::from_raw(raw as *mut T)) Ok(Box::from_raw(raw as *mut T))
} }
} else { } else {

176
src/api.rs Normal file
View File

@ -0,0 +1,176 @@
use crate::any::{Any, AnyExt, Dynamic};
use crate::engine::{Engine, EvalAltResult, FnIntExt, FnSpec, Scope};
use crate::parser::{lex, parse, ParseError, Position, AST};
use std::sync::Arc;
impl Engine {
/// Compile a string into an AST
pub fn compile(input: &str) -> Result<AST, ParseError> {
let tokens = lex(input);
parse(&mut tokens.peekable())
}
/// Compile a file into an AST
pub fn compile_file(filename: &str) -> Result<AST, EvalAltResult> {
use std::fs::File;
use std::io::prelude::*;
let mut f = File::open(filename)
.map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?;
let mut contents = String::new();
f.read_to_string(&mut contents)
.map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))
.and_then(|_| Self::compile(&contents).map_err(EvalAltResult::ErrorParsing))
}
/// Evaluate a file
pub fn eval_file<T: Any + Clone>(&mut self, filename: &str) -> Result<T, EvalAltResult> {
use std::fs::File;
use std::io::prelude::*;
let mut f = File::open(filename)
.map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?;
let mut contents = String::new();
f.read_to_string(&mut contents)
.map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))
.and_then(|_| self.eval::<T>(&contents))
}
/// Evaluate a string
pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
let mut scope = Scope::new();
self.eval_with_scope(&mut scope, input)
}
/// Evaluate a string with own scope
pub fn eval_with_scope<T: Any + Clone>(
&mut self,
scope: &mut Scope,
input: &str,
) -> Result<T, EvalAltResult> {
let ast = Self::compile(input).map_err(EvalAltResult::ErrorParsing)?;
self.eval_ast_with_scope(scope, &ast)
}
/// Evaluate an AST
pub fn eval_ast<T: Any + Clone>(&mut self, ast: &AST) -> Result<T, EvalAltResult> {
let mut scope = Scope::new();
self.eval_ast_with_scope(&mut scope, ast)
}
/// Evaluate an AST with own scope
pub fn eval_ast_with_scope<T: Any + Clone>(
&mut self,
scope: &mut Scope,
ast: &AST,
) -> Result<T, EvalAltResult> {
let AST(os, fns) = ast;
fns.iter().for_each(|f| {
self.script_fns.insert(
FnSpec {
ident: f.name.clone(),
args: None,
},
Arc::new(FnIntExt::Int(f.clone())),
);
});
let result = os
.iter()
.try_fold(Box::new(()) as Dynamic, |_, o| self.eval_stmt(scope, o));
self.script_fns.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)
})?),
Ok(out) => Ok(*out.downcast::<T>().map_err(|a| {
let name = self.map_type_name((*a).type_name());
EvalAltResult::ErrorMismatchOutputType(name, Position::eof())
})?),
Err(err) => Err(err),
}
}
/// Evaluate a file, but only return errors, if there are 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> {
use std::fs::File;
use std::io::prelude::*;
let mut f = File::open(filename)
.map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?;
let mut contents = String::new();
f.read_to_string(&mut contents)
.map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))
.and_then(|_| self.consume(&contents))
}
/// Evaluate a string, but only return errors, if there are 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.
/// Useful for when you don't need the result, but still need
/// to keep track of possible errors
pub fn consume_with_scope(
&mut self,
scope: &mut Scope,
input: &str,
) -> Result<(), EvalAltResult> {
let tokens = lex(input);
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(
FnSpec {
ident: f.name.clone(),
args: None,
},
Arc::new(FnIntExt::Int(f.clone())),
);
}
let val = os
.iter()
.try_fold(Box::new(()) as Dynamic, |_, o| self.eval_stmt(scope, o))
.map(|_| ());
self.script_fns.clear(); // Clean up engine
val
})
}
/// Overrides `on_print`
pub fn on_print(&mut self, callback: impl Fn(&str) + 'static) {
self.on_print = Box::new(callback);
}
/// Overrides `on_debug`
pub fn on_debug(&mut self, callback: impl Fn(&str) + 'static) {
self.on_debug = Box::new(callback);
}
}

336
src/builtin.rs Normal file
View File

@ -0,0 +1,336 @@
use crate::{any::Any, Array, Dynamic, Engine, RegisterDynamicFn, RegisterFn};
use std::fmt::{Debug, Display};
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub};
macro_rules! reg_op {
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
$(
$self.register_fn($x, $op as fn(x: $y, y: $y)->$y);
)*
)
}
macro_rules! reg_un {
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
$(
$self.register_fn($x, $op as fn(x: $y)->$y);
)*
)
}
macro_rules! reg_cmp {
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
$(
$self.register_fn($x, $op as fn(x: $y, y: $y)->bool);
)*
)
}
macro_rules! reg_func1 {
($self:expr, $x:expr, $op:expr, $r:ty, $( $y:ty ),*) => (
$(
$self.register_fn($x, $op as fn(x: $y)->$r);
)*
)
}
macro_rules! reg_func2x {
($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => (
$(
$self.register_fn($x, $op as fn(x: $v, y: $y)->$r);
)*
)
}
macro_rules! reg_func2y {
($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => (
$(
$self.register_fn($x, $op as fn(y: $y, x: $v)->$r);
)*
)
}
macro_rules! reg_func3 {
($self:expr, $x:expr, $op:expr, $v:ty, $w:ty, $r:ty, $( $y:ty ),*) => (
$(
$self.register_fn($x, $op as fn(x: $v, y: $w, z: $y)->$r);
)*
)
}
impl Engine {
/// Register the built-in library.
pub(crate) fn register_builtins(&mut self) {
fn add<T: Add>(x: T, y: T) -> <T as Add>::Output {
x + y
}
fn sub<T: Sub>(x: T, y: T) -> <T as Sub>::Output {
x - y
}
fn mul<T: Mul>(x: T, y: T) -> <T as Mul>::Output {
x * y
}
fn div<T: Div>(x: T, y: T) -> <T as Div>::Output {
x / y
}
fn neg<T: Neg>(x: T) -> <T as Neg>::Output {
-x
}
fn lt<T: PartialOrd>(x: T, y: T) -> bool {
x < y
}
fn lte<T: PartialOrd>(x: T, y: T) -> bool {
x <= y
}
fn gt<T: PartialOrd>(x: T, y: T) -> bool {
x > y
}
fn gte<T: PartialOrd>(x: T, y: T) -> bool {
x >= y
}
fn eq<T: PartialEq>(x: T, y: T) -> bool {
x == y
}
fn ne<T: PartialEq>(x: T, y: T) -> bool {
x != y
}
fn and(x: bool, y: bool) -> bool {
x && y
}
fn or(x: bool, y: bool) -> bool {
x || y
}
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
}
fn binary_or<T: BitOr>(x: T, y: T) -> <T as BitOr>::Output {
x | y
}
fn binary_xor<T: BitXor>(x: T, y: T) -> <T as BitXor>::Output {
x ^ y
}
fn left_shift<T: Shl<T>>(x: T, y: T) -> <T as Shl<T>>::Output {
x.shl(y)
}
fn right_shift<T: Shr<T>>(x: T, y: T) -> <T as Shr<T>>::Output {
x.shr(y)
}
fn modulo<T: Rem<T>>(x: T, y: T) -> <T as Rem<T>>::Output {
x % y
}
fn pow_i64_i64(x: i64, y: i64) -> i64 {
x.pow(y as u32)
}
fn pow_f64_f64(x: f64, y: f64) -> f64 {
x.powf(y)
}
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);
reg_op!(self, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64);
reg_op!(self, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64);
reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, String, char);
reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, String, char);
reg_cmp!(self, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, String, char);
reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, String, char);
reg_cmp!(
self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, bool, f32, f64, String, char
);
reg_cmp!(
self, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64, bool, f32, f64, String, char
);
//reg_op!(self, "||", or, bool);
//reg_op!(self, "&&", and, bool);
reg_op!(self, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op!(self, "|", or, bool);
reg_op!(self, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op!(self, "&", and, bool);
reg_op!(self, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op!(self, "<<", left_shift, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op!(self, ">>", right_shift, i8, u8, i16, u16);
reg_op!(self, ">>", right_shift, i32, i64, u32, u64);
reg_op!(self, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64);
self.register_fn("~", pow_i64_i64);
self.register_fn("~", pow_f64_f64);
self.register_fn("~", pow_f64_i64);
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("[]", 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 conversion functions
self.register_fn("to_float", |x: i8| x as f64);
self.register_fn("to_float", |x: u8| x as f64);
self.register_fn("to_float", |x: i16| x as f64);
self.register_fn("to_float", |x: u16| x as f64);
self.register_fn("to_float", |x: i32| x as f64);
self.register_fn("to_float", |x: u32| x as f64);
self.register_fn("to_float", |x: i64| x as f64);
self.register_fn("to_float", |x: u64| x as f64);
self.register_fn("to_float", |x: f32| x as f64);
self.register_fn("to_int", |x: i8| x as i64);
self.register_fn("to_int", |x: u8| x as i64);
self.register_fn("to_int", |x: i16| x as i64);
self.register_fn("to_int", |x: u16| x as i64);
self.register_fn("to_int", |x: i32| x as i64);
self.register_fn("to_int", |x: u32| x as i64);
self.register_fn("to_int", |x: u64| x as i64);
self.register_fn("to_int", |x: f32| x as i64);
self.register_fn("to_int", |x: f64| x 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
fn push<T: Any>(list: &mut Array, item: T) {
list.push(Box::new(item));
}
fn pad<T: Any + Clone>(list: &mut Array, len: i64, item: T) {
if len >= 0 {
while list.len() < len as usize {
push(list, item.clone());
}
}
}
reg_func2x!(self, "push", push, &mut Array, (), i8, u8, i16, u16);
reg_func2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64);
reg_func2x!(self, "push", push, &mut Array, (), f32, f64, bool, char);
reg_func2x!(self, "push", push, &mut Array, (), String, Array, ());
reg_func3!(self, "pad", pad, &mut Array, i64, (), i8, u8, i16, u16);
reg_func3!(self, "pad", pad, &mut Array, i64, (), i32, u32, f32);
reg_func3!(self, "pad", pad, &mut Array, i64, (), i64, u64, f64);
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_fn("len", |list: &mut Array| list.len() as i64);
self.register_fn("clear", |list: &mut Array| list.clear());
self.register_fn("truncate", |list: &mut Array, len: i64| {
if len >= 0 {
list.truncate(len as usize);
}
});
// Register string concatenate functions
fn prepend<T: Display>(x: T, y: String) -> String {
format!("{}{}", x, y)
}
fn append<T: Display>(x: String, y: T) -> String {
format!("{}{}", x, y)
}
reg_func2x!(
self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64,
bool, char
);
self.register_fn("+", |x: String, y: Array| format!("{}{:?}", x, y));
self.register_fn("+", |x: String, _: ()| format!("{}", x));
reg_func2y!(
self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64,
bool, char
);
self.register_fn("+", |x: Array, y: String| format!("{:?}{}", x, y));
self.register_fn("+", |_: (), y: String| format!("{}", y));
// Register string utility functions
self.register_fn("len", |s: &mut String| s.chars().count() as i64);
self.register_fn("contains", |s: &mut String, ch: char| s.contains(ch));
self.register_fn("contains", |s: &mut String, find: String| s.contains(&find));
self.register_fn("clear", |s: &mut String| s.clear());
self.register_fn("truncate", |s: &mut String, len: i64| {
if len >= 0 {
let chars: Vec<_> = s.chars().take(len as usize).collect();
s.clear();
chars.iter().for_each(|&ch| s.push(ch));
} else {
s.clear();
}
});
self.register_fn("pad", |s: &mut String, len: i64, ch: char| {
for _ in 0..s.chars().count() - len as usize {
s.push(ch);
}
});
self.register_fn("replace", |s: &mut String, find: String, sub: String| {
let new_str = s.replace(&find, &sub);
s.clear();
s.push_str(&new_str);
});
self.register_fn("trim", |s: &mut String| {
let trimmed = s.trim();
if trimmed.len() < s.len() {
let chars: Vec<_> = trimmed.chars().collect();
s.clear();
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

@ -1,24 +1,22 @@
//! Helper module which defines `FnArgs` //! Helper module which defines `FnArgs`
//! to make function calling easier. //! to make function calling easier.
use crate::any::Any; use crate::any::{Any, Variant};
pub trait FunArgs<'a> { pub trait FunArgs<'a> {
fn into_vec(self) -> Vec<&'a mut dyn Any>; fn into_vec(self) -> Vec<&'a mut Variant>;
} }
macro_rules! impl_args { macro_rules! impl_args {
($($p:ident),*) => { ($($p:ident),*) => {
impl<'a, $($p),*> FunArgs<'a> for ($(&'a mut $p,)*) impl<'a, $($p: Any + Clone),*> FunArgs<'a> for ($(&'a mut $p,)*)
where
$($p: Any + Clone),*
{ {
fn into_vec(self) -> Vec<&'a mut dyn Any> { fn into_vec(self) -> Vec<&'a mut Variant> {
let ($($p,)*) = self; let ($($p,)*) = self;
#[allow(unused_variables, unused_mut)] #[allow(unused_variables, unused_mut)]
let mut v = Vec::new(); let mut v = Vec::new();
$(v.push($p as &mut dyn Any);)* $(v.push($p as &mut Variant);)*
v v
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,14 @@
use std::any::TypeId; use std::any::TypeId;
use crate::any::Any; use crate::any::{Any, Dynamic};
use crate::engine::{Engine, EvalAltResult}; use crate::engine::{Engine, EvalAltResult, FnCallArgs};
use crate::parser::Position;
pub trait RegisterFn<FN, ARGS, RET> { pub trait RegisterFn<FN, ARGS, RET> {
fn register_fn(&mut self, name: &str, f: FN); fn register_fn(&mut self, name: &str, f: FN);
} }
pub trait RegisterBoxFn<FN, ARGS> { pub trait RegisterDynamicFn<FN, ARGS> {
fn register_box_fn(&mut self, name: &str, f: FN); fn register_dynamic_fn(&mut self, name: &str, f: FN);
} }
pub struct Ref<A>(A); pub struct Ref<A>(A);
@ -23,63 +24,69 @@ macro_rules! def_register {
def_register!(imp); def_register!(imp);
}; };
(imp $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => { (imp $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => {
impl<$($par,)* FN, RET> RegisterFn<FN, ($($mark,)*), RET> for Engine impl<
where
$($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
{ {
fn register_fn(&mut self, name: &str, f: FN) { fn register_fn(&mut self, name: &str, f: FN) {
let fun = move |mut args: Vec<&mut dyn Any>| { let fn_name = name.to_string();
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.
if args.len() != count_args!($($par)*) { const NUM_ARGS: usize = count_args!($($par)*);
return Err(EvalAltResult::ErrorFunctionArgMismatch);
if args.len() != NUM_ARGS {
return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, pos));
} }
#[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>) let $par = ((*drain.next().unwrap()).downcast_mut() as Option<&mut $par>).unwrap();
.ok_or(EvalAltResult::ErrorFunctionArgMismatch)?;
)* )*
// 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 Box<dyn Any>) Ok(Box::new(r) as Dynamic)
}; };
self.register_fn_raw(name.to_owned(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); self.register_fn_raw(name.into(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
} }
} }
impl<$($par,)* FN> RegisterBoxFn<FN, ($($mark,)*)> for Engine impl<
where
$($par: Any + Clone,)* $($par: Any + Clone,)*
FN: Fn($($param),*) -> Box<dyn Any> + 'static FN: Fn($($param),*) -> Dynamic + 'static,
> RegisterDynamicFn<FN, ($($mark,)*)> for Engine
{ {
fn register_box_fn(&mut self, name: &str, f: FN) { fn register_dynamic_fn(&mut self, name: &str, f: FN) {
let fun = move |mut args: Vec<&mut dyn Any>| { let fn_name = name.to_string();
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.
if args.len() != count_args!($($par)*) { const NUM_ARGS: usize = count_args!($($par)*);
return Err(EvalAltResult::ErrorFunctionArgMismatch);
if args.len() != NUM_ARGS {
return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, pos));
} }
#[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>) let $par = ((*drain.next().unwrap()).downcast_mut() as Option<&mut $par>).unwrap();
.ok_or(EvalAltResult::ErrorFunctionArgMismatch)?;
)* )*
// 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.to_owned(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); self.register_fn_raw(name.into(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
} }
} }

View File

@ -25,7 +25,7 @@
//! //!
//! let mut engine = Engine::new(); //! let mut engine = Engine::new();
//! engine.register_fn("compute_something", compute_something); //! engine.register_fn("compute_something", compute_something);
//! assert_eq!(engine.eval_file::<bool>("my_script.rhai"), Ok(true)); //! assert_eq!(engine.eval_file::<bool>("my_script.rhai").unwrap(), true);
//! ``` //! ```
//! //!
//! [Check out the README on GitHub for more information!](https://github.com/jonathandturner/rhai) //! [Check out the README on GitHub for more information!](https://github.com/jonathandturner/rhai)
@ -40,12 +40,14 @@ macro_rules! debug_println {
} }
mod any; mod any;
mod api;
mod builtin;
mod call; mod call;
mod engine; mod engine;
mod fn_register; mod fn_register;
mod parser; mod parser;
pub use any::Any; pub use any::Dynamic;
pub use engine::{Engine, EvalAltResult, Scope}; pub use engine::{Array, Engine, EvalAltResult, Scope};
pub use fn_register::{RegisterBoxFn, RegisterFn}; pub use fn_register::{RegisterDynamicFn, RegisterFn};
pub use parser::{ParseError, ParseErrorType, AST}; pub use parser::{ParseError, ParseErrorType, AST};

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +1,17 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult, RegisterFn};
use rhai::RegisterFn;
#[test] #[test]
fn test_arrays() { fn test_arrays() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("let x = [1, 2, 3]; x[1]"), Ok(2)); assert_eq!(engine.eval::<i64>("let x = [1, 2, 3]; x[1]")?, 2);
assert_eq!( assert_eq!(engine.eval::<i64>("let y = [1, 2, 3]; y[1] = 5; y[1]")?, 5);
engine.eval::<i64>("let y = [1, 2, 3]; y[1] = 5; y[1]"),
Ok(5) Ok(())
);
} }
#[test] #[test]
fn test_array_with_structs() { fn test_array_with_structs() -> Result<(), EvalAltResult> {
#[derive(Clone)] #[derive(Clone)]
struct TestStruct { struct TestStruct {
x: i64, x: i64,
@ -45,7 +43,7 @@ fn test_array_with_structs() {
engine.register_fn("update", TestStruct::update); engine.register_fn("update", TestStruct::update);
engine.register_fn("new_ts", TestStruct::new); engine.register_fn("new_ts", TestStruct::new);
assert_eq!(engine.eval::<i64>("let a = [new_ts()]; a[0].x"), Ok(1)); assert_eq!(engine.eval::<i64>("let a = [new_ts()]; a[0].x")?, 1);
assert_eq!( assert_eq!(
engine.eval::<i64>( engine.eval::<i64>(
@ -53,7 +51,9 @@ fn test_array_with_structs() {
a[0].x = 100; \ a[0].x = 100; \
a[0].update(); \ a[0].update(); \
a[0].x", a[0].x",
), )?,
Ok(1100) 1100
); );
Ok(())
} }

View File

@ -1,13 +1,22 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_binary_ops() { fn test_binary_ops() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("10 % 4"), Ok(2)); assert_eq!(engine.eval::<i64>("10 % 4")?, 2);
assert_eq!(engine.eval::<i64>("10 << 4"), Ok(160)); assert_eq!(engine.eval::<i64>("10 << 4")?, 160);
assert_eq!(engine.eval::<i64>("10 >> 4"), Ok(0)); assert_eq!(engine.eval::<i64>("10 >> 4")?, 0);
assert_eq!(engine.eval::<i64>("10 & 4"), Ok(0)); assert_eq!(engine.eval::<i64>("10 & 4")?, 0);
assert_eq!(engine.eval::<i64>("10 | 4"), Ok(14)); assert_eq!(engine.eval::<i64>("10 | 4")?, 14);
assert_eq!(engine.eval::<i64>("10 ^ 4"), Ok(14)); assert_eq!(engine.eval::<i64>("10 ^ 4")?, 14);
assert_eq!(engine.eval::<bool>("42 == 42")?, true);
assert_eq!(engine.eval::<bool>("42 > 42")?, false);
// Incompatible types compare to false
assert_eq!(engine.eval::<bool>("true == 42")?, false);
assert_eq!(engine.eval::<bool>(r#""42" == 42"#)?, false);
Ok(())
} }

View File

@ -1,15 +1,15 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_left_shift() { fn test_left_shift() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("4 << 2")?, 16);
assert_eq!(engine.eval::<i64>("4 << 2"), Ok(16)); Ok(())
} }
#[test] #[test]
fn test_right_shift() { fn test_right_shift() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("9 >> 1")?, 4);
assert_eq!(engine.eval::<i64>("9 >> 1"), Ok(4)); Ok(())
} }

View File

@ -1,15 +1,104 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_bool_op1() { fn test_bool_op1() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<bool>("true && (false || true)"), Ok(true)); assert_eq!(engine.eval::<bool>("true && (false || true)")?, true);
assert_eq!(engine.eval::<bool>("true & (false | true)")?, true);
Ok(())
} }
#[test] #[test]
fn test_bool_op2() { fn test_bool_op2() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<bool>("false && (false || true)"), Ok(false)); assert_eq!(engine.eval::<bool>("false && (false || true)")?, false);
assert_eq!(engine.eval::<bool>("false & (false | true)")?, false);
Ok(())
}
#[test]
fn test_bool_op3() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
assert!(engine.eval::<bool>("true && (false || 123)").is_err());
assert_eq!(engine.eval::<bool>("true && (true || 123)")?, true);
assert!(engine.eval::<bool>("123 && (false || true)").is_err());
assert_eq!(engine.eval::<bool>("false && (true || 123)")?, false);
Ok(())
}
#[test]
fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
assert_eq!(
engine.eval::<bool>(
r"
fn this() { true }
fn that() { 9/0 }
this() || that();
"
)?,
true
);
assert_eq!(
engine.eval::<bool>(
r"
fn this() { false }
fn that() { 9/0 }
this() && that();
"
)?,
false
);
Ok(())
}
#[test]
#[should_panic]
fn test_bool_op_no_short_circuit1() {
let mut engine = Engine::new();
assert_eq!(
engine
.eval::<bool>(
r"
fn this() { false }
fn that() { 9/0 }
this() | that();
"
)
.unwrap(),
false
);
}
#[test]
#[should_panic]
fn test_bool_op_no_short_circuit2() {
let mut engine = Engine::new();
assert_eq!(
engine
.eval::<bool>(
r"
fn this() { false }
fn that() { 9/0 }
this() & that();
"
)
.unwrap(),
false
);
} }

View File

@ -1,14 +1,19 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_chars() { fn test_chars() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<char>("'y'"), Ok('y')); assert_eq!(engine.eval::<char>("'y'")?, 'y');
assert_eq!(engine.eval::<char>("'\\u2764'"), Ok('❤')); assert_eq!(engine.eval::<char>("'\\u2764'")?, '❤');
assert_eq!(engine.eval::<char>(r#"let x="hello"; x[2]"#)?, 'l');
assert_eq!(
engine.eval::<String>(r#"let x="hello"; x[2]='$'; x"#)?,
"he$lo".to_string()
);
match engine.eval::<char>("''") { assert!(engine.eval::<char>("'\\uhello'").is_err());
Err(_) => (), assert!(engine.eval::<char>("''").is_err());
_ => assert!(false),
} Ok(())
} }

View File

@ -1,68 +1,66 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_or_equals() { fn test_or_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("let x = 16; x |= 74; x"), Ok(90)); assert_eq!(engine.eval::<i64>("let x = 16; x |= 74; x")?, 90);
assert_eq!(engine.eval::<bool>("let x = true; x |= false; x"), Ok(true)); assert_eq!(engine.eval::<bool>("let x = true; x |= false; x")?, true);
assert_eq!(engine.eval::<bool>("let x = false; x |= true; x"), Ok(true)); assert_eq!(engine.eval::<bool>("let x = false; x |= true; x")?, true);
Ok(())
} }
#[test] #[test]
fn test_and_equals() { fn test_and_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("let x = 16; x &= 31; x"), Ok(16)); assert_eq!(engine.eval::<i64>("let x = 16; x &= 31; x")?, 16);
assert_eq!( assert_eq!(engine.eval::<bool>("let x = true; x &= false; x")?, false);
engine.eval::<bool>("let x = true; x &= false; x"), assert_eq!(engine.eval::<bool>("let x = false; x &= true; x")?, false);
Ok(false) assert_eq!(engine.eval::<bool>("let x = true; x &= true; x")?, true);
);
assert_eq!( Ok(())
engine.eval::<bool>("let x = false; x &= true; x"),
Ok(false)
);
assert_eq!(engine.eval::<bool>("let x = true; x &= true; x"), Ok(true));
} }
#[test] #[test]
fn test_xor_equals() { fn test_xor_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("let x = 90; x ^= 12; x")?, 86);
assert_eq!(engine.eval::<i64>("let x = 90; x ^= 12; x"), Ok(86)); Ok(())
} }
#[test] #[test]
fn test_multiply_equals() { fn test_multiply_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("let x = 2; x *= 3; x")?, 6);
assert_eq!(engine.eval::<i64>("let x = 2; x *= 3; x"), Ok(6)); Ok(())
} }
#[test] #[test]
fn test_divide_equals() { fn test_divide_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("let x = 6; x /= 2; x")?, 3);
assert_eq!(engine.eval::<i64>("let x = 6; x /= 2; x"), Ok(3)); Ok(())
} }
#[test] #[test]
fn test_left_shift_equals() { fn test_left_shift_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("let x = 9; x >>=1; x")?, 4);
assert_eq!(engine.eval::<i64>("let x = 9; x >>=1; x"), Ok(4)); Ok(())
} }
#[test] #[test]
fn test_right_shift_equals() { fn test_right_shift_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("let x = 4; x<<= 2; x")?, 16);
assert_eq!(engine.eval::<i64>("let x = 4; x<<= 2; x"), Ok(16)); Ok(())
} }
#[test] #[test]
fn test_modulo_equals() { fn test_modulo_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("let x = 10; x %= 4; x")?, 2);
assert_eq!(engine.eval::<i64>("let x = 10; x %= 4; x"), Ok(2)); Ok(())
} }

View File

@ -1,16 +1,17 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
use rhai::EvalAltResult;
#[test] #[test]
fn test_decrement() { fn test_decrement() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("let x = 10; x -= 7; x"), Ok(3)); assert_eq!(engine.eval::<i64>("let x = 10; x -= 7; x")?, 3);
assert_eq!( let r = engine.eval::<String>("let s = \"test\"; s -= \"ing\"; s");
engine.eval::<String>("let s = \"test\"; s -= \"ing\"; s"),
Err(EvalAltResult::ErrorFunctionNotFound( match r {
"- (alloc::string::String, alloc::string::String)".to_string() Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "- (string, string)" => (),
)) _ => panic!(),
); }
Ok(())
} }

View File

@ -1,23 +1,24 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult, RegisterFn};
use rhai::RegisterFn;
#[test] #[test]
fn test_float() { fn test_float() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<bool>("let x = 0.0; let y = 1.0; x < y"), engine.eval::<bool>("let x = 0.0; let y = 1.0; x < y")?,
Ok(true) true
); );
assert_eq!( assert_eq!(
engine.eval::<bool>("let x = 0.0; let y = 1.0; x > y"), engine.eval::<bool>("let x = 0.0; let y = 1.0; x > y")?,
Ok(false) false
); );
assert_eq!(engine.eval::<f64>("let x = 9.9999; x"), Ok(9.9999)); assert_eq!(engine.eval::<f64>("let x = 9.9999; x")?, 9.9999);
Ok(())
} }
#[test] #[test]
fn struct_with_float() { fn struct_with_float() -> Result<(), EvalAltResult> {
#[derive(Clone)] #[derive(Clone)]
struct TestStruct { struct TestStruct {
x: f64, x: f64,
@ -50,11 +51,13 @@ fn struct_with_float() {
engine.register_fn("new_ts", TestStruct::new); engine.register_fn("new_ts", TestStruct::new);
assert_eq!( assert_eq!(
engine.eval::<f64>("let ts = new_ts(); ts.update(); ts.x"), engine.eval::<f64>("let ts = new_ts(); ts.update(); ts.x")?,
Ok(6.789) 6.789
); );
assert_eq!( assert_eq!(
engine.eval::<f64>("let ts = new_ts(); ts.x = 10.1001; ts.x"), engine.eval::<f64>("let ts = new_ts(); ts.x = 10.1001; ts.x")?,
Ok(10.1001) 10.1001
); );
Ok(())
} }

View File

@ -1,7 +1,7 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_for() { fn test_for() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let script = r" let script = r"
@ -20,5 +20,7 @@ fn test_for() {
sum1 + sum2 sum1 + sum2
"; ";
assert_eq!(engine.eval::<i64>(script).unwrap(), 30); assert_eq!(engine.eval::<i64>(script)?, 30);
Ok(())
} }

View File

@ -1,8 +1,7 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult, RegisterFn};
use rhai::RegisterFn;
#[test] #[test]
fn test_get_set() { fn test_get_set() -> Result<(), EvalAltResult> {
#[derive(Clone)] #[derive(Clone)]
struct TestStruct { struct TestStruct {
x: i64, x: i64,
@ -29,14 +28,13 @@ fn test_get_set() {
engine.register_get_set("x", TestStruct::get_x, TestStruct::set_x); engine.register_get_set("x", TestStruct::get_x, TestStruct::set_x);
engine.register_fn("new_ts", TestStruct::new); engine.register_fn("new_ts", TestStruct::new);
assert_eq!( assert_eq!(engine.eval::<i64>("let a = new_ts(); a.x = 500; a.x")?, 500);
engine.eval::<i64>("let a = new_ts(); a.x = 500; a.x"),
Ok(500) Ok(())
);
} }
#[test] #[test]
fn test_big_get_set() { fn test_big_get_set() -> Result<(), EvalAltResult> {
#[derive(Clone)] #[derive(Clone)]
struct TestChild { struct TestChild {
x: i64, x: i64,
@ -88,7 +86,9 @@ fn test_big_get_set() {
engine.register_fn("new_tp", TestParent::new); engine.register_fn("new_tp", TestParent::new);
assert_eq!( assert_eq!(
engine.eval::<i64>("let a = new_tp(); a.child.x = 500; a.child.x"), engine.eval::<i64>("let a = new_tp(); a.child.x = 500; a.child.x")?,
Ok(500) 500
); );
Ok(())
} }

View File

@ -1,10 +1,29 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_if() { fn test_if() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("if true { 55 }"), Ok(55)); assert_eq!(engine.eval::<i64>("if true { 55 }")?, 55);
assert_eq!(engine.eval::<i64>("if false { 55 } else { 44 }"), Ok(44)); assert_eq!(engine.eval::<i64>("if false { 55 } else { 44 }")?, 44);
assert_eq!(engine.eval::<i64>("if true { 55 } else { 44 }"), Ok(55)); assert_eq!(engine.eval::<i64>("if true { 55 } else { 44 }")?, 55);
assert_eq!(
engine.eval::<i64>("if false { 55 } else if true { 33 } else { 44 }")?,
33
);
assert_eq!(
engine.eval::<i64>(
r"
if false { 55 }
else if false { 33 }
else if false { 66 }
else if false { 77 }
else if false { 88 }
else { 44 }
"
)?,
44
);
Ok(())
} }

View File

@ -1,12 +1,14 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_increment() { fn test_increment() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("let x = 1; x += 2; x"), Ok(3)); assert_eq!(engine.eval::<i64>("let x = 1; x += 2; x")?, 3);
assert_eq!( assert_eq!(
engine.eval::<String>("let s = \"test\"; s += \"ing\"; s"), engine.eval::<String>("let s = \"test\"; s += \"ing\"; s")?,
Ok("testing".to_string()) "testing".to_string()
); );
Ok(())
} }

View File

@ -1,25 +1,30 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_internal_fn() { fn test_internal_fn() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!( assert_eq!(engine.eval::<i64>("fn addme(a, b) { a+b } addme(3, 4)")?, 7);
engine.eval::<i64>("fn addme(a, b) { a+b } addme(3, 4)"), assert_eq!(engine.eval::<i64>("fn bob() { return 4; 5 } bob()")?, 4);
Ok(7)
); Ok(())
assert_eq!(engine.eval::<i64>("fn bob() { return 4; 5 } bob()"), Ok(4));
} }
#[test] #[test]
fn test_big_internal_fn() { fn test_big_internal_fn() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<i64>( engine.eval::<i64>(
"fn mathme(a, b, c, d, e, f) { a - b * c + d * e - f \ r"
} mathme(100, 5, 2, 9, 6, 32)", fn mathme(a, b, c, d, e, f) {
), a - b * c + d * e - f
Ok(112) }
mathme(100, 5, 2, 9, 6, 32)
",
)?,
112
); );
Ok(())
} }

View File

@ -1,12 +1,11 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_loop() { fn test_loop() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert!(engine assert!(engine.eval::<bool>(
.eval::<bool>( r"
"
let x = 0; let x = 0;
let i = 0; let i = 0;
@ -22,6 +21,7 @@ fn test_loop() {
x == 45 x == 45
" "
) )?);
.unwrap())
Ok(())
} }

View File

@ -1,8 +1,7 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult, RegisterFn};
use rhai::RegisterFn;
#[test] #[test]
fn test_method_call() { fn test_method_call() -> Result<(), EvalAltResult> {
#[derive(Clone)] #[derive(Clone)]
struct TestStruct { struct TestStruct {
x: i64, x: i64,
@ -25,9 +24,9 @@ fn test_method_call() {
engine.register_fn("update", TestStruct::update); engine.register_fn("update", TestStruct::update);
engine.register_fn("new_ts", TestStruct::new); engine.register_fn("new_ts", TestStruct::new);
if let Ok(result) = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x") { let ts = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
assert_eq!(result.x, 1001);
} else { assert_eq!(ts.x, 1001);
assert!(false);
} Ok(())
} }

View File

@ -4,12 +4,12 @@ use rhai::{Engine, EvalAltResult, RegisterFn};
fn test_mismatched_op() { fn test_mismatched_op() {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!( let r = engine.eval::<i64>("60 + \"hello\"");
engine.eval::<i64>("60 + \"hello\""),
Err(EvalAltResult::ErrorMismatchOutputType( match r {
"alloc::string::String".into() Err(EvalAltResult::ErrorMismatchOutputType(err, _)) if err == "string" => (),
)) _ => panic!(),
); }
} }
#[test] #[test]
@ -29,10 +29,14 @@ fn test_mismatched_op_custom_type() {
engine.register_type::<TestStruct>(); engine.register_type::<TestStruct>();
engine.register_fn("new_ts", TestStruct::new); engine.register_fn("new_ts", TestStruct::new);
assert_eq!( let r = engine.eval::<i64>("60 + new_ts()");
engine.eval::<i64>("60 + new_ts()"),
Err(EvalAltResult::ErrorFunctionNotFound( match r {
"+ (i64, mismatched_op::test_mismatched_op_custom_type::TestStruct)".into() Err(EvalAltResult::ErrorFunctionNotFound(err, _))
)) if err == "+ (i64, mismatched_op::test_mismatched_op_custom_type::TestStruct)" =>
); {
()
}
_ => panic!(),
}
} }

View File

@ -1,16 +1,18 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_not() { fn test_not() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<bool>("let not_true = !true; not_true"), engine.eval::<bool>("let not_true = !true; not_true")?,
Ok(false) false
); );
assert_eq!(engine.eval::<bool>("fn not(x) { !x } not(false)"), Ok(true)); assert_eq!(engine.eval::<bool>("fn not(x) { !x } not(false)")?, true);
// TODO - do we allow stacking unary operators directly? e.g '!!!!!!!true' // TODO - do we allow stacking unary operators directly? e.g '!!!!!!!true'
assert_eq!(engine.eval::<bool>("!(!(!(!(true))))"), Ok(true)); assert_eq!(engine.eval::<bool>("!(!(!(!(true))))")?, true);
Ok(())
} }

View File

@ -1,35 +1,43 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_number_literal() { fn test_number_literal() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("65"), Ok(65)); assert_eq!(engine.eval::<i64>("65")?, 65);
Ok(())
} }
#[test] #[test]
fn test_hex_literal() { fn test_hex_literal() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("let x = 0xf; x"), Ok(15)); assert_eq!(engine.eval::<i64>("let x = 0xf; x")?, 15);
assert_eq!(engine.eval::<i64>("let x = 0xff; x"), Ok(255)); assert_eq!(engine.eval::<i64>("let x = 0xff; x")?, 255);
Ok(())
} }
#[test] #[test]
fn test_octal_literal() { fn test_octal_literal() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("let x = 0o77; x"), Ok(63)); assert_eq!(engine.eval::<i64>("let x = 0o77; x")?, 63);
assert_eq!(engine.eval::<i64>("let x = 0o1234; x"), Ok(668)); assert_eq!(engine.eval::<i64>("let x = 0o1234; x")?, 668);
Ok(())
} }
#[test] #[test]
fn test_binary_literal() { fn test_binary_literal() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("let x = 0b1111; x"), Ok(15)); assert_eq!(engine.eval::<i64>("let x = 0b1111; x")?, 15);
assert_eq!( assert_eq!(
engine.eval::<i64>("let x = 0b0011_1100_1010_0101; x"), engine.eval::<i64>("let x = 0b0011_1100_1010_0101; x")?,
Ok(15525) 15525
); );
Ok(())
} }

View File

@ -1,19 +1,23 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_ops() { fn test_ops() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("60 + 5"), Ok(65)); assert_eq!(engine.eval::<i64>("60 + 5")?, 65);
assert_eq!(engine.eval::<i64>("(1 + 2) * (6 - 4) / 2"), Ok(3)); assert_eq!(engine.eval::<i64>("(1 + 2) * (6 - 4) / 2")?, 3);
Ok(())
} }
#[test] #[test]
fn test_op_prec() { fn test_op_prec() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<i64>("let x = 0; if x == 10 || true { x = 1} x"), engine.eval::<i64>("let x = 0; if x == 10 || true { x = 1} x")?,
Ok(1) 1
); );
Ok(())
} }

View File

@ -1,36 +1,34 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_power_of() { fn test_power_of() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("2 ~ 3"), Ok(8)); assert_eq!(engine.eval::<i64>("2 ~ 3")?, 8);
assert_eq!(engine.eval::<i64>("(-2 ~ 3)"), Ok(-8)); assert_eq!(engine.eval::<i64>("(-2 ~ 3)")?, -8);
assert_eq!(engine.eval::<f64>("2.2 ~ 3.3"), Ok(13.489468760533386_f64)); assert_eq!(engine.eval::<f64>("2.2 ~ 3.3")?, 13.489468760533386_f64);
assert_eq!(engine.eval::<f64>("2.0~-2.0"), Ok(0.25_f64)); assert_eq!(engine.eval::<f64>("2.0~-2.0")?, 0.25_f64);
assert_eq!(engine.eval::<f64>("(-2.0~-2.0)"), Ok(0.25_f64)); assert_eq!(engine.eval::<f64>("(-2.0~-2.0)")?, 0.25_f64);
assert_eq!(engine.eval::<f64>("(-2.0~-2)"), Ok(0.25_f64)); assert_eq!(engine.eval::<f64>("(-2.0~-2)")?, 0.25_f64);
assert_eq!(engine.eval::<i64>("4~3"), Ok(64)); assert_eq!(engine.eval::<i64>("4~3")?, 64);
Ok(())
} }
#[test] #[test]
fn test_power_of_equals() { fn test_power_of_equals() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("let x = 2; x ~= 3; x"), Ok(8)); assert_eq!(engine.eval::<i64>("let x = 2; x ~= 3; x")?, 8);
assert_eq!(engine.eval::<i64>("let x = -2; x ~= 3; x"), Ok(-8)); assert_eq!(engine.eval::<i64>("let x = -2; x ~= 3; x")?, -8);
assert_eq!( assert_eq!(
engine.eval::<f64>("let x = 2.2; x ~= 3.3; x"), engine.eval::<f64>("let x = 2.2; x ~= 3.3; x")?,
Ok(13.489468760533386_f64) 13.489468760533386_f64
); );
assert_eq!( assert_eq!(engine.eval::<f64>("let x = 2.0; x ~= -2.0; x")?, 0.25_f64);
engine.eval::<f64>("let x = 2.0; x ~= -2.0; x"), assert_eq!(engine.eval::<f64>("let x = -2.0; x ~= -2.0; x")?, 0.25_f64);
Ok(0.25_f64) assert_eq!(engine.eval::<f64>("let x = -2.0; x ~= -2; x")?, 0.25_f64);
); assert_eq!(engine.eval::<i64>("let x =4; x ~= 3; x")?, 64);
assert_eq!(
engine.eval::<f64>("let x = -2.0; x ~= -2.0; x"), Ok(())
Ok(0.25_f64)
);
assert_eq!(engine.eval::<f64>("let x = -2.0; x ~= -2; x"), Ok(0.25_f64));
assert_eq!(engine.eval::<i64>("let x =4; x ~= 3; x"), Ok(64));
} }

View File

@ -1,15 +1,17 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_string() { 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>("\"Test string: \\u2764\"")?,
Ok("Test string: ❤".to_string()) "Test string: ❤".to_string()
); );
assert_eq!( assert_eq!(
engine.eval::<String>("\"foo\" + \"bar\""), engine.eval::<String>("\"foo\" + \"bar\"")?,
Ok("foobar".to_string()) "foobar".to_string()
); );
Ok(())
} }

View File

@ -1,15 +1,17 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
// TODO also add test case for unary after compound // TODO also add test case for unary after compound
// Hah, turns out unary + has a good use after all! // Hah, turns out unary + has a good use after all!
fn test_unary_after_binary() { fn test_unary_after_binary() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("10 % +4"), Ok(2)); assert_eq!(engine.eval::<i64>("10 % +4")?, 2);
assert_eq!(engine.eval::<i64>("10 << +4"), Ok(160)); assert_eq!(engine.eval::<i64>("10 << +4")?, 160);
assert_eq!(engine.eval::<i64>("10 >> +4"), Ok(0)); assert_eq!(engine.eval::<i64>("10 >> +4")?, 0);
assert_eq!(engine.eval::<i64>("10 & +4"), Ok(0)); assert_eq!(engine.eval::<i64>("10 & +4")?, 0);
assert_eq!(engine.eval::<i64>("10 | +4"), Ok(14)); assert_eq!(engine.eval::<i64>("10 | +4")?, 14);
assert_eq!(engine.eval::<i64>("10 ^ +4"), Ok(14)); assert_eq!(engine.eval::<i64>("10 ^ +4")?, 14);
Ok(())
} }

View File

@ -1,10 +1,12 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_unary_minus() { fn test_unary_minus() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<i64>("let x = -5; x"), Ok(-5)); assert_eq!(engine.eval::<i64>("let x = -5; x")?, -5);
assert_eq!(engine.eval::<i64>("fn n(x) { -x } n(5)"), Ok(-5)); assert_eq!(engine.eval::<i64>("fn n(x) { -x } n(5)")?, -5);
assert_eq!(engine.eval::<i64>("5 - -(-5)"), Ok(0)); assert_eq!(engine.eval::<i64>("5 - -(-5)")?, 0);
Ok(())
} }

View File

@ -1,25 +1,22 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_unit() { fn test_unit() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<()>("let x = (); x")?, ());
assert_eq!(engine.eval::<()>("let x = (); x"), Ok(())); Ok(())
} }
#[test] #[test]
fn test_unit_eq() { fn test_unit_eq() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<bool>("let x = (); let y = (); x == y")?, true);
assert_eq!( Ok(())
engine.eval::<bool>("let x = (); let y = (); x == y"),
Ok(true)
);
} }
#[test] #[test]
fn test_unit_with_spaces() { fn test_unit_with_spaces() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<()>("let x = ( ); x")?, ());
assert_eq!(engine.eval::<()>("let x = ( ); x"), Ok(())); Ok(())
} }

View File

@ -1,28 +1,16 @@
use rhai::{Engine, Scope}; use rhai::{Engine, EvalAltResult, Scope};
#[test] #[test]
fn test_var_scope() { fn test_var_scope() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let mut scope = Scope::new(); let mut scope = Scope::new();
assert_eq!( engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?;
engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5"), assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x")?, 9);
Ok(()) engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?;
); assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x")?, 12);
assert_eq!(engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?, ());
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x")?, 12);
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x"), Ok(9)); Ok(())
assert_eq!(
engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;"),
Ok(())
);
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x"), Ok(12));
assert_eq!(
engine.eval_with_scope::<()>(&mut scope, "{let x = 3}"),
Ok(())
);
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x"), Ok(12));
} }

View File

@ -1,14 +1,16 @@
use rhai::Engine; use rhai::{Engine, EvalAltResult};
#[test] #[test]
fn test_while() { fn test_while() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<i64>( engine.eval::<i64>(
"let x = 0; while x < 10 { x = x + 1; if x > 5 { \ "let x = 0; while x < 10 { x = x + 1; if x > 5 { \
break } } x", break } } x",
), )?,
Ok(6) 6
); );
Ok(())
} }