From ef6c6ea6d2dae55e0f853496af506bafefb203b6 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 29 Mar 2020 17:15:12 +0800 Subject: [PATCH] Add `no_object` feature to disable objects. --- Cargo.toml | 3 ++- README.md | 31 ++++++++++++++++++++++------ examples/arrays_and_structs.rs | 5 +++++ examples/custom_types_and_methods.rs | 4 ++++ src/api.rs | 7 ++++++- src/engine.rs | 6 ++++++ src/optimize.rs | 1 + src/parser.rs | 13 ++++++++---- tests/arrays.rs | 1 + tests/expressions.rs | 1 + tests/float.rs | 1 + tests/get_set.rs | 2 ++ tests/math.rs | 6 +++--- tests/method_call.rs | 2 ++ tests/mismatched_op.rs | 1 + tests/side_effects.rs | 2 ++ 16 files changed, 71 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d98dc9ca..063abd0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,13 +20,14 @@ categories = [ "no-std", "embedded", "parser-implementations" ] num-traits = "0.2.11" [features] -#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize"] +#default = ["no_function", "no_index", "no_object", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize"] 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) diff --git a/README.md b/README.md index 922b0969..e99847c0 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ Optional features | `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`. | @@ -76,6 +77,7 @@ Excluding unneeded functionalities can result in smaller, faster builds as well [`no_index`]: #optional-features [`no_float`]: #optional-features [`no_function`]: #optional-features +[`no_object`]: #optional-features [`no_optimize`]: #optional-features [`only_i32`]: #optional-features [`only_i64`]: #optional-features @@ -294,7 +296,7 @@ type_of('c') == "char"; type_of(42) == "i64"; let x = 123; -x.type_of(); // error - 'type_of' cannot use postfix notation +x.type_of(); // error - 'type_of' cannot use method-call style type_of(x) == "i64"; x = 99.999; @@ -496,6 +498,7 @@ fn main() -> Result<(), EvalAltResult> ``` All custom types must implement `Clone`. This allows the [`Engine`] to pass by value. +You can turn off support for custom types via the [`no_object`] feature. ```rust #[derive(Clone)] @@ -522,7 +525,8 @@ let mut engine = Engine::new(); engine.register_type::(); ``` -To use methods and functions with the [`Engine`], we need to register them. There are some convenience functions to help with this. Below I register update and new with the [`Engine`]. +To use methods and functions with the [`Engine`], we need to register them. There are some convenience functions to help with this. +Below I register update and new with the [`Engine`]. *Note: [`Engine`] follows the convention that methods use a `&mut` first parameter so that invoking methods can update the value in memory.* @@ -531,7 +535,8 @@ engine.register_fn("update", TestStruct::update); // registers 'update(&mut ts engine.register_fn("new_ts", TestStruct::new); // registers 'new' ``` -Finally, we call our script. The script can see the function and method we registered earlier. We need to get the result back out from script land just as before, this time casting to our custom struct type. +Finally, we call our script. The script can see the function and method we registered earlier. +We need to get the result back out from script land just as before, this time casting to our custom struct type. ```rust let result = engine.eval::("let x = new_ts(); x.update(); x")?; @@ -539,7 +544,8 @@ let result = engine.eval::("let x = new_ts(); x.update(); x")?; println!("result: {}", result.field); // prints 42 ``` -In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method-call on that type because internally they are the same thing: methods on a type is implemented as a functions taking an first argument. +In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method-call on that type because internally they are the same thing: +methods on a type is implemented as a functions taking an first argument. ```rust fn foo(ts: &mut TestStruct) -> i64 { @@ -553,7 +559,15 @@ let result = engine.eval::("let x = new_ts(); x.foo()")?; println!("result: {}", result); // prints 1 ``` -[`type_of()`] works fine with custom types and returns the name of the type. If `register_type_with_name` is used to register the custom type +If the [`no_object`] feature is turned on, however, the _method_ style of function calls (i.e. calling a function as an object-method) is no longer supported. + +```rust +// Below is a syntax error under 'no_object' because 'len' cannot be called in method style. +let result = engine.eval::("let x = [1, 2, 3]; x.len()")?; +``` + +[`type_of()`] works fine with custom types and returns the name of the type. +If `register_type_with_name` is used to register the custom type with a special "pretty-print" name, [`type_of()`] will return that name instead. ```rust @@ -605,6 +619,9 @@ let result = engine.eval::("let a = new_ts(); a.xyz = 42; a.xyz")?; println!("Answer: {}", result); // prints 42 ``` +Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set` and `register_get_set` +are not available when the [`no_object`] feature is turned on. + Initializing and maintaining state --------------------------------- @@ -1326,6 +1343,8 @@ a.update(); // method call update(a); // this works, but 'a' is unchanged because only a COPY of 'a' is passed to 'update' by VALUE ``` +Custom types, properties and methods can be disabled via the [`no_object`] feature. + `print` and `debug` ------------------- @@ -1606,7 +1625,7 @@ eval("{ let z = y }"); // to keep a variable local, use a statement blo print("z = " + z); // error - variable 'z' not found -"print(42)".eval(); // nope - just like 'type_of' postfix notation doesn't work +"print(42)".eval(); // nope - just like 'type_of', method-call style doesn't work ``` Script segments passed to `eval` execute inside the current [`Scope`], so they can access and modify _everything_, diff --git a/examples/arrays_and_structs.rs b/examples/arrays_and_structs.rs index 8ea7fcf0..5e604120 100644 --- a/examples/arrays_and_structs.rs +++ b/examples/arrays_and_structs.rs @@ -15,6 +15,8 @@ impl TestStruct { } } +#[cfg(not(feature = "no_index"))] +#[cfg(not(feature = "no_object"))] fn main() { let mut engine = Engine::new(); @@ -32,3 +34,6 @@ fn main() { engine.eval::("let x = [new_ts()]; x[0].update(); x[0]") ); } + +#[cfg(any(feature = "no_index", feature = "no_object"))] +fn main() {} diff --git a/examples/custom_types_and_methods.rs b/examples/custom_types_and_methods.rs index bd20159e..c912520d 100644 --- a/examples/custom_types_and_methods.rs +++ b/examples/custom_types_and_methods.rs @@ -15,6 +15,7 @@ impl TestStruct { } } +#[cfg(not(feature = "no_object"))] fn main() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); @@ -29,3 +30,6 @@ fn main() -> Result<(), EvalAltResult> { Ok(()) } + +#[cfg(feature = "no_object")] +fn main() {} diff --git a/src/api.rs b/src/api.rs index db92aed7..50a93f8c 100644 --- a/src/api.rs +++ b/src/api.rs @@ -75,6 +75,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` + #[cfg(not(feature = "no_object"))] pub fn register_type(&mut self) { self.register_type_with_name::(type_name::()); } @@ -122,6 +123,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` + #[cfg(not(feature = "no_object"))] pub fn register_type_with_name(&mut self, name: &str) { // Add the pretty-print type name into the map self.type_names @@ -173,6 +175,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` + #[cfg(not(feature = "no_object"))] pub fn register_get( &mut self, name: &str, @@ -215,6 +218,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` + #[cfg(not(feature = "no_object"))] pub fn register_set( &mut self, name: &str, @@ -262,6 +266,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` + #[cfg(not(feature = "no_object"))] pub fn register_get_set( &mut self, name: &str, @@ -864,7 +869,7 @@ impl<'e> Engine<'e> { /// let mut engine = Engine::new(); /// /// // Set 'retain_functions' in 'consume' to keep the function definitions - /// engine.consume(true, "fn add(x, y) { x.len() + y }")?; + /// engine.consume(true, "fn add(x, y) { len(x) + y }")?; /// /// // Call the script-defined function /// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?; diff --git a/src/engine.rs b/src/engine.rs index 9ce74b9d..2d3ab8a7 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -344,6 +344,7 @@ impl Engine<'_> { /// /// Otherwise, if `src` is `Some`, then it holds a name and index into `scope`; using `get_mut` on /// `scope` can retrieve a mutable reference to the variable's value to use as `this`. + #[cfg(not(feature = "no_object"))] fn get_dot_val_helper( &mut self, scope: &mut Scope, @@ -487,6 +488,7 @@ impl Engine<'_> { } /// Evaluate a dot chain getter + #[cfg(not(feature = "no_object"))] fn get_dot_val( &mut self, scope: &mut Scope, @@ -741,6 +743,7 @@ impl Engine<'_> { } /// Chain-evaluate a dot setter + #[cfg(not(feature = "no_object"))] fn set_dot_val_helper( &mut self, scope: &mut Scope, @@ -858,6 +861,7 @@ impl Engine<'_> { } // Evaluate a dot chain setter + #[cfg(not(feature = "no_object"))] fn set_dot_val( &mut self, scope: &mut Scope, @@ -1021,6 +1025,7 @@ impl Engine<'_> { } // dot_lhs.dot_rhs = rhs + #[cfg(not(feature = "no_object"))] Expr::Dot(dot_lhs, dot_rhs, _) => self.set_dot_val( scope, dot_lhs, @@ -1041,6 +1046,7 @@ impl Engine<'_> { } } + #[cfg(not(feature = "no_object"))] Expr::Dot(lhs, rhs, _) => self.get_dot_val(scope, lhs, rhs, level), #[cfg(not(feature = "no_index"))] diff --git a/src/optimize.rs b/src/optimize.rs index c8dc32f7..8d0bcc0f 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -334,6 +334,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { expr => Expr::Assignment(id, Box::new(optimize_expr(expr, state)), pos), }, // lhs.rhs + #[cfg(not(feature = "no_object"))] Expr::Dot(lhs, rhs, pos) => Expr::Dot( Box::new(optimize_expr(*lhs, state)), Box::new(optimize_expr(*rhs, state)), diff --git a/src/parser.rs b/src/parser.rs index 4dc64971..f6807eeb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -357,6 +357,7 @@ pub enum Expr { /// expr = expr Assignment(Box, Box, Position), /// lhs.rhs + #[cfg(not(feature = "no_object"))] Dot(Box, Box, Position), /// expr[expr] #[cfg(not(feature = "no_index"))] @@ -443,10 +444,12 @@ impl Expr { | Expr::False(pos) | Expr::Unit(pos) => *pos, - Expr::Assignment(expr, _, _) - | Expr::Dot(expr, _, _) - | Expr::And(expr, _) - | Expr::Or(expr, _) => expr.position(), + Expr::Assignment(expr, _, _) | Expr::And(expr, _) | Expr::Or(expr, _) => { + expr.position() + } + + #[cfg(not(feature = "no_object"))] + Expr::Dot(expr, _, _) => expr.position(), #[cfg(not(feature = "no_float"))] Expr::FloatConstant(_, pos) => *pos, @@ -1767,6 +1770,7 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result match dot_lhs.as_ref() { // var.dot_rhs Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false), @@ -1875,6 +1879,7 @@ fn parse_binary_op<'a>( Token::PlusAssign => parse_op_assignment("+", current_lhs, rhs, pos)?, Token::MinusAssign => parse_op_assignment("-", current_lhs, rhs, pos)?, + #[cfg(not(feature = "no_object"))] Token::Period => { fn change_var_to_property(expr: Expr) -> Expr { match expr { diff --git a/tests/arrays.rs b/tests/arrays.rs index 710d0cf7..5cfec145 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -16,6 +16,7 @@ fn test_arrays() -> Result<(), EvalAltResult> { } #[test] +#[cfg(not(feature = "no_object"))] fn test_array_with_structs() -> Result<(), EvalAltResult> { #[derive(Clone)] struct TestStruct { diff --git a/tests/expressions.rs b/tests/expressions.rs index 3d42ed98..9023f97c 100644 --- a/tests/expressions.rs +++ b/tests/expressions.rs @@ -27,6 +27,7 @@ fn test_expressions() -> Result<(), EvalAltResult> { /// This example taken from https://github.com/jonathandturner/rhai/issues/115 #[test] +#[cfg(not(feature = "no_object"))] fn test_expressions_eval() -> Result<(), EvalAltResult> { #[derive(Debug, Clone)] struct AGENT { diff --git a/tests/float.rs b/tests/float.rs index 688c095f..20349098 100644 --- a/tests/float.rs +++ b/tests/float.rs @@ -21,6 +21,7 @@ fn test_float() -> Result<(), EvalAltResult> { } #[test] +#[cfg(not(feature = "no_object"))] fn struct_with_float() -> Result<(), EvalAltResult> { #[derive(Clone)] struct TestStruct { diff --git a/tests/get_set.rs b/tests/get_set.rs index a1c64d25..9f63a365 100644 --- a/tests/get_set.rs +++ b/tests/get_set.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "no_object"))] + use rhai::{Engine, EvalAltResult, RegisterFn, INT}; #[test] diff --git a/tests/math.rs b/tests/math.rs index 1bfdf521..24181c5f 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -12,12 +12,12 @@ fn test_math() -> Result<(), EvalAltResult> { #[cfg(not(feature = "only_i32"))] assert_eq!( - engine.eval::("(-9223372036854775807).abs()")?, + engine.eval::("abs(-9223372036854775807)")?, 9_223_372_036_854_775_807 ); #[cfg(feature = "only_i32")] - assert_eq!(engine.eval::("(-2147483647).abs()")?, 2147483647); + assert_eq!(engine.eval::("abs(-2147483647)")?, 2147483647); // Overflow/underflow/division-by-zero errors #[cfg(not(feature = "unchecked"))] @@ -26,7 +26,7 @@ fn test_math() -> Result<(), EvalAltResult> { { assert!(matches!( engine - .eval::("(-9223372036854775808).abs()") + .eval::("abs(-9223372036854775808)") .expect_err("expects negation overflow"), EvalAltResult::ErrorArithmetic(_, _) )); diff --git a/tests/method_call.rs b/tests/method_call.rs index 9e217306..6043345a 100644 --- a/tests/method_call.rs +++ b/tests/method_call.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "no_object"))] + use rhai::{Engine, EvalAltResult, RegisterFn, INT}; #[test] diff --git a/tests/mismatched_op.rs b/tests/mismatched_op.rs index 3fcc02c6..5629f6cd 100644 --- a/tests/mismatched_op.rs +++ b/tests/mismatched_op.rs @@ -12,6 +12,7 @@ fn test_mismatched_op() { } #[test] +#[cfg(not(feature = "no_object"))] fn test_mismatched_op_custom_type() { #[derive(Clone)] struct TestStruct { diff --git a/tests/side_effects.rs b/tests/side_effects.rs index 69b82e64..54b25432 100644 --- a/tests/side_effects.rs +++ b/tests/side_effects.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "no_object"))] + ///! This test simulates an external command object that is driven by a script. use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT}; use std::cell::RefCell;