diff --git a/CHANGELOG.md b/CHANGELOG.md index d2285af7..75cea982 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ Bug fixes * BLOB's no longer panic when accessed with an out-of-bounds index. +New features +------------ + +* New options for `Engine` which allows disabling `if`-expressions, `switch`-expressions, statement expressions and/or anonymous functions. + Enhancements ------------ diff --git a/src/api/limits.rs b/src/api/limits.rs index af512494..f95ddc8f 100644 --- a/src/api/limits.rs +++ b/src/api/limits.rs @@ -7,8 +7,7 @@ use std::prelude::v1::*; use std::num::{NonZeroU64, NonZeroUsize}; -/// _(internals)_ A type containing all the limits imposed by the [`Engine`]. -/// Exported under the `internals` feature only. +/// A type containing all the limits imposed by the [`Engine`]. /// /// Not available under `unchecked`. #[derive(Debug, Clone, Eq, PartialEq, Hash)] diff --git a/src/api/mod.rs b/src/api/mod.rs index b2c7c8f4..831c9944 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -12,6 +12,8 @@ pub mod register; pub mod call_fn; +pub mod options; + pub mod limits; pub mod events; diff --git a/src/api/options.rs b/src/api/options.rs new file mode 100644 index 00000000..24785236 --- /dev/null +++ b/src/api/options.rs @@ -0,0 +1,87 @@ +//! Settings for [`Engine`]'s language options. + +use crate::Engine; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +/// A type containing all language options for the [`Engine`]. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct LanguageOptions { + /// Is `if`-expression allowed? + pub allow_if_expr: bool, + /// Is `switch` expression allowed? + pub allow_switch_expr: bool, + /// Is statement-expression allowed? + pub allow_stmt_expr: bool, + /// Is anonymous function allowed? + #[cfg(not(feature = "no_function"))] + pub allow_anonymous_fn: bool, +} + +impl LanguageOptions { + /// Create a new [`Options`] with default values. + #[inline(always)] + pub const fn new() -> Self { + Self { + allow_if_expr: true, + allow_switch_expr: true, + allow_stmt_expr: true, + allow_anonymous_fn: true, + } + } +} + +impl Default for LanguageOptions { + fn default() -> Self { + Self::new() + } +} + +impl Engine { + /// Is `if`-expression allowed? + #[inline(always)] + pub fn allow_if_expression(&self) -> bool { + self.options.allow_if_expr + } + /// Set whether `if`-expression is allowed. + #[inline(always)] + pub fn set_allow_if_expression(&mut self, enable: bool) { + self.options.allow_if_expr = enable; + } + /// Is `switch` expression allowed? + #[inline(always)] + pub fn allow_switch_expression(&self) -> bool { + self.options.allow_switch_expr + } + /// Set whether `switch` expression is allowed. + #[inline(always)] + pub fn set_allow_switch_expression(&mut self, enable: bool) { + self.options.allow_switch_expr = enable; + } + /// Is statement-expression allowed? + #[inline(always)] + pub fn allow_statement_expression(&self) -> bool { + self.options.allow_stmt_expr + } + /// Set whether statement-expression is allowed. + #[inline(always)] + pub fn set_allow_statement_expression(&mut self, enable: bool) { + self.options.allow_stmt_expr = enable; + } + /// Is anonymous function allowed? + /// + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + pub fn allow_anonymous_fn(&self) -> bool { + self.options.allow_anonymous_fn + } + /// Set whether anonymous function is allowed. + /// + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + pub fn set_allow_anonymous_fn(&mut self, enable: bool) { + self.options.allow_anonymous_fn = enable; + } +} diff --git a/src/engine.rs b/src/engine.rs index aea6638a..a6f9469b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -973,6 +973,9 @@ pub struct Engine { #[cfg(not(feature = "no_optimize"))] pub(crate) optimization_level: crate::OptimizationLevel, + /// Language options. + pub(crate) options: crate::api::options::LanguageOptions, + /// Max limits. #[cfg(not(feature = "unchecked"))] pub(crate) limits: crate::api::limits::Limits, @@ -1096,6 +1099,8 @@ impl Engine { #[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel::default(), + options: crate::api::options::LanguageOptions::new(), + #[cfg(not(feature = "unchecked"))] limits: crate::api::limits::Limits::new(), }; diff --git a/src/lib.rs b/src/lib.rs index 41514a52..077245c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -214,10 +214,6 @@ pub use ast::FloatWrapper; #[cfg(feature = "internals")] pub use engine::{EvalState, FnResolutionCache, FnResolutionCacheEntry, Imports}; -#[cfg(feature = "internals")] -#[cfg(not(feature = "unchecked"))] -pub use api::limits::Limits; - #[cfg(feature = "internals")] pub use module::NamespaceRef; diff --git a/src/parser.rs b/src/parser.rs index 26f2d625..ce819648 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,5 +1,6 @@ //! Main module defining the lexer and parser. +use crate::api::options::LanguageOptions; use crate::ast::{ BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*, @@ -227,12 +228,14 @@ struct ParseSettings { is_function_scope: bool, /// Is the current position inside a loop? is_breakable: bool, + /// Default language options. + default_options: LanguageOptions, /// Is anonymous function allowed? #[cfg(not(feature = "no_function"))] allow_anonymous_fn: bool, - /// Is if-expression allowed? + /// Is `if`-expression allowed? allow_if_expr: bool, - /// Is switch expression allowed? + /// Is `switch` expression allowed? allow_switch_expr: bool, /// Is statement-expression allowed? allow_stmt_expr: bool, @@ -1167,15 +1170,15 @@ fn parse_primary( } let settings = ParseSettings { - allow_if_expr: true, - allow_switch_expr: true, - allow_stmt_expr: true, - allow_anonymous_fn: true, + allow_if_expr: settings.default_options.allow_if_expr, + allow_switch_expr: settings.default_options.allow_switch_expr, + allow_stmt_expr: settings.default_options.allow_stmt_expr, + allow_anonymous_fn: settings.default_options.allow_anonymous_fn, is_global: false, is_function_scope: true, is_breakable: false, level: 0, - pos: settings.pos, + ..settings }; let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?; @@ -2795,15 +2798,16 @@ fn parse_stmt( } let settings = ParseSettings { - allow_if_expr: true, - allow_switch_expr: true, - allow_stmt_expr: true, - allow_anonymous_fn: true, + allow_if_expr: settings.default_options.allow_if_expr, + allow_switch_expr: settings.default_options.allow_switch_expr, + allow_stmt_expr: settings.default_options.allow_stmt_expr, + allow_anonymous_fn: settings.default_options.allow_anonymous_fn, is_global: false, is_function_scope: true, is_breakable: false, level: 0, pos, + ..settings }; let func = parse_fn( @@ -3232,6 +3236,7 @@ impl Engine { let mut functions = BTreeMap::new(); let settings = ParseSettings { + default_options: self.options, allow_if_expr: false, allow_switch_expr: false, allow_stmt_expr: false, @@ -3288,11 +3293,12 @@ impl Engine { while !input.peek().expect(NEVER_ENDS).0.is_eof() { let settings = ParseSettings { - allow_if_expr: true, - allow_switch_expr: true, - allow_stmt_expr: true, + default_options: self.options, + allow_if_expr: self.options.allow_if_expr, + allow_switch_expr: self.options.allow_switch_expr, + allow_stmt_expr: self.options.allow_stmt_expr, #[cfg(not(feature = "no_function"))] - allow_anonymous_fn: true, + allow_anonymous_fn: self.options.allow_anonymous_fn, is_global: true, #[cfg(not(feature = "no_function"))] is_function_scope: false, diff --git a/src/tests.rs b/src/tests.rs index 50e82788..ce0a89b3 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -15,8 +15,10 @@ fn check_struct_sizes() { assert_eq!(size_of::(), if PACKED { 8 } else { 16 }); assert_eq!(size_of::>(), if PACKED { 8 } else { 16 }); - #[cfg(not(feature = "no_position"))] - assert_eq!(size_of::(), 4); + assert_eq!( + size_of::(), + if cfg!(feature = "no_position") { 0 } else { 4 } + ); assert_eq!(size_of::(), 16); assert_eq!(size_of::>(), 16); assert_eq!(size_of::(), 32); diff --git a/tests/options.rs b/tests/options.rs new file mode 100644 index 00000000..26de92ab --- /dev/null +++ b/tests/options.rs @@ -0,0 +1,26 @@ +use rhai::{Engine, EvalAltResult}; + +#[test] +fn test_options() -> Result<(), Box> { + let mut engine = Engine::new(); + + engine.compile("let x = if y { z } else { w };")?; + + engine.set_allow_if_expression(false); + + assert!(engine.compile("let x = if y { z } else { w };").is_err()); + + engine.compile("let x = { let z = 0; z + 1 };")?; + + engine.set_allow_statement_expression(false); + + assert!(engine.compile("let x = { let z = 0; z + 1 };").is_err()); + + engine.compile("let x = || 42;")?; + + engine.set_allow_anonymous_fn(false); + + assert!(engine.compile("let x = || 42;").is_err()); + + Ok(()) +}