From bcff6bfd712d3ed151abe33b50c9197a158a73ec Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 8 Apr 2020 10:19:03 +0800 Subject: [PATCH] Remove no_stdlib feature in favor of Engine::new_raw(). --- Cargo.toml | 8 +++-- README.md | 73 +++++++++++++++++++++++-------------- src/api.rs | 35 +++++++++++++++--- src/builtin.rs | 3 +- src/engine.rs | 82 ++++++++++++++++++++++++++---------------- src/lib.rs | 1 - src/parser.rs | 11 ++---- tests/arrays.rs | 43 +++++++++++----------- tests/maps.rs | 61 +++++++++++++++---------------- tests/mismatched_op.rs | 3 +- tests/stack.rs | 1 + tests/string.rs | 6 ++-- 12 files changed, 191 insertions(+), 136 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 43339787..632018f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,16 +20,14 @@ categories = [ "no-std", "embedded", "parser-implementations" ] num-traits = "*" [features] -#default = ["no_function", "no_index", "no_object", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize", "sync"] +#default = ["no_stdlib", "no_function", "no_index", "no_object", "no_float", "only_i32", "unchecked", "no_optimize", "sync"] default = [] 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 no_object = [] # no custom objects no_optimize = [] # no script optimizer -optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing 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 @@ -37,6 +35,10 @@ sync = [] # restrict to only types that implement Send + Sync # compiling for no-std no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm" ] +# 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 diff --git a/README.md b/README.md index c6ec7f48..90609c5a 100644 --- a/README.md +++ b/README.md @@ -59,26 +59,24 @@ Beware that in order to use pre-releases (e.g. alpha and beta), the exact versio Optional features ----------------- -| Feature | Description | -| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `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 not needed. | -| `no_index` | Disable arrays and indexing features if not needed. | -| `no_object` | Disable support for custom types and objects. | -| `no_float` | Disable floating-point numbers and math if not needed. | -| `no_optimize` | Disable the script optimizer. | -| `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_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, [`Engine`], [`Scope`] and `AST` are all `Send + Sync`. | +| Feature | Description | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `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 not needed. | +| `no_index` | Disable arrays and indexing features if not needed. | +| `no_object` | Disable support for custom types and objects. | +| `no_float` | Disable floating-point numbers and math if not needed. | +| `no_optimize` | Disable the script optimizer. | +| `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_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, [`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. Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language. [`unchecked`]: #optional-features -[`no_stdlib`]: #optional-features [`no_index`]: #optional-features [`no_float`]: #optional-features [`no_function`]: #optional-features @@ -160,7 +158,7 @@ Hello world [`Engine`]: #hello-world -To get going with Rhai, create an instance of the scripting engine and then call `eval`: +To get going with Rhai, create an instance of the scripting engine via `Engine::new` and then call the `eval` method: ```rust use rhai::{Engine, EvalAltResult}; @@ -171,7 +169,7 @@ fn main() -> Result<(), EvalAltResult> let result = engine.eval::("40 + 2")?; - println!("Answer: {}", result); // prints 42 + println!("Answer: {}", result); // prints 42 Ok(()) } @@ -203,7 +201,7 @@ let ast = engine.compile("40 + 2")?; for _ in 0..42 { let result: i64 = engine.eval_ast(&ast)?; - println!("Answer #{}: {}", i, result); // prints 42 +println!("Answer #{}: {}", i, result); // prints 42 } ``` @@ -253,6 +251,27 @@ let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )? // ^^ unit = tuple of zero ``` +Raw `Engine` +------------ + +[raw `Engine`]: #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: + +* the `print` and `debug` statements do nothing instead of displaying to the console (see [`print` and `debug`](#print-and-debug) below) +* the _standard library_ of utility functions is _not_ loaded by default (load it using the `register_stdlib` method). + +```rust +let mut engine = Engine::new_raw(); // Create a 'raw' Engine + +engine.register_stdlib(); // Register the standard library manually + +engine. +``` + Evaluate expressions only ------------------------- @@ -263,8 +282,8 @@ In these cases, use the `compile_expression` and `eval_expression` methods or th let result = engine.eval_expression::("2 + (10 + 10) * 2")?; ``` -When evaluation _expressions_, no control-flow statement (e.g. `if`, `while`, `for`) is not supported and will be -parse errors when encountered - not even variable assignments. +When evaluation _expressions_, no full-blown statement (e.g. `if`, `while`, `for`) - not even variable assignments - +is supported and will be considered parse errors when encountered. ```rust // The following are all syntax errors because the script is not an expression. @@ -955,7 +974,7 @@ number = -5 - +5; Numeric functions ----------------- -The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on +The following standard functions (defined in the standard library but excluded if using a [raw `Engine`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: | Function | Description | @@ -966,7 +985,7 @@ The following standard functions (defined in the standard library but excluded i Floating-point functions ------------------------ -The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on `f64` only: +The following standard functions (defined in the standard library but excluded if using a [raw `Engine`]) operate on `f64` only: | Category | Functions | | ---------------- | ------------------------------------------------------------ | @@ -996,7 +1015,7 @@ Individual characters within a Rhai string can also be replaced just as if the s In Rhai, there is also no separate concepts of `String` and `&str` as in Rust. Strings can be built up from other strings and types via the `+` operator (provided by the standard library but excluded -if [`no_stdlib`]). This is particularly useful when printing output. +if using a [raw `Engine`]). This is particularly useful when printing output. [`type_of()`] a string returns `"string"`. @@ -1044,7 +1063,7 @@ record == "Bob X. Davis: age 42 ❤\n"; 'C' in record == false; ``` -The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on strings: +The following standard functions (defined in the standard library but excluded if using a [raw `Engine`]) operate on strings: | Function | Description | | ---------- | ------------------------------------------------------------------------ | @@ -1097,7 +1116,7 @@ The Rust type of a Rhai array is `rhai::Array`. [`type_of()`] an array returns ` Arrays are disabled via the [`no_index`] feature. -The following functions (defined in the standard library but excluded if [`no_stdlib`]) operate on arrays: +The following functions (defined in the standard library but excluded if using a [raw `Engine`]) operate on arrays: | Function | Description | | ------------ | ------------------------------------------------------------------------------------- | @@ -1193,7 +1212,7 @@ The Rust type of a Rhai object map is `rhai::Map`. [`type_of()`] an object map r Object maps are disabled via the [`no_object`] feature. -The following functions (defined in the standard library but excluded if [`no_stdlib`]) operate on object maps: +The following functions (defined in the standard library but excluded if using a [raw `Engine`]) operate on object maps: | Function | Description | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | @@ -1271,7 +1290,7 @@ Comparison operators Comparing most values of the same data type work out-of-the-box for standard types supported by the system. -However, if the [`no_stdlib`] feature is turned on, comparisons can only be made between restricted system types - +However, if using a [raw `Engine`], comparisons can only be made between restricted system types - `INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`. ```rust @@ -1753,7 +1772,7 @@ An [`Engine`]'s optimization level is set via a call to `set_optimization_level` engine.set_optimization_level(rhai::OptimizationLevel::Full); ``` -If it is ever needed to _re_-optimize an `AST`, use the `optimize_ast` method. +If it is ever needed to _re_-optimize an `AST`, use the `optimize_ast` method: ```rust // Compile script to AST diff --git a/src/api.rs b/src/api.rs index 55943f24..db3a9859 100644 --- a/src/api.rs +++ b/src/api.rs @@ -387,8 +387,30 @@ impl<'e> Engine<'e> { /// # } /// ``` pub fn compile_with_scope(&self, scope: &Scope, input: &str) -> Result { + self.compile_with_scope_and_optimization_level( + scope, + input, + #[cfg(not(feature = "no_optimize"))] + self.optimization_level, + ) + } + + /// Compile a string into an `AST` using own scope at a specific optimization level. + pub(crate) fn compile_with_scope_and_optimization_level( + &self, + scope: &Scope, + input: &str, + #[cfg(not(feature = "no_optimize"))] optimization_level: OptimizationLevel, + ) -> Result { let tokens_stream = lex(input); - parse(&mut tokens_stream.peekable(), self, scope) + + parse( + &mut tokens_stream.peekable(), + self, + scope, + #[cfg(not(feature = "no_optimize"))] + optimization_level, + ) } /// Read the contents of a file into a string. @@ -800,8 +822,14 @@ impl<'e> Engine<'e> { pub fn consume_with_scope(&self, scope: &mut Scope, input: &str) -> Result<(), EvalAltResult> { let tokens_stream = lex(input); - let ast = parse(&mut tokens_stream.peekable(), self, scope) - .map_err(EvalAltResult::ErrorParsing)?; + let ast = parse( + &mut tokens_stream.peekable(), + self, + scope, + #[cfg(not(feature = "no_optimize"))] + self.optimization_level, + ) + .map_err(EvalAltResult::ErrorParsing)?; self.consume_ast_with_scope(scope, &ast) } @@ -837,7 +865,6 @@ impl<'e> Engine<'e> { /// /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { - /// # #[cfg(not(feature = "no_stdlib"))] /// # #[cfg(not(feature = "no_function"))] /// # { /// use rhai::{Engine, Scope}; diff --git a/src/builtin.rs b/src/builtin.rs index b6baed63..f63f4fd5 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -757,8 +757,7 @@ macro_rules! reg_fn2y { /// Register the built-in library. impl Engine<'_> { - #[cfg(not(feature = "no_stdlib"))] - pub(crate) fn register_stdlib(&mut self) { + pub fn register_stdlib(&mut self) { #[cfg(not(feature = "no_float"))] { // Advanced math functions diff --git a/src/engine.rs b/src/engine.rs index e24b2ee8..f351f72e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -283,28 +283,25 @@ pub struct Engine<'e> { impl Default for Engine<'_> { fn default() -> Self { - // User-friendly names for built-in types - let type_names = [ - #[cfg(not(feature = "no_index"))] - (type_name::(), "array"), - #[cfg(not(feature = "no_object"))] - (type_name::(), "map"), - (type_name::(), "string"), - (type_name::(), "dynamic"), - (type_name::(), "variant"), - ] - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect(); - // Create the new scripting Engine let mut engine = Engine { functions: None, type_iterators: None, - type_names: Some(type_names), - on_print: Some(Box::new(default_print)), // default print/debug implementations + type_names: None, + + // default print/debug implementations + #[cfg(not(feature = "no_std"))] + on_print: Some(Box::new(default_print)), + #[cfg(not(feature = "no_std"))] on_debug: Some(Box::new(default_print)), + // default print/debug implementations + #[cfg(feature = "no_std")] + on_print: None, + #[cfg(feature = "no_std")] + on_debug: None, + + // optimization level #[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "optimize_full"))] optimization_level: OptimizationLevel::Simple, @@ -316,10 +313,11 @@ impl Default for Engine<'_> { max_call_stack_depth: MAX_CALL_STACK_DEPTH, }; + engine.fill_type_names(); engine.register_core_lib(); #[cfg(not(feature = "no_stdlib"))] - engine.register_stdlib(); // Register the standard library when no_stdlib is not set + engine.register_stdlib(); engine } @@ -354,6 +352,24 @@ fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { } impl Engine<'_> { + fn fill_type_names(&mut self) { + // User-friendly names for built-in types + self.type_names = Some( + [ + #[cfg(not(feature = "no_index"))] + (type_name::(), "array"), + #[cfg(not(feature = "no_object"))] + (type_name::(), "map"), + (type_name::(), "string"), + (type_name::(), "dynamic"), + (type_name::(), "variant"), + ] + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(), + ); + } + /// Create a new `Engine` pub fn new() -> Self { // fn abc(f: F) { @@ -364,7 +380,7 @@ impl Engine<'_> { Default::default() } - /// Create a new `Engine` with minimal configurations - i.e. without pretty-print type names etc. + /// Create a new `Engine` with minimal configurations without the standard library etc. pub fn new_raw() -> Self { let mut engine = Engine { functions: None, @@ -384,11 +400,9 @@ impl Engine<'_> { max_call_stack_depth: MAX_CALL_STACK_DEPTH, }; + engine.fill_type_names(); engine.register_core_lib(); - #[cfg(not(feature = "no_stdlib"))] - engine.register_stdlib(); // Register the standard library when no_stdlib is not set - engine } @@ -1487,13 +1501,24 @@ impl Engine<'_> { })?; // Compile the script text - let mut ast = self.compile(script).map_err(EvalAltResult::ErrorParsing)?; + // No optimizations because we only run it once + let mut ast = self + .compile_with_scope_and_optimization_level( + &Scope::new(), + script, + #[cfg(not(feature = "no_optimize"))] + OptimizationLevel::None, + ) + .map_err(EvalAltResult::ErrorParsing)?; // If new functions are defined within the eval string, it is an error - if ast.1.len() > 0 { - return Err(EvalAltResult::ErrorParsing( - ParseErrorType::WrongFnDefinition.into_err(pos), - )); + #[cfg(not(feature = "no_function"))] + { + if ast.1.len() > 0 { + return Err(EvalAltResult::ErrorParsing( + ParseErrorType::WrongFnDefinition.into_err(pos), + )); + } } if let Some(lib) = fn_lib { @@ -1760,11 +1785,6 @@ impl Engine<'_> { /// Print/debug to stdout #[cfg(not(feature = "no_std"))] -#[cfg(not(feature = "no_stdlib"))] fn default_print(s: &str) { println!("{}", s); } - -/// No-op -#[cfg(any(feature = "no_std", feature = "no_stdlib"))] -fn default_print(_: &str) {} diff --git a/src/lib.rs b/src/lib.rs index dba21d7a..b2982f08 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,6 @@ //! //! | Feature | Description | //! | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -//! | `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 not needed. | //! | `no_index` | Disable arrays and indexing features if not needed. | diff --git a/src/parser.rs b/src/parser.rs index c5a1041e..569d0ac4 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -6,7 +6,7 @@ use crate::error::{LexError, ParseError, ParseErrorType}; use crate::scope::{EntryType as ScopeEntryType, Scope}; #[cfg(not(feature = "no_optimize"))] -use crate::optimize::optimize_into_ast; +use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::stdlib::{ borrow::Cow, @@ -2866,19 +2866,14 @@ pub fn parse<'a, 'e>( input: &mut Peekable>, engine: &Engine<'e>, scope: &Scope, + #[cfg(not(feature = "no_optimize"))] optimization_level: OptimizationLevel, ) -> Result { let (statements, functions) = parse_global_level(input)?; Ok( // Optimize AST #[cfg(not(feature = "no_optimize"))] - optimize_into_ast( - engine, - scope, - statements, - functions, - engine.optimization_level, - ), + optimize_into_ast(engine, scope, statements, functions, optimization_level), // // Do not optimize AST if `no_optimize` #[cfg(feature = "no_optimize")] diff --git a/tests/arrays.rs b/tests/arrays.rs index e1dc066a..33568460 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -13,42 +13,39 @@ fn test_arrays() -> Result<(), EvalAltResult> { ); assert!(engine.eval::("let y = [1, 2, 3]; 2 in y")?); - #[cfg(not(feature = "no_stdlib"))] - { - assert_eq!( - engine.eval::( - r" + assert_eq!( + engine.eval::( + r" let x = [1, 2, 3]; let y = [4, 5]; x.append(y); x.len() " - )?, - 5 - ); - assert_eq!( - engine.eval::( - r" + )?, + 5 + ); + assert_eq!( + engine.eval::( + r" let x = [1, 2, 3]; x += [4, 5]; x.len() " - )?, - 5 - ); - assert_eq!( - engine - .eval::( - r" + )?, + 5 + ); + assert_eq!( + engine + .eval::( + r" let x = [1, 2, 3]; let y = [4, 5]; x + y " - )? - .len(), - 5 - ); - } + )? + .len(), + 5 + ); Ok(()) } diff --git a/tests/maps.rs b/tests/maps.rs index c256a1a8..b979f331 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -33,42 +33,39 @@ fn test_map_indexing() -> Result<(), EvalAltResult> { assert!(engine.eval::("let y = #{a: 1, b: 2, c: 3}; 'b' in y")?); assert!(!engine.eval::(r#"let y = #{a: 1, b: 2, c: 3}; "z" in y"#)?); - #[cfg(not(feature = "no_stdlib"))] - { - assert_eq!( - engine.eval::( + assert_eq!( + engine.eval::( + r" + let x = #{a: 1, b: 2, c: 3}; + let y = #{b: 42, d: 9}; + x.mixin(y); + x.len() + x.b + " + )?, + 46 + ); + assert_eq!( + engine.eval::( + r" + let x = #{a: 1, b: 2, c: 3}; + x += #{b: 42, d: 9}; + x.len() + x.b + " + )?, + 46 + ); + assert_eq!( + engine + .eval::( r" let x = #{a: 1, b: 2, c: 3}; let y = #{b: 42, d: 9}; - x.mixin(y); - x.len() + x.b + x + y " - )?, - 46 - ); - assert_eq!( - engine.eval::( - r" - let x = #{a: 1, b: 2, c: 3}; - x += #{b: 42, d: 9}; - x.len() + x.b - " - )?, - 46 - ); - assert_eq!( - engine - .eval::( - r" - let x = #{a: 1, b: 2, c: 3}; - let y = #{b: 42, d: 9}; - x + y - " - )? - .len(), - 4 - ); - } + )? + .len(), + 4 + ); Ok(()) } diff --git a/tests/mismatched_op.rs b/tests/mismatched_op.rs index 57cbb86e..e6e6e15d 100644 --- a/tests/mismatched_op.rs +++ b/tests/mismatched_op.rs @@ -1,12 +1,11 @@ use rhai::{Engine, EvalAltResult, RegisterFn, INT}; #[test] -#[cfg(not(feature = "no_stdlib"))] fn test_mismatched_op() { let engine = Engine::new(); assert!( - matches!(engine.eval::(r#"60 + "hello""#).expect_err("expects error"), + matches!(engine.eval::(r#""hello, " + "world!""#).expect_err("expects error"), EvalAltResult::ErrorMismatchOutputType(err, _) if err == "string") ); } diff --git a/tests/stack.rs b/tests/stack.rs index 62964c9c..feee09d5 100644 --- a/tests/stack.rs +++ b/tests/stack.rs @@ -1,3 +1,4 @@ +#![cfg(not(feature = "no_function"))] use rhai::{Engine, EvalAltResult}; #[test] diff --git a/tests/string.rs b/tests/string.rs index 3999df9e..3eeb3a35 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -22,12 +22,12 @@ fn test_string() -> Result<(), EvalAltResult> { #[cfg(not(feature = "no_stdlib"))] assert_eq!(engine.eval::(r#""foo" + 123"#)?, "foo123"); + #[cfg(not(feature = "no_stdlib"))] + assert_eq!(engine.eval::("(42).to_string()")?, "42"); + #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_stdlib"))] assert_eq!(engine.eval::(r#""foo" + 123.4556"#)?, "foo123.4556"); - #[cfg(not(feature = "no_stdlib"))] - assert_eq!(engine.eval::("(42).to_string()")?, "42"); - Ok(()) }