Merge branch 'min-build'

This commit is contained in:
Stephen Chung 2020-03-11 14:43:25 +08:00
commit dbfc38763a
16 changed files with 287 additions and 199 deletions

View File

@ -18,15 +18,18 @@ include = [
num-traits = "*" num-traits = "*"
[features] [features]
#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked"]
default = [] default = []
debug_msgs = [] debug_msgs = [] # print debug messages on function registrations and calls
unchecked = [] unchecked = [] # unchecked arithmetic
no_stdlib = [] no_stdlib = [] # no standard library of utility functions
no_index = [] no_index = [] # no arrays and indexing
no_float = [] no_float = [] # no floating-point
only_i32 = [] no_function = [] # no script-defined functions
only_i64 = [] only_i32 = [] # set INT=i32 (useful for 32-bit systems)
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
[profile.release] [profile.release]
lto = "fat" lto = "fat"
codegen-units = 1 codegen-units = 1
#opt-level = "z" # optimize for size

View File

@ -10,7 +10,7 @@ Rhai's current feature set:
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app) * Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
* Easy-to-use language similar to JS+Rust * Easy-to-use language similar to JS+Rust
* Support for overloaded functions * Support for overloaded functions
* Very few additional dependencies (right now only `num-traits` to do checked arithmetic operations) * Very few additional dependencies (right now only [`num-traits`] to do checked arithmetic operations)
**Note:** Currently, the version is 0.10.2, so the language and API's may change before they stabilize. **Note:** Currently, the version is 0.10.2, so the language and API's may change before they stabilize.
@ -38,15 +38,16 @@ Beware that in order to use pre-releases (alpha and beta) you need to specify th
Optional features Optional features
----------------- -----------------
| Feature | Description | | Feature | Description |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `debug_msgs` | Print debug messages to stdout related to function registrations and calls. | | `debug_msgs` | Print debug messages to stdout related to function registrations and calls. |
| `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. Standard types are not affected. | | `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. Standard types are not affected. |
| `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! |
| `no_index` | Disable arrays and indexing features if you don't need them. | | `no_function` | Disable script-defined functions if you don't need them. |
| `no_float` | Disable floating-point numbers and math if you don't need them. | | `no_index` | Disable arrays and indexing features if you don't need them. |
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. | | `no_float` | Disable floating-point numbers and math if you don't need them. |
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. | | `only_i32` | Set the system integer type to `i32` and disable all other integer types. |
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. |
By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here for you to opt-**out** of certain functionalities that you do not need. By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here for you to opt-**out** of certain functionalities that you do not need.
Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language. Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language.
@ -56,8 +57,8 @@ 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] - 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] on [awesome-rust].
Examples Examples
-------- --------
@ -195,24 +196,24 @@ Values and types
The following primitive types are supported natively: The following primitive types are supported natively:
| Category | Types | | Category | Types |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| **Integer** | `u8`, `i8`, `u16`, `i16`, <br/>`u32`, `i32` (default for [`only_i32`](#optional-features)),<br/>`u64`, `i64` _(default)_ | | **Integer** | `u8`, `i8`, `u16`, `i16`, <br/>`u32`, `i32` (default for [`only_i32`]),<br/>`u64`, `i64` _(default)_ |
| **Floating-point** (disabled with [`no_float`](#optional-features)) | `f32`, `f64` _(default)_ | | **Floating-point** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ |
| **Character** | `char` | | **Character** | `char` |
| **Boolean** | `bool` | | **Boolean** | `bool` |
| **Array** (disabled with [`no_index`](#optional-features)) | `rhai::Array` | | **Array** (disabled with [`no_index`]) | `rhai::Array` |
| **Dynamic** (i.e. can be anything) | `rhai::Dynamic` | | **Dynamic** (i.e. can be anything) | `rhai::Dynamic` |
| **System** (current configuration) | `rhai::INT` (`i32` or `i64`),<br/>`rhai::FLOAT` (`f32` or `f64`) | | **System** (current configuration) | `rhai::INT` (`i32` or `i64`),<br/>`rhai::FLOAT` (`f32` or `f64`) |
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different; you cannot even add them together. All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different; you cannot even add them together.
The default integer type is `i64`. If you do not need any other integer type, you can enable the [`only_i64`](#optional-features) feature. The default integer type is `i64`. If you do not need any other integer type, you can enable the [`only_i64`] feature.
If you only need 32-bit integers, you can enable the [`only_i32`](#optional-features) feature and remove support for all integer types other than `i32` including `i64`. If you only need 32-bit integers, you can enable the [`only_i32`] feature and remove support for all integer types other than `i32` including `i64`.
This is useful on 32-bit systems where using 64-bit integers incurs a performance penalty. This is useful on 32-bit systems where using 64-bit integers incurs a performance penalty.
If you do not need floating-point, enable the [`no_float`](#optional-features) feature to remove support. If you do not need floating-point, enable the [`no_float`] feature to remove support.
Value conversions Value conversions
----------------- -----------------
@ -303,17 +304,17 @@ use std::fmt::Display;
use rhai::{Engine, RegisterFn}; use rhai::{Engine, RegisterFn};
fn showit<T: Display>(x: &mut T) -> () { fn show_it<T: Display>(x: &mut T) -> () {
println!("{}", x) println!("put up a good show: {}!", x)
} }
fn main() fn main()
{ {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.register_fn("print", showit as fn(x: &mut i64)->()); engine.register_fn("print", show_it as fn(x: &mut i64)->());
engine.register_fn("print", showit as fn(x: &mut bool)->()); engine.register_fn("print", show_it as fn(x: &mut bool)->());
engine.register_fn("print", showit as fn(x: &mut String)->()); engine.register_fn("print", show_it as fn(x: &mut String)->());
} }
``` ```
@ -615,24 +616,23 @@ Unary operators
```rust ```rust
let number = -5; let number = -5;
number = -5 - +5; number = -5 - +5;
let booly = !true; let boolean = !true;
``` ```
Numeric functions Numeric functions
----------------- -----------------
The following standard functions (defined in the standard library but excluded if [`no_stdlib`](#optional-features)) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only:
| Function | Description | | Function | Description |
| ---------- | ----------------------------------- | | ---------- | --------------------------------- |
| `abs` | absolute value | | `abs` | absolute value |
| `to_int` | converts an `f32` or `f64` to `i64` | | `to_float` | converts an integer type to `f64` |
| `to_float` | converts an integer type to `f64` |
Floating-point functions Floating-point functions
------------------------ ------------------------
The following standard functions (defined in the standard library but excluded if [`no_stdlib`](#optional-features)) operate on `f64` only: The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on `f64` only:
| Category | Functions | | Category | Functions |
| ---------------- | ------------------------------------------------------------ | | ---------------- | ------------------------------------------------------------ |
@ -642,7 +642,8 @@ The following standard functions (defined in the standard library but excluded i
| Exponential | `exp` (base _e_) | | Exponential | `exp` (base _e_) |
| Logarithmic | `ln` (base _e_), `log10` (base 10), `log` (any base) | | Logarithmic | `ln` (base _e_), `log10` (base 10), `log` (any base) |
| Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` | | Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` |
| Tests | `is_nan`, `is_finite`, `is_infinite` | | Conversion | `to_int` |
| Testing | `is_nan`, `is_finite`, `is_infinite` |
Strings and Chars Strings and Chars
----------------- -----------------
@ -684,7 +685,7 @@ record[4] = '\x58'; // 0x58 = 'X'
record == "Bob X. Davis: age 42 ❤\n"; record == "Bob X. Davis: age 42 ❤\n";
``` ```
The following standard functions (defined in the standard library but excluded if [`no_stdlib`](#optional-features)) operate on strings: The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on strings:
| Function | Description | | Function | Description |
| ---------- | ------------------------------------------------------------------------ | | ---------- | ------------------------------------------------------------------------ |
@ -731,7 +732,7 @@ Arrays
You can create arrays of values, and then access them with numeric indices. You can create arrays of values, and then access them with numeric indices.
The following functions (defined in the standard library but excluded if [`no_stdlib`](#optional-features)) operate on arrays: The following functions (defined in the standard library but excluded if [`no_stdlib`]) operate on arrays:
| Function | Description | | Function | Description |
| ---------- | ------------------------------------------------------------------------------------- | | ---------- | ------------------------------------------------------------------------------------- |
@ -803,7 +804,7 @@ engine.register_fn("push",
The type of a Rhai array is `rhai::Array`. `type_of()` returns `"array"`. The type of a Rhai array is `rhai::Array`. `type_of()` returns `"array"`.
Arrays are disabled via the [`no_index`](#optional-features) feature. Arrays are disabled via the [`no_index`] feature.
Comparison operators Comparison operators
-------------------- --------------------
@ -954,7 +955,7 @@ println!(result); // prints "Runtime error: 42 is too large! (line 5, position
Functions Functions
--------- ---------
Rhai supports defining functions in script: Rhai supports defining functions in script (unless disabled with [`no_function`]):
```rust ```rust
fn add(x, y) { fn add(x, y) {
@ -1048,3 +1049,17 @@ for entry in log {
println!("{}", entry); println!("{}", entry);
} }
``` ```
[ChaiScript]: http://chaiscript.com/
[scripting languages for Rust]: https://github.com/rust-unofficial/awesome-rust#scripting
[awesome-rust]: https://github.com/rust-unofficial/awesome-rust
[`num-traits`]: https://crates.io/crates/num-traits/
[`debug_msgs`]: #optional-features
[`unchecked`]: #optional-features
[`no_stdlib`]: #optional-features
[`no_index`]: #optional-features
[`no_float`]: #optional-features
[`no_function`]: #optional-features
[`only_i32`]: #optional-features
[`only_i64`]: #optional-features

View File

@ -168,17 +168,25 @@ impl<'e> Engine<'e> {
retain_functions: bool, retain_functions: bool,
ast: &AST, ast: &AST,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
let AST(statements, functions) = ast; #[cfg(feature = "no_function")]
let AST(statements) = ast;
functions.iter().for_each(|f| { #[cfg(not(feature = "no_function"))]
engine.script_functions.insert( let statements = {
FnSpec { let AST(statements, functions) = ast;
name: f.name.clone().into(),
args: None, functions.iter().for_each(|f| {
}, engine.script_functions.insert(
Arc::new(FnIntExt::Int(f.clone())), FnSpec {
); name: f.name.clone().into(),
}); args: None,
},
Arc::new(FnIntExt::Int(f.clone())),
);
});
statements
};
let result = statements let result = statements
.iter() .iter()
@ -244,16 +252,26 @@ impl<'e> Engine<'e> {
parse(&mut tokens.peekable(), self.optimize) parse(&mut tokens.peekable(), self.optimize)
.map_err(|err| EvalAltResult::ErrorParsing(err)) .map_err(|err| EvalAltResult::ErrorParsing(err))
.and_then(|AST(ref statements, ref functions)| { .and_then(|ast| {
for f in functions { #[cfg(feature = "no_function")]
self.script_functions.insert( let AST(statements) = ast;
FnSpec {
name: f.name.clone().into(), #[cfg(not(feature = "no_function"))]
args: None, let statements = {
}, let AST(ref statements, ref functions) = ast;
Arc::new(FnIntExt::Int(f.clone())),
); functions.iter().for_each(|f| {
} self.script_functions.insert(
FnSpec {
name: f.name.clone().into(),
args: None,
},
Arc::new(FnIntExt::Int(f.clone())),
);
});
statements
};
let val = statements let val = statements
.iter() .iter()
@ -275,6 +293,7 @@ impl<'e> Engine<'e> {
/// ```rust /// ```rust
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// # #[cfg(not(feature = "no_stdlib"))] /// # #[cfg(not(feature = "no_stdlib"))]
/// # #[cfg(not(feature = "no_function"))]
/// # { /// # {
/// use rhai::Engine; /// use rhai::Engine;
/// ///
@ -289,6 +308,7 @@ impl<'e> Engine<'e> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_function"))]
pub fn call_fn<A: FuncArgs, T: Any + Clone>( pub fn call_fn<A: FuncArgs, T: Any + Clone>(
&mut self, &mut self,
name: &str, name: &str,

View File

@ -2,45 +2,23 @@
//! _standard library_ of utility functions. //! _standard library_ of utility functions.
use crate::any::Any; use crate::any::Any;
use crate::engine::Engine;
use crate::fn_register::RegisterFn;
use crate::parser::INT;
#[cfg(not(feature = "unchecked"))]
use crate::{parser::Position, result::EvalAltResult, RegisterResultFn};
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::engine::Array; use crate::engine::Array;
use crate::engine::Engine;
#[cfg(not(feature = "no_float"))] use crate::fn_register::{RegisterFn, RegisterResultFn};
use crate::parser::{Position, INT};
use crate::result::EvalAltResult;
use crate::FLOAT; use crate::FLOAT;
use num_traits::{
identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl,
CheckedShr, CheckedSub,
};
use std::{ use std::{
fmt::{Debug, Display}, fmt::{Debug, Display},
ops::{BitAnd, BitOr, BitXor, Range}, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub},
}; {i32, i64, u32},
#[cfg(feature = "unchecked")]
use std::ops::{Shl, Shr};
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_float"))]
use std::{i32, i64};
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "only_i32"))]
use std::u32;
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
use std::ops::{Add, Div, Mul, Neg, Rem, Sub};
#[cfg(not(feature = "unchecked"))]
use {
num_traits::{
CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr,
CheckedSub,
},
std::convert::TryFrom,
}; };
macro_rules! reg_op { macro_rules! reg_op {
@ -162,12 +140,9 @@ impl Engine<'_> {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn div<T>(x: T, y: T) -> Result<T, EvalAltResult> fn div<T>(x: T, y: T) -> Result<T, EvalAltResult>
where where
T: Display + CheckedDiv + PartialEq + TryFrom<i8>, T: Display + CheckedDiv + PartialEq + Zero,
{ {
if y == <T as TryFrom<i8>>::try_from(0) if y == T::zero() {
.map_err(|_| ())
.expect("zero should always succeed")
{
return Err(EvalAltResult::ErrorArithmetic( return Err(EvalAltResult::ErrorArithmetic(
format!("Division by zero: {} / {}", x, y), format!("Division by zero: {} / {}", x, y),
Position::none(), Position::none(),
@ -191,8 +166,10 @@ impl Engine<'_> {
}) })
} }
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn abs<T: Display + CheckedNeg + PartialOrd + From<i8>>(x: T) -> Result<T, EvalAltResult> { fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> Result<T, EvalAltResult> {
if x >= 0.into() { // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics
// when the number is ::MIN instead of returning ::MIN itself.
if x >= <T as Zero>::zero() {
Ok(x) Ok(x)
} else { } else {
x.checked_neg().ok_or_else(|| { x.checked_neg().ok_or_else(|| {
@ -224,14 +201,15 @@ impl Engine<'_> {
-x -x
} }
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn abs_u<T: Neg + PartialOrd + From<i8>>(x: T) -> T fn abs_u<T>(x: T) -> <T as Neg>::Output
where where
<T as Neg>::Output: Into<T>, T: Neg + PartialOrd + Default + Into<<T as Neg>::Output>,
{ {
if x < 0.into() { // Numbers should default to zero
(-x).into() if x < Default::default() {
-x
} else { } else {
x x.into()
} }
} }
fn lt<T: PartialOrd>(x: T, y: T) -> bool { fn lt<T: PartialOrd>(x: T, y: T) -> bool {

View File

@ -29,6 +29,7 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
pub(crate) const KEYWORD_PRINT: &'static str = "print"; pub(crate) const KEYWORD_PRINT: &'static str = "print";
pub(crate) const KEYWORD_DEBUG: &'static str = "debug"; pub(crate) const KEYWORD_DEBUG: &'static str = "debug";
pub(crate) const KEYWORD_DUMP_AST: &'static str = "dump_ast";
pub(crate) const KEYWORD_TYPE_OF: &'static str = "type_of"; pub(crate) const KEYWORD_TYPE_OF: &'static str = "type_of";
pub(crate) const FUNC_GETTER: &'static str = "get$"; pub(crate) const FUNC_GETTER: &'static str = "get$";
pub(crate) const FUNC_SETTER: &'static str = "set$"; pub(crate) const FUNC_SETTER: &'static str = "set$";
@ -788,6 +789,9 @@ impl Engine<'_> {
.eval_index_expr(scope, lhs, idx_expr, *idx_pos) .eval_index_expr(scope, lhs, idx_expr, *idx_pos)
.map(|(_, _, _, x)| x), .map(|(_, _, _, x)| x),
#[cfg(feature = "no_index")]
Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"),
// Statement block // Statement block
Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt), Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt),
@ -855,7 +859,34 @@ impl Engine<'_> {
Ok(Box::new(arr)) Ok(Box::new(arr))
} }
#[cfg(feature = "no_index")]
Expr::Array(_, _) => panic!("encountered an array during no_index!"),
// Dump AST
Expr::FunctionCall(fn_name, args, _, pos) if fn_name == KEYWORD_DUMP_AST => {
let pos = if args.len() == 0 {
*pos
} else {
args[0].position()
};
// Change the argument to a debug dump of the expressions
let result = args
.into_iter()
.map(|expr| format!("{:#?}", expr))
.collect::<Vec<_>>()
.join("\n");
// Redirect call to `print`
self.call_fn_raw(
KEYWORD_PRINT,
vec![result.into_dynamic().as_mut()],
None,
pos,
)
}
// Normal function call
Expr::FunctionCall(fn_name, args, def_val, pos) => { Expr::FunctionCall(fn_name, args, def_val, pos) => {
let mut args = args let mut args = args
.iter() .iter()

View File

@ -120,20 +120,20 @@ macro_rules! def_register {
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS { if args.len() != NUM_ARGS {
Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)) return 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::<$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::<$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, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
} }
@ -152,19 +152,19 @@ macro_rules! def_register {
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS { if args.len() != NUM_ARGS {
Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)) return 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::<$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::<$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, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
} }
@ -184,23 +184,23 @@ macro_rules! def_register {
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS { if args.len() != NUM_ARGS {
Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)) return 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::<$par>().unwrap();
)*
// Call the user-supplied function using ($clone) to #[allow(unused_variables, unused_mut)]
// potentially clone the value, otherwise pass the reference. let mut drain = args.drain(..);
match f($(($clone)($par)),*) { $(
Ok(r) => Ok(Box::new(r) as Dynamic), // Downcast every element, return in case of a type mismatch
Err(mut err) => { let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap();
err.set_position(pos); )*
Err(err)
} // Call the user-supplied function using ($clone) to
// potentially clone the value, otherwise pass the reference.
match f($(($clone)($par)),*) {
Ok(r) => Ok(Box::new(r) as Dynamic),
Err(mut err) => {
err.set_position(pos);
Err(err)
} }
} }
}; };

View File

@ -141,6 +141,7 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
Box::new(optimize_expr(*rhs, changed)), Box::new(optimize_expr(*rhs, changed)),
pos, pos,
), ),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) { Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) {
(Expr::Array(mut items, _), Expr::IntegerConstant(i, _)) (Expr::Array(mut items, _), Expr::IntegerConstant(i, _))
@ -158,6 +159,9 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
pos, pos,
), ),
}, },
#[cfg(feature = "no_index")]
Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(items, pos) => { Expr::Array(items, pos) => {
let original_len = items.len(); let original_len = items.len();
@ -172,6 +176,9 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
Expr::Array(items, pos) Expr::Array(items, pos)
} }
#[cfg(feature = "no_index")]
Expr::Array(_, _) => panic!("encountered an array during no_index!"),
Expr::And(lhs, rhs) => match (*lhs, *rhs) { Expr::And(lhs, rhs) => match (*lhs, *rhs) {
(Expr::True(_), rhs) => { (Expr::True(_), rhs) => {
*changed = true; *changed = true;
@ -208,7 +215,6 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
Box::new(optimize_expr(rhs, changed)), Box::new(optimize_expr(rhs, changed)),
), ),
}, },
Expr::FunctionCall(id, args, def_value, pos) => { Expr::FunctionCall(id, args, def_value, pos) => {
let original_len = args.len(); let original_len = args.len();

View File

@ -3,13 +3,18 @@
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::error::{LexError, ParseError, ParseErrorType}; use crate::error::{LexError, ParseError, ParseErrorType};
use crate::optimize::optimize; use crate::optimize::optimize;
use std::{borrow::Cow, char, fmt, iter::Peekable, str::Chars, str::FromStr, usize}; use std::{borrow::Cow, char, fmt, iter::Peekable, str::Chars, str::FromStr, usize};
/// The system integer type /// The system integer type.
///
/// If the `only_i32` feature is enabled, this will be `i32` instead.
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
pub type INT = i64; pub type INT = i64;
/// The system integer type /// The system integer type
///
/// If the `only_i32` feature is not enabled, this will be `i64` instead.
#[cfg(feature = "only_i32")] #[cfg(feature = "only_i32")]
pub type INT = i32; pub type INT = i32;
@ -134,7 +139,10 @@ impl fmt::Debug for Position {
} }
/// Compiled AST (abstract syntax tree) of a Rhai script. /// Compiled AST (abstract syntax tree) of a Rhai script.
pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<FnDef<'static>>); pub struct AST(
pub(crate) Vec<Stmt>,
#[cfg(not(feature = "no_function"))] pub(crate) Vec<FnDef<'static>>,
);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FnDef<'a> { pub struct FnDef<'a> {
@ -179,9 +187,7 @@ pub enum Expr {
FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position), FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position),
Assignment(Box<Expr>, Box<Expr>, Position), Assignment(Box<Expr>, Box<Expr>, Position),
Dot(Box<Expr>, Box<Expr>, Position), Dot(Box<Expr>, Box<Expr>, Position),
#[cfg(not(feature = "no_index"))]
Index(Box<Expr>, Box<Expr>, Position), Index(Box<Expr>, Box<Expr>, Position),
#[cfg(not(feature = "no_index"))]
Array(Vec<Expr>, Position), Array(Vec<Expr>, Position),
And(Box<Expr>, Box<Expr>), And(Box<Expr>, Box<Expr>),
Or(Box<Expr>, Box<Expr>), Or(Box<Expr>, Box<Expr>),
@ -197,24 +203,21 @@ impl Expr {
| Expr::Identifier(_, pos) | Expr::Identifier(_, pos)
| Expr::CharConstant(_, pos) | Expr::CharConstant(_, pos)
| Expr::StringConstant(_, pos) | Expr::StringConstant(_, pos)
| Expr::FunctionCall(_, _, _, pos)
| Expr::Stmt(_, pos) | Expr::Stmt(_, pos)
| Expr::FunctionCall(_, _, _, pos)
| Expr::Array(_, pos)
| Expr::True(pos) | Expr::True(pos)
| Expr::False(pos) | Expr::False(pos)
| Expr::Unit(pos) => *pos, | Expr::Unit(pos) => *pos,
Expr::Assignment(e, _, _) | Expr::Dot(e, _, _) | Expr::And(e, _) | Expr::Or(e, _) => { Expr::Assignment(e, _, _)
e.position() | Expr::Dot(e, _, _)
} | Expr::Index(e, _, _)
| Expr::And(e, _)
| Expr::Or(e, _) => e.position(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Expr::FloatConstant(_, pos) => *pos, Expr::FloatConstant(_, pos) => *pos,
#[cfg(not(feature = "no_index"))]
Expr::Index(e, _, _) => e.position(),
#[cfg(not(feature = "no_index"))]
Expr::Array(_, pos) => *pos,
} }
} }
@ -1820,6 +1823,7 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
} }
} }
#[cfg(not(feature = "no_function"))]
fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef<'static>, ParseError> { fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef<'static>, ParseError> {
let pos = match input.next() { let pos = match input.next() {
Some((_, tok_pos)) => tok_pos, Some((_, tok_pos)) => tok_pos,
@ -1892,11 +1896,14 @@ fn parse_top_level<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
optimize_ast: bool, optimize_ast: bool,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let mut statements = Vec::new(); let mut statements = Vec::<Stmt>::new();
let mut functions = Vec::new();
#[cfg(not(feature = "no_function"))]
let mut functions = Vec::<FnDef>::new();
while input.peek().is_some() { while input.peek().is_some() {
match input.peek() { match input.peek() {
#[cfg(not(feature = "no_function"))]
Some(&(Token::Fn, _)) => functions.push(parse_fn(input)?), Some(&(Token::Fn, _)) => functions.push(parse_fn(input)?),
_ => statements.push(parse_stmt(input)?), _ => statements.push(parse_stmt(input)?),
} }
@ -1910,6 +1917,7 @@ fn parse_top_level<'a>(
return Ok(if optimize_ast { return Ok(if optimize_ast {
AST( AST(
optimize(statements), optimize(statements),
#[cfg(not(feature = "no_function"))]
functions functions
.into_iter() .into_iter()
.map(|mut fn_def| { .map(|mut fn_def| {
@ -1920,7 +1928,11 @@ fn parse_top_level<'a>(
.collect(), .collect(),
) )
} else { } else {
AST(statements, functions) AST(
statements,
#[cfg(not(feature = "no_function"))]
functions,
)
}); });
} }

View File

@ -3,6 +3,7 @@
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::error::ParseError; use crate::error::ParseError;
use crate::parser::{Position, INT}; use crate::parser::{Position, INT};
use std::{error::Error, fmt}; use std::{error::Error, fmt};
/// Evaluation result. /// Evaluation result.
@ -75,12 +76,12 @@ impl Error for EvalAltResult {
Self::ErrorArrayBounds(_, index, _) if *index < 0 => { Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
"Array access expects non-negative index" "Array access expects non-negative index"
} }
Self::ErrorArrayBounds(max, _, _) if *max == 0 => "Access of empty array", Self::ErrorArrayBounds(0, _, _) => "Access of empty array",
Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds", Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds",
Self::ErrorStringBounds(_, index, _) if *index < 0 => { Self::ErrorStringBounds(_, index, _) if *index < 0 => {
"Indexing a string expects a non-negative index" "Indexing a string expects a non-negative index"
} }
Self::ErrorStringBounds(max, _, _) if *max == 0 => "Indexing of empty string", Self::ErrorStringBounds(0, _, _) => "Indexing of empty string",
Self::ErrorStringBounds(_, _, _) => "String index out of bounds", Self::ErrorStringBounds(_, _, _) => "String index out of bounds",
Self::ErrorIfGuard(_) => "If guard expects boolean expression", Self::ErrorIfGuard(_) => "If guard expects boolean expression",
Self::ErrorFor(_) => "For loop expects array or range", Self::ErrorFor(_) => "For loop expects array or range",
@ -128,6 +129,16 @@ impl fmt::Display for EvalAltResult {
write!(f, "{} '{}': {}", desc, filename, err) write!(f, "{} '{}': {}", desc, filename, err)
} }
Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p),
Self::ErrorFunctionArgsMismatch(fun, 0, n, pos) => write!(
f,
"Function '{}' expects no argument but {} found ({})",
fun, n, pos
),
Self::ErrorFunctionArgsMismatch(fun, 1, n, pos) => write!(
f,
"Function '{}' expects one argument but {} found ({})",
fun, n, pos
),
Self::ErrorFunctionArgsMismatch(fun, need, n, pos) => write!( Self::ErrorFunctionArgsMismatch(fun, need, n, pos) => write!(
f, f,
"Function '{}' expects {} argument(s) but {} found ({})", "Function '{}' expects {} argument(s) but {} found ({})",
@ -142,26 +153,30 @@ impl fmt::Display for EvalAltResult {
Self::ErrorArrayBounds(_, index, pos) if *index < 0 => { Self::ErrorArrayBounds(_, index, pos) if *index < 0 => {
write!(f, "{}: {} < 0 ({})", desc, index, pos) write!(f, "{}: {} < 0 ({})", desc, index, pos)
} }
Self::ErrorArrayBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), Self::ErrorArrayBounds(0, _, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorArrayBounds(1, index, pos) => write!(
f,
"Array index {} is out of bounds: only one element in the array ({})",
index, pos
),
Self::ErrorArrayBounds(max, index, pos) => write!( Self::ErrorArrayBounds(max, index, pos) => write!(
f, f,
"Array index {} is out of bounds: only {} element{} in the array ({})", "Array index {} is out of bounds: only {} elements in the array ({})",
index, index, max, pos
max,
if *max > 1 { "s" } else { "" },
pos
), ),
Self::ErrorStringBounds(_, index, pos) if *index < 0 => { Self::ErrorStringBounds(_, index, pos) if *index < 0 => {
write!(f, "{}: {} < 0 ({})", desc, index, pos) write!(f, "{}: {} < 0 ({})", desc, index, pos)
} }
Self::ErrorStringBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), Self::ErrorStringBounds(0, _, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorStringBounds(1, index, pos) => write!(
f,
"String index {} is out of bounds: only one character in the string ({})",
index, pos
),
Self::ErrorStringBounds(max, index, pos) => write!( Self::ErrorStringBounds(max, index, pos) => write!(
f, f,
"String index {} is out of bounds: only {} character{} in the string ({})", "String index {} is out of bounds: only {} characters in the string ({})",
index, index, max, pos
max,
if *max > 1 { "s" } else { "" },
pos
), ),
} }
} }

View File

@ -1,6 +1,7 @@
//! Module that defines the `Scope` type representing a function call-stack scope. //! Module that defines the `Scope` type representing a function call-stack scope.
use crate::any::{Any, Dynamic}; use crate::any::{Any, Dynamic};
use std::borrow::Cow; use std::borrow::Cow;
/// A type containing information about current scope. /// A type containing information about current scope.

View File

@ -39,10 +39,9 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> {
assert_eq!( assert_eq!(
engine.eval::<bool>( engine.eval::<bool>(
r" r"
fn this() { true } let this = true;
fn that() { 9/0 }
this() || that(); this || { throw; };
" "
)?, )?,
true true
@ -51,10 +50,9 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> {
assert_eq!( assert_eq!(
engine.eval::<bool>( engine.eval::<bool>(
r" r"
fn this() { false } let this = false;
fn that() { 9/0 }
this() && that(); this && { throw; };
" "
)?, )?,
false false
@ -72,10 +70,9 @@ fn test_bool_op_no_short_circuit1() {
engine engine
.eval::<bool>( .eval::<bool>(
r" r"
fn this() { false } let this = true;
fn that() { 9/0 }
this() | that(); this | { throw; }
" "
) )
.unwrap(), .unwrap(),
@ -92,10 +89,9 @@ fn test_bool_op_no_short_circuit2() {
engine engine
.eval::<bool>( .eval::<bool>(
r" r"
fn this() { false } let this = false;
fn that() { 9/0 }
this() & that(); this & { throw; }
" "
) )
.unwrap(), .unwrap(),

View File

@ -1,4 +1,5 @@
#![cfg(not(feature = "no_stdlib"))] #![cfg(not(feature = "no_stdlib"))]
#![cfg(not(feature = "no_function"))]
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]

View File

@ -1,3 +1,5 @@
#![cfg(not(feature = "no_function"))]
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]

View File

@ -24,6 +24,10 @@ fn test_math() -> Result<(), EvalAltResult> {
{ {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
{ {
match engine.eval::<INT>("(-9223372036854775808).abs()") {
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
r => panic!("should return overflow error: {:?}", r),
}
match engine.eval::<INT>("9223372036854775807 + 1") { match engine.eval::<INT>("9223372036854775807 + 1") {
Err(EvalAltResult::ErrorArithmetic(_, _)) => (), Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
r => panic!("should return overflow error: {:?}", r), r => panic!("should return overflow error: {:?}", r),

View File

@ -9,6 +9,7 @@ fn test_not() -> Result<(), EvalAltResult> {
false false
); );
#[cfg(not(feature = "no_function"))]
assert_eq!(engine.eval::<bool>("fn not(x) { !x } not(false)")?, 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'

View File

@ -5,7 +5,10 @@ fn test_unary_minus() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = -5; x")?, -5); assert_eq!(engine.eval::<INT>("let x = -5; x")?, -5);
#[cfg(not(feature = "no_function"))]
assert_eq!(engine.eval::<INT>("fn neg(x) { -x } neg(5)")?, -5); assert_eq!(engine.eval::<INT>("fn neg(x) { -x } neg(5)")?, -5);
assert_eq!(engine.eval::<INT>("5 - -+++--+-5")?, 0); assert_eq!(engine.eval::<INT>("5 - -+++--+-5")?, 0);
Ok(()) Ok(())