diff --git a/Cargo.toml b/Cargo.toml
index 8fa20102..a5177aa6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,15 +18,18 @@ include = [
num-traits = "*"
[features]
+#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked"]
default = []
-debug_msgs = []
-unchecked = []
-no_stdlib = []
-no_index = []
-no_float = []
-only_i32 = []
-only_i64 = []
+debug_msgs = [] # print debug messages on function registrations and calls
+unchecked = [] # unchecked arithmetic
+no_stdlib = [] # no standard library of utility functions
+no_index = [] # no arrays and indexing
+no_float = [] # no floating-point
+no_function = [] # no script-defined functions
+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]
lto = "fat"
codegen-units = 1
+#opt-level = "z" # optimize for size
diff --git a/README.md b/README.md
index 8d7724ea..c96d0a32 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ Rhai's current feature set:
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
* Easy-to-use language similar to JS+Rust
* 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.
@@ -38,15 +38,16 @@ Beware that in order to use pre-releases (alpha and beta) you need to specify th
Optional features
-----------------
-| Feature | Description |
-| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `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. |
-| `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_float` | Disable floating-point numbers and math if you don't need them. |
-| `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. |
+| Feature | Description |
+| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `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. |
+| `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! |
+| `no_function` | Disable script-defined functions if you don't need them. |
+| `no_index` | Disable arrays and indexing features if you don't need them. |
+| `no_float` | Disable floating-point numbers and math if you don't need them. |
+| `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.
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:
-* [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)
+* [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] on [awesome-rust].
Examples
--------
@@ -195,24 +196,24 @@ Values and types
The following primitive types are supported natively:
-| Category | Types |
-| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
-| **Integer** | `u8`, `i8`, `u16`, `i16`,
`u32`, `i32` (default for [`only_i32`](#optional-features)),
`u64`, `i64` _(default)_ |
-| **Floating-point** (disabled with [`no_float`](#optional-features)) | `f32`, `f64` _(default)_ |
-| **Character** | `char` |
-| **Boolean** | `bool` |
-| **Array** (disabled with [`no_index`](#optional-features)) | `rhai::Array` |
-| **Dynamic** (i.e. can be anything) | `rhai::Dynamic` |
-| **System** (current configuration) | `rhai::INT` (`i32` or `i64`),
`rhai::FLOAT` (`f32` or `f64`) |
+| Category | Types |
+| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
+| **Integer** | `u8`, `i8`, `u16`, `i16`,
`u32`, `i32` (default for [`only_i32`]),
`u64`, `i64` _(default)_ |
+| **Floating-point** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ |
+| **Character** | `char` |
+| **Boolean** | `bool` |
+| **Array** (disabled with [`no_index`]) | `rhai::Array` |
+| **Dynamic** (i.e. can be anything) | `rhai::Dynamic` |
+| **System** (current configuration) | `rhai::INT` (`i32` or `i64`),
`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.
-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.
-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
-----------------
@@ -303,17 +304,17 @@ use std::fmt::Display;
use rhai::{Engine, RegisterFn};
-fn showit(x: &mut T) -> () {
- println!("{}", x)
+fn show_it(x: &mut T) -> () {
+ println!("put up a good show: {}!", x)
}
fn main()
{
let mut engine = Engine::new();
- engine.register_fn("print", showit as fn(x: &mut i64)->());
- engine.register_fn("print", showit 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 i64)->());
+ engine.register_fn("print", show_it as fn(x: &mut bool)->());
+ engine.register_fn("print", show_it as fn(x: &mut String)->());
}
```
@@ -615,24 +616,23 @@ Unary operators
```rust
let number = -5;
number = -5 - +5;
-let booly = !true;
+let boolean = !true;
```
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 |
-| ---------- | ----------------------------------- |
-| `abs` | absolute value |
-| `to_int` | converts an `f32` or `f64` to `i64` |
-| `to_float` | converts an integer type to `f64` |
+| Function | Description |
+| ---------- | --------------------------------- |
+| `abs` | absolute value |
+| `to_float` | converts an integer type to `f64` |
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 |
| ---------------- | ------------------------------------------------------------ |
@@ -642,7 +642,8 @@ The following standard functions (defined in the standard library but excluded i
| Exponential | `exp` (base _e_) |
| Logarithmic | `ln` (base _e_), `log10` (base 10), `log` (any base) |
| 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
-----------------
@@ -684,7 +685,7 @@ record[4] = '\x58'; // 0x58 = 'X'
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 |
| ---------- | ------------------------------------------------------------------------ |
@@ -731,7 +732,7 @@ Arrays
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 |
| ---------- | ------------------------------------------------------------------------------------- |
@@ -803,7 +804,7 @@ engine.register_fn("push",
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
--------------------
@@ -954,7 +955,7 @@ println!(result); // prints "Runtime error: 42 is too large! (line 5, position
Functions
---------
-Rhai supports defining functions in script:
+Rhai supports defining functions in script (unless disabled with [`no_function`]):
```rust
fn add(x, y) {
@@ -1048,3 +1049,17 @@ for entry in log {
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
diff --git a/src/api.rs b/src/api.rs
index 9d309756..f5c7e0ce 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -168,17 +168,25 @@ impl<'e> Engine<'e> {
retain_functions: bool,
ast: &AST,
) -> Result {
- let AST(statements, functions) = ast;
+ #[cfg(feature = "no_function")]
+ let AST(statements) = ast;
- functions.iter().for_each(|f| {
- engine.script_functions.insert(
- FnSpec {
- name: f.name.clone().into(),
- args: None,
- },
- Arc::new(FnIntExt::Int(f.clone())),
- );
- });
+ #[cfg(not(feature = "no_function"))]
+ let statements = {
+ let AST(statements, functions) = ast;
+
+ functions.iter().for_each(|f| {
+ engine.script_functions.insert(
+ FnSpec {
+ name: f.name.clone().into(),
+ args: None,
+ },
+ Arc::new(FnIntExt::Int(f.clone())),
+ );
+ });
+
+ statements
+ };
let result = statements
.iter()
@@ -244,16 +252,26 @@ impl<'e> Engine<'e> {
parse(&mut tokens.peekable(), self.optimize)
.map_err(|err| EvalAltResult::ErrorParsing(err))
- .and_then(|AST(ref statements, ref functions)| {
- for f in functions {
- self.script_functions.insert(
- FnSpec {
- name: f.name.clone().into(),
- args: None,
- },
- Arc::new(FnIntExt::Int(f.clone())),
- );
- }
+ .and_then(|ast| {
+ #[cfg(feature = "no_function")]
+ let AST(statements) = ast;
+
+ #[cfg(not(feature = "no_function"))]
+ let statements = {
+ let AST(ref statements, ref functions) = ast;
+
+ 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
.iter()
@@ -275,6 +293,7 @@ impl<'e> Engine<'e> {
/// ```rust
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// # #[cfg(not(feature = "no_stdlib"))]
+ /// # #[cfg(not(feature = "no_function"))]
/// # {
/// use rhai::Engine;
///
@@ -289,6 +308,7 @@ impl<'e> Engine<'e> {
/// # Ok(())
/// # }
/// ```
+ #[cfg(not(feature = "no_function"))]
pub fn call_fn(
&mut self,
name: &str,
diff --git a/src/builtin.rs b/src/builtin.rs
index 0e8ab757..04a290ce 100644
--- a/src/builtin.rs
+++ b/src/builtin.rs
@@ -2,45 +2,23 @@
//! _standard library_ of utility functions.
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"))]
use crate::engine::Array;
-
-#[cfg(not(feature = "no_float"))]
+use crate::engine::Engine;
+use crate::fn_register::{RegisterFn, RegisterResultFn};
+use crate::parser::{Position, INT};
+use crate::result::EvalAltResult;
use crate::FLOAT;
+use num_traits::{
+ identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl,
+ CheckedShr, CheckedSub,
+};
+
use std::{
fmt::{Debug, Display},
- ops::{BitAnd, BitOr, BitXor, Range},
-};
-
-#[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,
+ ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub},
+ {i32, i64, u32},
};
macro_rules! reg_op {
@@ -162,12 +140,9 @@ impl Engine<'_> {
#[cfg(not(feature = "unchecked"))]
fn div(x: T, y: T) -> Result
where
- T: Display + CheckedDiv + PartialEq + TryFrom,
+ T: Display + CheckedDiv + PartialEq + Zero,
{
- if y == >::try_from(0)
- .map_err(|_| ())
- .expect("zero should always succeed")
- {
+ if y == T::zero() {
return Err(EvalAltResult::ErrorArithmetic(
format!("Division by zero: {} / {}", x, y),
Position::none(),
@@ -191,8 +166,10 @@ impl Engine<'_> {
})
}
#[cfg(not(feature = "unchecked"))]
- fn abs>(x: T) -> Result {
- if x >= 0.into() {
+ fn abs(x: T) -> Result {
+ // 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 >= ::zero() {
Ok(x)
} else {
x.checked_neg().ok_or_else(|| {
@@ -224,14 +201,15 @@ impl Engine<'_> {
-x
}
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
- fn abs_u>(x: T) -> T
+ fn abs_u(x: T) -> ::Output
where
- ::Output: Into,
+ T: Neg + PartialOrd + Default + Into<::Output>,
{
- if x < 0.into() {
- (-x).into()
+ // Numbers should default to zero
+ if x < Default::default() {
+ -x
} else {
- x
+ x.into()
}
}
fn lt(x: T, y: T) -> bool {
diff --git a/src/engine.rs b/src/engine.rs
index ebbf4ec7..62acd255 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -29,6 +29,7 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box>;
pub(crate) const KEYWORD_PRINT: &'static str = "print";
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 FUNC_GETTER: &'static str = "get$";
pub(crate) const FUNC_SETTER: &'static str = "set$";
@@ -788,6 +789,9 @@ impl Engine<'_> {
.eval_index_expr(scope, lhs, idx_expr, *idx_pos)
.map(|(_, _, _, x)| x),
+ #[cfg(feature = "no_index")]
+ Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"),
+
// Statement block
Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt),
@@ -855,7 +859,34 @@ impl Engine<'_> {
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::>()
+ .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) => {
let mut args = args
.iter()
diff --git a/src/fn_register.rs b/src/fn_register.rs
index 9af9ab82..84dbf150 100644
--- a/src/fn_register.rs
+++ b/src/fn_register.rs
@@ -120,20 +120,20 @@ macro_rules! def_register {
const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS {
- 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)
+ return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos));
}
+
+ #[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));
}
@@ -152,19 +152,19 @@ macro_rules! def_register {
const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS {
- 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)),*))
+ return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos));
}
+
+ #[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));
}
@@ -184,23 +184,23 @@ macro_rules! def_register {
const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS {
- 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();
- )*
+ return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos));
+ }
- // 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)
- }
+ #[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.
+ match f($(($clone)($par)),*) {
+ Ok(r) => Ok(Box::new(r) as Dynamic),
+ Err(mut err) => {
+ err.set_position(pos);
+ Err(err)
}
}
};
diff --git a/src/optimize.rs b/src/optimize.rs
index 085b09f9..cb92fb11 100644
--- a/src/optimize.rs
+++ b/src/optimize.rs
@@ -141,6 +141,7 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
Box::new(optimize_expr(*rhs, changed)),
pos,
),
+
#[cfg(not(feature = "no_index"))]
Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) {
(Expr::Array(mut items, _), Expr::IntegerConstant(i, _))
@@ -158,6 +159,9 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
pos,
),
},
+ #[cfg(feature = "no_index")]
+ Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"),
+
#[cfg(not(feature = "no_index"))]
Expr::Array(items, pos) => {
let original_len = items.len();
@@ -172,6 +176,9 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
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::True(_), rhs) => {
*changed = true;
@@ -208,7 +215,6 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
Box::new(optimize_expr(rhs, changed)),
),
},
-
Expr::FunctionCall(id, args, def_value, pos) => {
let original_len = args.len();
diff --git a/src/parser.rs b/src/parser.rs
index fcbe7e20..5aec9d46 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -3,13 +3,18 @@
use crate::any::Dynamic;
use crate::error::{LexError, ParseError, ParseErrorType};
use crate::optimize::optimize;
+
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"))]
pub type INT = i64;
/// The system integer type
+///
+/// If the `only_i32` feature is not enabled, this will be `i64` instead.
#[cfg(feature = "only_i32")]
pub type INT = i32;
@@ -134,7 +139,10 @@ impl fmt::Debug for Position {
}
/// Compiled AST (abstract syntax tree) of a Rhai script.
-pub struct AST(pub(crate) Vec, pub(crate) Vec>);
+pub struct AST(
+ pub(crate) Vec,
+ #[cfg(not(feature = "no_function"))] pub(crate) Vec>,
+);
#[derive(Debug, Clone)]
pub struct FnDef<'a> {
@@ -179,9 +187,7 @@ pub enum Expr {
FunctionCall(String, Vec, Option, Position),
Assignment(Box, Box, Position),
Dot(Box, Box, Position),
- #[cfg(not(feature = "no_index"))]
Index(Box, Box, Position),
- #[cfg(not(feature = "no_index"))]
Array(Vec, Position),
And(Box, Box),
Or(Box, Box),
@@ -197,24 +203,21 @@ impl Expr {
| Expr::Identifier(_, pos)
| Expr::CharConstant(_, pos)
| Expr::StringConstant(_, pos)
- | Expr::FunctionCall(_, _, _, pos)
| Expr::Stmt(_, pos)
+ | Expr::FunctionCall(_, _, _, pos)
+ | Expr::Array(_, pos)
| Expr::True(pos)
| Expr::False(pos)
| Expr::Unit(pos) => *pos,
- Expr::Assignment(e, _, _) | Expr::Dot(e, _, _) | Expr::And(e, _) | Expr::Or(e, _) => {
- e.position()
- }
+ Expr::Assignment(e, _, _)
+ | Expr::Dot(e, _, _)
+ | Expr::Index(e, _, _)
+ | Expr::And(e, _)
+ | Expr::Or(e, _) => e.position(),
#[cfg(not(feature = "no_float"))]
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>) -> Result(input: &mut Peekable>) -> Result, ParseError> {
let pos = match input.next() {
Some((_, tok_pos)) => tok_pos,
@@ -1892,11 +1896,14 @@ fn parse_top_level<'a>(
input: &mut Peekable>,
optimize_ast: bool,
) -> Result {
- let mut statements = Vec::new();
- let mut functions = Vec::new();
+ let mut statements = Vec::::new();
+
+ #[cfg(not(feature = "no_function"))]
+ let mut functions = Vec::::new();
while input.peek().is_some() {
match input.peek() {
+ #[cfg(not(feature = "no_function"))]
Some(&(Token::Fn, _)) => functions.push(parse_fn(input)?),
_ => statements.push(parse_stmt(input)?),
}
@@ -1910,6 +1917,7 @@ fn parse_top_level<'a>(
return Ok(if optimize_ast {
AST(
optimize(statements),
+ #[cfg(not(feature = "no_function"))]
functions
.into_iter()
.map(|mut fn_def| {
@@ -1920,7 +1928,11 @@ fn parse_top_level<'a>(
.collect(),
)
} else {
- AST(statements, functions)
+ AST(
+ statements,
+ #[cfg(not(feature = "no_function"))]
+ functions,
+ )
});
}
diff --git a/src/result.rs b/src/result.rs
index 8034329c..e0bb3734 100644
--- a/src/result.rs
+++ b/src/result.rs
@@ -3,6 +3,7 @@
use crate::any::Dynamic;
use crate::error::ParseError;
use crate::parser::{Position, INT};
+
use std::{error::Error, fmt};
/// Evaluation result.
@@ -75,12 +76,12 @@ impl Error for EvalAltResult {
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
"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::ErrorStringBounds(_, index, _) if *index < 0 => {
"Indexing a string expects a non-negative index"
}
- Self::ErrorStringBounds(max, _, _) if *max == 0 => "Indexing of empty string",
+ Self::ErrorStringBounds(0, _, _) => "Indexing of empty string",
Self::ErrorStringBounds(_, _, _) => "String index out of bounds",
Self::ErrorIfGuard(_) => "If guard expects boolean expression",
Self::ErrorFor(_) => "For loop expects array or range",
@@ -128,6 +129,16 @@ impl fmt::Display for EvalAltResult {
write!(f, "{} '{}': {}", desc, filename, err)
}
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!(
f,
"Function '{}' expects {} argument(s) but {} found ({})",
@@ -142,26 +153,30 @@ impl fmt::Display for EvalAltResult {
Self::ErrorArrayBounds(_, index, pos) if *index < 0 => {
write!(f, "{}: {} < 0 ({})", desc, index, pos)
}
- Self::ErrorArrayBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos),
+ Self::ErrorArrayBounds(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!(
f,
- "Array index {} is out of bounds: only {} element{} in the array ({})",
- index,
- max,
- if *max > 1 { "s" } else { "" },
- pos
+ "Array index {} is out of bounds: only {} elements in the array ({})",
+ index, max, pos
),
Self::ErrorStringBounds(_, index, pos) if *index < 0 => {
write!(f, "{}: {} < 0 ({})", desc, index, pos)
}
- Self::ErrorStringBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos),
+ Self::ErrorStringBounds(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!(
f,
- "String index {} is out of bounds: only {} character{} in the string ({})",
- index,
- max,
- if *max > 1 { "s" } else { "" },
- pos
+ "String index {} is out of bounds: only {} characters in the string ({})",
+ index, max, pos
),
}
}
diff --git a/src/scope.rs b/src/scope.rs
index d097f422..ca3ec4d9 100644
--- a/src/scope.rs
+++ b/src/scope.rs
@@ -1,6 +1,7 @@
//! Module that defines the `Scope` type representing a function call-stack scope.
use crate::any::{Any, Dynamic};
+
use std::borrow::Cow;
/// A type containing information about current scope.
diff --git a/tests/bool_op.rs b/tests/bool_op.rs
index 7c1654bb..a7960476 100644
--- a/tests/bool_op.rs
+++ b/tests/bool_op.rs
@@ -39,10 +39,9 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> {
assert_eq!(
engine.eval::(
r"
- fn this() { true }
- fn that() { 9/0 }
+ let this = true;
- this() || that();
+ this || { throw; };
"
)?,
true
@@ -51,10 +50,9 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> {
assert_eq!(
engine.eval::(
r"
- fn this() { false }
- fn that() { 9/0 }
+ let this = false;
- this() && that();
+ this && { throw; };
"
)?,
false
@@ -72,10 +70,9 @@ fn test_bool_op_no_short_circuit1() {
engine
.eval::(
r"
- fn this() { false }
- fn that() { 9/0 }
+ let this = true;
- this() | that();
+ this | { throw; }
"
)
.unwrap(),
@@ -92,10 +89,9 @@ fn test_bool_op_no_short_circuit2() {
engine
.eval::(
r"
- fn this() { false }
- fn that() { 9/0 }
+ let this = false;
- this() & that();
+ this & { throw; }
"
)
.unwrap(),
diff --git a/tests/engine.rs b/tests/engine.rs
index a01ae916..d795b35b 100644
--- a/tests/engine.rs
+++ b/tests/engine.rs
@@ -1,4 +1,5 @@
#![cfg(not(feature = "no_stdlib"))]
+#![cfg(not(feature = "no_function"))]
use rhai::{Engine, EvalAltResult, INT};
#[test]
diff --git a/tests/internal_fn.rs b/tests/internal_fn.rs
index 09bb357e..8e38f0d9 100644
--- a/tests/internal_fn.rs
+++ b/tests/internal_fn.rs
@@ -1,3 +1,5 @@
+#![cfg(not(feature = "no_function"))]
+
use rhai::{Engine, EvalAltResult, INT};
#[test]
diff --git a/tests/math.rs b/tests/math.rs
index 4dd7fecc..e9565b39 100644
--- a/tests/math.rs
+++ b/tests/math.rs
@@ -24,6 +24,10 @@ fn test_math() -> Result<(), EvalAltResult> {
{
#[cfg(not(feature = "only_i32"))]
{
+ match engine.eval::("(-9223372036854775808).abs()") {
+ Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
+ r => panic!("should return overflow error: {:?}", r),
+ }
match engine.eval::("9223372036854775807 + 1") {
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
r => panic!("should return overflow error: {:?}", r),
diff --git a/tests/not.rs b/tests/not.rs
index 8bade80a..52913136 100644
--- a/tests/not.rs
+++ b/tests/not.rs
@@ -9,6 +9,7 @@ fn test_not() -> Result<(), EvalAltResult> {
false
);
+ #[cfg(not(feature = "no_function"))]
assert_eq!(engine.eval::("fn not(x) { !x } not(false)")?, true);
// TODO - do we allow stacking unary operators directly? e.g '!!!!!!!true'
diff --git a/tests/unary_minus.rs b/tests/unary_minus.rs
index 8f5f6013..80170498 100644
--- a/tests/unary_minus.rs
+++ b/tests/unary_minus.rs
@@ -5,7 +5,10 @@ fn test_unary_minus() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
assert_eq!(engine.eval::("let x = -5; x")?, -5);
+
+ #[cfg(not(feature = "no_function"))]
assert_eq!(engine.eval::("fn neg(x) { -x } neg(5)")?, -5);
+
assert_eq!(engine.eval::("5 - -+++--+-5")?, 0);
Ok(())