From 7c4d22d98af1856bd7d35dc322ca9adde897cc2c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 11 Mar 2020 13:28:12 +0800 Subject: [PATCH] Add `no_function` feature to disable script-defined functions. --- Cargo.toml | 18 ++++---- README.md | 99 +++++++++++++++++++++++++------------------- src/api.rs | 60 ++++++++++++++++++--------- src/engine.rs | 2 + src/parser.rs | 20 +++++++-- tests/bool_op.rs | 20 ++++----- tests/engine.rs | 1 + tests/internal_fn.rs | 2 + tests/not.rs | 1 + tests/unary_minus.rs | 3 ++ 10 files changed, 140 insertions(+), 86 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 600705e7..a5177aa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,16 +18,18 @@ include = [ num-traits = "*" [features] -#default = ["no_index", "no_float", "only_i32", "no_stdlib", "unchecked"] +#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/engine.rs b/src/engine.rs index c1130e14..62acd255 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -4,6 +4,8 @@ use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::parser::{Expr, FnDef, Position, Stmt}; use crate::result::EvalAltResult; use crate::scope::Scope; + +#[cfg(not(feature = "no_index"))] use crate::INT; use std::{ diff --git a/src/parser.rs b/src/parser.rs index cdb8bac9..5aec9d46 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -139,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> { @@ -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/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/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(())