Add no_function feature to disable script-defined functions.

This commit is contained in:
Stephen Chung 2020-03-11 13:28:12 +08:00
parent 047f064cd1
commit 7c4d22d98a
10 changed files with 140 additions and 86 deletions

View File

@ -18,16 +18,18 @@ include = [
num-traits = "*" num-traits = "*"
[features] [features]
#default = ["no_index", "no_float", "only_i32", "no_stdlib", "unchecked"] #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

@ -4,6 +4,8 @@ use crate::any::{Any, AnyExt, Dynamic, Variant};
use crate::parser::{Expr, FnDef, Position, Stmt}; use crate::parser::{Expr, FnDef, Position, Stmt};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::Scope; use crate::scope::Scope;
#[cfg(not(feature = "no_index"))]
use crate::INT; use crate::INT;
use std::{ use std::{

View File

@ -139,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> {
@ -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

@ -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

@ -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(())