Complete built-in operators.
This commit is contained in:
parent
b49e1e199a
commit
d56634cac7
18
Cargo.toml
18
Cargo.toml
@ -20,26 +20,22 @@ categories = [ "no-std", "embedded", "parser-implementations" ]
|
||||
num-traits = { version = "0.2.11", default-features = false }
|
||||
|
||||
[features]
|
||||
#default = ["no_stdlib", "no_function", "no_index", "no_object", "no_module", "no_float", "only_i32", "unchecked", "no_optimize", "sync"]
|
||||
#default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"]
|
||||
default = []
|
||||
unchecked = [] # unchecked arithmetic
|
||||
no_index = [] # no arrays and indexing
|
||||
no_float = [] # no floating-point
|
||||
no_function = [] # no script-defined functions
|
||||
no_object = [] # no custom objects
|
||||
sync = [] # restrict to only types that implement Send + Sync
|
||||
no_optimize = [] # no script optimizer
|
||||
no_module = [] # no modules
|
||||
no_float = [] # no floating-point
|
||||
only_i32 = [] # set INT=i32 (useful for 32-bit systems)
|
||||
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
|
||||
sync = [] # restrict to only types that implement Send + Sync
|
||||
no_index = [] # no arrays and indexing
|
||||
no_object = [] # no custom objects
|
||||
no_function = [] # no script-defined functions
|
||||
no_module = [] # no modules
|
||||
|
||||
# compiling for no-std
|
||||
no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ]
|
||||
|
||||
# other developer features
|
||||
no_stdlib = [] # do not register the standard library
|
||||
optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
|
60
README.md
60
README.md
@ -69,18 +69,18 @@ Optional features
|
||||
-----------------
|
||||
|
||||
| Feature | Description |
|
||||
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `unchecked` | Exclude arithmetic checking (such as over-flows and division by zero), stack depth limit and operations count limit. Beware that a bad script may panic the entire system! |
|
||||
| `no_function` | Disable script-defined functions. |
|
||||
| `no_index` | Disable [arrays] and indexing features. |
|
||||
| `no_object` | Disable support for custom types and object maps. |
|
||||
| `no_float` | Disable floating-point numbers and math. |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `unchecked` | Disable arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit. Beware that a bad script may panic the entire system! |
|
||||
| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and `AST`, are all `Send + Sync`. |
|
||||
| `no_optimize` | Disable the script optimizer. |
|
||||
| `no_module` | Disable modules. |
|
||||
| `no_float` | Disable floating-point numbers and math. |
|
||||
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
|
||||
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
|
||||
| `no_index` | Disable [arrays] and indexing features. |
|
||||
| `no_object` | Disable support for custom types and [object maps]. |
|
||||
| `no_function` | Disable script-defined functions. |
|
||||
| `no_module` | Disable loading modules. |
|
||||
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||
| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and `AST`, are all `Send + Sync`. |
|
||||
|
||||
By default, Rhai includes all the standard functionalities in a small, tight package.
|
||||
Most features are here to opt-**out** of certain functionalities that are not needed.
|
||||
@ -88,16 +88,16 @@ Excluding unneeded functionalities can result in smaller, faster builds
|
||||
as well as more control over what a script can (or cannot) do.
|
||||
|
||||
[`unchecked`]: #optional-features
|
||||
[`no_index`]: #optional-features
|
||||
[`no_float`]: #optional-features
|
||||
[`no_function`]: #optional-features
|
||||
[`no_object`]: #optional-features
|
||||
[`sync`]: #optional-features
|
||||
[`no_optimize`]: #optional-features
|
||||
[`no_module`]: #optional-features
|
||||
[`no_float`]: #optional-features
|
||||
[`only_i32`]: #optional-features
|
||||
[`only_i64`]: #optional-features
|
||||
[`no_index`]: #optional-features
|
||||
[`no_object`]: #optional-features
|
||||
[`no_function`]: #optional-features
|
||||
[`no_module`]: #optional-features
|
||||
[`no_std`]: #optional-features
|
||||
[`sync`]: #optional-features
|
||||
|
||||
### Performance builds
|
||||
|
||||
@ -133,9 +133,9 @@ Omitting arrays (`no_index`) yields the most code-size savings, followed by floa
|
||||
(`no_float`), checked arithmetic (`unchecked`) and finally object maps and custom types (`no_object`).
|
||||
Disable script-defined functions (`no_function`) only when the feature is not needed because code size savings is minimal.
|
||||
|
||||
[`Engine::new_raw`](#raw-engine) creates a _raw_ engine which does not register _any_ utility functions.
|
||||
This makes the scripting language quite useless as even basic arithmetic operators are not supported.
|
||||
Selectively include the necessary functionalities by loading specific [packages] to minimize the footprint.
|
||||
[`Engine::new_raw`](#raw-engine) creates a _raw_ engine.
|
||||
A _raw_ engine supports, out of the box, only a very restricted set of basic arithmetic and logical operators.
|
||||
Selectively include other necessary functionalities by loading specific [packages] to minimize the footprint.
|
||||
Packages are sharable (even across threads via the [`sync`] feature), so they only have to be created once.
|
||||
|
||||
Related
|
||||
@ -376,7 +376,8 @@ Raw `Engine`
|
||||
`Engine::new` creates a scripting [`Engine`] with common functionalities (e.g. printing to the console via `print`).
|
||||
In many controlled embedded environments, however, these are not needed.
|
||||
|
||||
Use `Engine::new_raw` to create a _raw_ `Engine`, in which _nothing_ is added, not even basic arithmetic and logic operators!
|
||||
Use `Engine::new_raw` to create a _raw_ `Engine`, in which only a minimal set of basic arithmetic and logical operators
|
||||
are supported.
|
||||
|
||||
### Packages
|
||||
|
||||
@ -401,10 +402,10 @@ engine.load_package(package.get()); // load the package manually. 'g
|
||||
The follow packages are available:
|
||||
|
||||
| Package | Description | In `CorePackage` | In `StandardPackage` |
|
||||
| ---------------------- | ----------------------------------------------- | :--------------: | :------------------: |
|
||||
| `ArithmeticPackage` | Arithmetic operators (e.g. `+`, `-`, `*`, `/`) | Yes | Yes |
|
||||
| ---------------------- | -------------------------------------------------------------------------- | :--------------: | :------------------: |
|
||||
| `ArithmeticPackage` | Arithmetic operators (e.g. `+`, `-`, `*`, `/`) for different numeric types | Yes | Yes |
|
||||
| `BasicIteratorPackage` | Numeric ranges (e.g. `range(1, 10)`) | Yes | Yes |
|
||||
| `LogicPackage` | Logic and comparison operators (e.g. `==`, `>`) | Yes | Yes |
|
||||
| `LogicPackage` | Logical and comparison operators (e.g. `==`, `>`) | Yes | Yes |
|
||||
| `BasicStringPackage` | Basic string functions | Yes | Yes |
|
||||
| `BasicTimePackage` | Basic time functions (e.g. [timestamps]) | Yes | Yes |
|
||||
| `MoreStringPackage` | Additional string functions | No | Yes |
|
||||
@ -795,7 +796,7 @@ let result: i64 = engine.eval("1 + 1.0"); // prints 2.0 (normally an e
|
||||
```
|
||||
|
||||
Use operator overloading for custom types (described below) only.
|
||||
Be very careful when overloading built-in operators because script writers expect standard operators to behave in a
|
||||
Be very careful when overloading built-in operators because script authors expect standard operators to behave in a
|
||||
consistent and predictable manner, and will be annoyed if a calculation for '`+`' turns into a subtraction, for example.
|
||||
|
||||
Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`].
|
||||
@ -2345,6 +2346,10 @@ engine.set_max_modules(5); // allow loading only up to 5 module
|
||||
engine.set_max_modules(0); // allow unlimited modules
|
||||
```
|
||||
|
||||
A script attempting to load more than the maximum number of modules will terminate with an error result.
|
||||
This check can be disabled via the [`unchecked`] feature for higher performance
|
||||
(but higher risks as well).
|
||||
|
||||
### Maximum call stack depth
|
||||
|
||||
Rhai by default limits function calls to a maximum depth of 128 levels (16 levels in debug build).
|
||||
@ -2366,6 +2371,8 @@ engine.set_max_call_levels(0); // allow no function calls at all (m
|
||||
```
|
||||
|
||||
A script exceeding the maximum call stack depth will terminate with an error result.
|
||||
This check can be disabled via the [`unchecked`] feature for higher performance
|
||||
(but higher risks as well).
|
||||
|
||||
### Maximum statement depth
|
||||
|
||||
@ -2409,15 +2416,17 @@ Make sure that `C x ( 5 + F ) + S` layered calls do not cause a stack overflow,
|
||||
A script exceeding the maximum nesting depths will terminate with a parsing error.
|
||||
The malignant `AST` will not be able to get past parsing in the first place.
|
||||
|
||||
The limits can be disabled via the [`unchecked`] feature for higher performance
|
||||
This check can be disabled via the [`unchecked`] feature for higher performance
|
||||
(but higher risks as well).
|
||||
|
||||
### Checked arithmetic
|
||||
|
||||
By default, all arithmetic calculations in Rhai are _checked_, meaning that the script terminates
|
||||
with an error whenever it detects a numeric over-flow/under-flow condition or an invalid
|
||||
floating-point operation, instead of crashing the entire system. This checking can be turned off
|
||||
via the [`unchecked`] feature for higher performance (but higher risks as well).
|
||||
floating-point operation, instead of crashing the entire system.
|
||||
|
||||
This checking can be turned off via the [`unchecked`] feature for higher performance
|
||||
(but higher risks as well).
|
||||
|
||||
### Blocking access to external data
|
||||
|
||||
@ -2433,7 +2442,6 @@ engine.register_get("add", add); // configure 'engine'
|
||||
let engine = engine; // shadow the variable so that 'engine' is now immutable
|
||||
```
|
||||
|
||||
|
||||
Script optimization
|
||||
===================
|
||||
|
||||
|
11
RELEASES.md
11
RELEASES.md
@ -16,6 +16,8 @@ Breaking changes
|
||||
* The `RegisterDynamicFn` trait is merged into the `RegisterResutlFn` trait which now always returns
|
||||
`Result<Dynamic, Box<EvalAltResult>>`.
|
||||
* Default maximum limit on levels of nested function calls is fine-tuned and set to a different value.
|
||||
* Some operator functions are now built in (see _Speed enhancements_ below), so they are available even
|
||||
when under `Engine::new_raw`.
|
||||
|
||||
New features
|
||||
------------
|
||||
@ -24,6 +26,15 @@ New features
|
||||
* New `EvalPackage` to disable `eval`.
|
||||
* More benchmarks.
|
||||
|
||||
Speed enhancements
|
||||
------------------
|
||||
|
||||
* Common operators (e.g. `+`, `>`, `==`) now call into highly efficient built-in implementations for standard types
|
||||
(i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and some `String`) if not overridden by a registered function.
|
||||
This yields a 5-10% speed benefit depending on script operator usage.
|
||||
* Implementations of common operators for standard types are removed from the `ArithmeticPackage` and `LogicPackage`
|
||||
(and therefore the `CorePackage`) because they are now always available, even under `Engine::new_raw`.
|
||||
|
||||
|
||||
Version 0.14.1
|
||||
==============
|
||||
|
@ -392,13 +392,8 @@ impl Default for Engine {
|
||||
optimization_level: OptimizationLevel::None,
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
#[cfg(not(feature = "optimize_full"))]
|
||||
optimization_level: OptimizationLevel::Simple,
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
#[cfg(feature = "optimize_full")]
|
||||
optimization_level: OptimizationLevel::Full,
|
||||
|
||||
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
||||
max_expr_depth: MAX_EXPR_DEPTH,
|
||||
max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH,
|
||||
@ -406,10 +401,6 @@ impl Default for Engine {
|
||||
max_modules: u64::MAX,
|
||||
};
|
||||
|
||||
#[cfg(feature = "no_stdlib")]
|
||||
engine.load_package(CorePackage::new().get());
|
||||
|
||||
#[cfg(not(feature = "no_stdlib"))]
|
||||
engine.load_package(StandardPackage::new().get());
|
||||
|
||||
engine
|
||||
@ -534,13 +525,8 @@ impl Engine {
|
||||
optimization_level: OptimizationLevel::None,
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
#[cfg(not(feature = "optimize_full"))]
|
||||
optimization_level: OptimizationLevel::Simple,
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
#[cfg(feature = "optimize_full")]
|
||||
optimization_level: OptimizationLevel::Full,
|
||||
|
||||
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
||||
max_expr_depth: MAX_EXPR_DEPTH,
|
||||
max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH,
|
||||
@ -635,13 +621,15 @@ impl Engine {
|
||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||
self.inc_operations(state, pos)?;
|
||||
|
||||
let native_only = hashes.1 == 0;
|
||||
|
||||
// Check for stack overflow
|
||||
if level > self.max_call_stack_depth {
|
||||
return Err(Box::new(EvalAltResult::ErrorStackOverflow(pos)));
|
||||
}
|
||||
|
||||
// First search in script-defined functions (can override built-in)
|
||||
if hashes.1 > 0 {
|
||||
if !native_only {
|
||||
if let Some(fn_def) = state.get_function(hashes.1) {
|
||||
let (result, state2) =
|
||||
self.call_script_fn(scope, *state, fn_name, fn_def, args, pos, level)?;
|
||||
@ -710,8 +698,8 @@ impl Engine {
|
||||
}
|
||||
|
||||
// If it is a 2-operand operator, see if it is built in
|
||||
if args.len() == 2 && args[0].type_id() == args[1].type_id() {
|
||||
match run_builtin_op(fn_name, args[0], args[1])? {
|
||||
if native_only && args.len() == 2 && args[0].type_id() == args[1].type_id() {
|
||||
match run_builtin_binary_op(fn_name, args[0], args[1])? {
|
||||
Some(v) => return Ok((v, false)),
|
||||
None => (),
|
||||
}
|
||||
@ -1949,8 +1937,8 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build in certain common operator implementations to avoid the cost of searching through the functions space.
|
||||
fn run_builtin_op(
|
||||
/// Build in common binary operator implementations to avoid the cost of calling a registered function.
|
||||
fn run_builtin_binary_op(
|
||||
op: &str,
|
||||
x: &Dynamic,
|
||||
y: &Dynamic,
|
||||
|
@ -710,8 +710,7 @@ fn parse_call_expr<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
state: &mut ParseState,
|
||||
id: String,
|
||||
#[cfg(not(feature = "no_module"))] mut modules: Option<Box<ModuleRef>>,
|
||||
#[cfg(feature = "no_module")] modules: Option<ModuleRef>,
|
||||
mut modules: Option<Box<ModuleRef>>,
|
||||
begin: Position,
|
||||
level: usize,
|
||||
allow_stmt_expr: bool,
|
||||
@ -753,12 +752,13 @@ fn parse_call_expr<'a>(
|
||||
let qualifiers = modules.iter().map(|(m, _)| m.as_str());
|
||||
calc_fn_hash(qualifiers, &id, 0, empty())
|
||||
} else {
|
||||
// Qualifiers (none) + function name + no parameters.
|
||||
calc_fn_hash(empty(), &id, 0, empty())
|
||||
}
|
||||
};
|
||||
// Qualifiers (none) + function name + no parameters.
|
||||
#[cfg(feature = "no_module")]
|
||||
let hash_fn_def = calc_fn_hash(empty(), &id, empty());
|
||||
let hash_fn_def = calc_fn_hash(empty(), &id, 0, empty());
|
||||
|
||||
return Ok(Expr::FnCall(Box::new((
|
||||
(id.into(), false, begin),
|
||||
@ -794,12 +794,13 @@ fn parse_call_expr<'a>(
|
||||
let qualifiers = modules.iter().map(|(m, _)| m.as_str());
|
||||
calc_fn_hash(qualifiers, &id, args.len(), empty())
|
||||
} else {
|
||||
// Qualifiers (none) + function name + number of arguments.
|
||||
calc_fn_hash(empty(), &id, args.len(), empty())
|
||||
}
|
||||
};
|
||||
// Qualifiers (none) + function name + dummy parameter types (one for each parameter).
|
||||
// Qualifiers (none) + function name + number of arguments.
|
||||
#[cfg(feature = "no_module")]
|
||||
let hash_fn_def = calc_fn_hash(empty(), &id, args_iter);
|
||||
let hash_fn_def = calc_fn_hash(empty(), &id, args.len(), empty());
|
||||
|
||||
return Ok(Expr::FnCall(Box::new((
|
||||
(id.into(), false, begin),
|
||||
|
@ -80,6 +80,8 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
|
||||
42
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
engine.set_max_modules(5);
|
||||
|
||||
assert!(matches!(
|
||||
@ -137,6 +139,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
|
||||
}
|
||||
"#,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
#![cfg(not(feature = "unchecked"))]
|
||||
use rhai::{Engine, EvalAltResult, ParseErrorType};
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
fn test_stack_overflow_fn_calls() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
@ -73,6 +74,7 @@ fn test_stack_overflow_parsing() -> Result<(), Box<EvalAltResult>> {
|
||||
err if err.error_type() == &ParseErrorType::ExprTooDeep
|
||||
));
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
engine.compile("fn abc(x) { x + 1 }")?;
|
||||
|
||||
Ok(())
|
||||
|
Loading…
Reference in New Issue
Block a user