diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8e97e90a..445f8946 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,7 @@ on: push: branches: - master - - bug-fixes + - v1.1-fixes pull_request: {} jobs: @@ -19,22 +19,22 @@ jobs: flags: - "" - "--features metadata,serde,internals" - - "--features unchecked" - - "--features sync" - - "--features no_position" - - "--features no_optimize" - - "--features no_float" + - "--features unchecked,serde,metadata,internals" + - "--features sync,serde,metadata,internals" + - "--features no_position,serde,metadata,internals" + - "--features no_optimize,serde,metadata,internals" + - "--features no_float,serde,metadata,internals" - "--features f32_float,serde,metadata,internals" - - "--features decimal" + - "--features decimal,serde,metadata,internals" - "--features no_float,decimal" - "--tests --features only_i32,serde,metadata,internals" - - "--features only_i64" - - "--features no_index" - - "--features no_object" - - "--features no_function" - - "--features no_module" - - "--features no_closure" - - "--features unicode-xid-ident" + - "--features only_i64,serde,metadata,internals" + - "--features no_index,serde,metadata,internals" + - "--features no_object,serde,metadata,internals" + - "--features no_function,serde,metadata,internals" + - "--features no_module,serde,metadata,internals" + - "--features no_closure,serde,metadata,internals" + - "--features unicode-xid-ident,serde,metadata,internals" - "--features sync,no_function,no_float,no_position,no_optimize,no_module,no_closure,metadata,serde,unchecked" - "--features no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,unchecked" toolchain: [stable] diff --git a/CHANGELOG.md b/CHANGELOG.md index 59875fda..a833a3b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ New features ------------ * `#[cfg(...)]` attributes can now be put directly on plugin functions or function defined in a plugin module. +* A custom syntax parser can now return a symbol starting with `$$` to inform the implementation function which syntax variant was actually parsed. Enhancements ------------ @@ -18,6 +19,7 @@ Enhancements * Array adds a `sort` method with no parameters which sorts homogeneous arrays of built-in comparable types (e.g. `INT`). * Inlining is disabled for error-path functions because errors are exceptional and scripts usually fail completely when an error is encountered. * The `optimize` module is completely eliminated under `no_optimize`, which should yield smaller code size. +* Add `NativeCallContext::position` to return the position of the function call. Deprecated API's ---------------- @@ -26,6 +28,37 @@ Deprecated API's * `From` for `Result>` is deprecated so it will no longer be possible to do `EvalAltResult::ErrorXXXXX.into()` to convert to a `Result`; instead, `Err(EvalAltResult:ErrorXXXXX.into())` must be used. Code is clearer if errors are explicitly wrapped in `Err`. +Version 1.1.3 +============= + +Bug fixes +--------- + +* Reverses a regression on string `+` operations. +* The global namespace is now searched before packages, which is the correct behavior. + + +Version 1.1.2 +============= + +Bug fixes +--------- + +* `0.0` now prints correctly (used to print `0e0`). +* Unary operators are now properly recognized as an expression statement. + + +Version 1.1.1 +============= + +Bug fixes +--------- + +* Assignment to indexing expression with dot expressions inside no longer cause a compilation error. +* The `no_module` and `internals` features now work together without a compilation error. +* String literal operations (such as `"hello" + ", world"`) now optimizes correctly. + + Version 1.1.0 ============= diff --git a/codegen/ui_tests/rhai_mod_unknown_type.stderr b/codegen/ui_tests/rhai_mod_unknown_type.stderr index 2a68d792..2cc0103f 100644 --- a/codegen/ui_tests/rhai_mod_unknown_type.stderr +++ b/codegen/ui_tests/rhai_mod_unknown_type.stderr @@ -13,9 +13,9 @@ help: a struct with a similar name exists | ~~~~~ help: consider importing one of these items | +11 | use core::fmt::Pointer; + | 11 | use std::fmt::Pointer; | 11 | use syn::__private::fmt::Pointer; | -11 | use core::fmt::Pointer; - | diff --git a/examples/serde.rs b/examples/serde.rs index bf40499d..f5554889 100644 --- a/examples/serde.rs +++ b/examples/serde.rs @@ -1,10 +1,11 @@ -#[cfg(not(feature = "serde"))] +#[cfg(any(not(feature = "serde"), feature = "no_object"))] fn main() { println!("This example requires the 'serde' feature to run."); println!("Try: cargo run --features serde --example serde"); } #[cfg(feature = "serde")] +#[cfg(not(feature = "no_object"))] fn main() { example::ser(); println!(); @@ -12,6 +13,7 @@ fn main() { } #[cfg(feature = "serde")] +#[cfg(not(feature = "no_object"))] mod example { use rhai::serde::{from_dynamic, to_dynamic}; use rhai::{Dynamic, Engine, Map}; diff --git a/src/ast.rs b/src/ast.rs index 3cf2b739..18aa6b7b 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -227,11 +227,11 @@ impl AST { /// Create an empty [`AST`]. #[inline] #[must_use] - pub(crate) fn empty() -> Self { + pub fn empty() -> Self { Self { source: None, - body: Default::default(), - functions: Default::default(), + body: StmtBlock::empty(), + functions: Module::new().into(), #[cfg(not(feature = "no_module"))] resolver: None, } @@ -399,11 +399,11 @@ impl AST { &self, filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, ) -> Self { - let mut functions: Module = Default::default(); + let mut functions = Module::new(); functions.merge_filtered(&self.functions, &filter); Self { source: self.source.clone(), - body: Default::default(), + body: StmtBlock::empty(), functions: functions.into(), #[cfg(not(feature = "no_module"))] resolver: self.resolver.clone(), @@ -417,7 +417,7 @@ impl AST { Self { source: self.source.clone(), body: self.body.clone(), - functions: Default::default(), + functions: Module::new().into(), #[cfg(not(feature = "no_module"))] resolver: self.resolver.clone(), } @@ -599,7 +599,7 @@ impl AST { } (false, true) => body.clone(), (true, false) => other.body.clone(), - (true, true) => Default::default(), + (true, true) => StmtBlock::empty(), }; let source = other.source.clone().or_else(|| self.source.clone()); @@ -717,7 +717,7 @@ impl AST { /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_module"))] + #[allow(dead_code)] #[inline] pub(crate) fn iter_fn_def(&self) -> impl Iterator { self.functions @@ -740,13 +740,13 @@ impl AST { #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn clear_functions(&mut self) -> &mut Self { - self.functions = Default::default(); + self.functions = Module::new().into(); self } /// Clear all statements in the [`AST`], leaving only function definitions. #[inline(always)] pub fn clear_statements(&mut self) -> &mut Self { - self.body = Default::default(); + self.body = StmtBlock::empty(); self } /// Recursively walk the [`AST`], including function bodies (if any). @@ -755,7 +755,7 @@ impl AST { #[cfg(not(feature = "no_module"))] #[inline] pub(crate) fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool { - let path = &mut Default::default(); + let path = &mut Vec::new(); for stmt in self.statements() { if !stmt.walk(path, on_node) { @@ -777,7 +777,7 @@ impl AST { #[cfg(feature = "internals")] #[inline] pub fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool { - let path = &mut Default::default(); + let path = &mut Vec::new(); for stmt in self.statements() { if !stmt.walk(path, on_node) { @@ -899,6 +899,12 @@ impl StmtBlock { statements.shrink_to_fit(); Self(statements, pos) } + /// Create an empty [`StmtBlock`]. + #[inline(always)] + #[must_use] + pub fn empty() -> Self { + Default::default() + } /// Is this statements block empty? #[inline(always)] #[must_use] @@ -1205,7 +1211,7 @@ impl From for StmtBlock { fn from(stmt: Stmt) -> Self { match stmt { Stmt::Block(mut block, pos) => Self(block.iter_mut().map(mem::take).collect(), pos), - Stmt::Noop(pos) => Self(Default::default(), pos), + Stmt::Noop(pos) => Self(StaticVec::new(), pos), _ => { let pos = stmt.position(); Self(vec![stmt].into(), pos) @@ -1565,7 +1571,7 @@ impl Stmt { #[derive(Debug, Clone, Hash)] pub struct CustomExpr { /// List of keywords. - pub keywords: StaticVec, + pub inputs: StaticVec, /// Is the current [`Scope`][crate::Scope] possibly modified by this custom statement /// (e.g. introducing a new variable)? pub scope_may_be_changed: bool, @@ -1826,7 +1832,9 @@ impl fmt::Debug for FloatWrapper { impl> fmt::Display for FloatWrapper { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let abs = self.0.abs(); - if abs > Self::MAX_NATURAL_FLOAT_FOR_DISPLAY.into() + if abs.fract().is_zero() { + f.write_str("0.0") + } else if abs > Self::MAX_NATURAL_FLOAT_FOR_DISPLAY.into() || abs < Self::MIN_NATURAL_FLOAT_FOR_DISPLAY.into() { write!(f, "{:e}", self.0) @@ -2004,7 +2012,7 @@ impl fmt::Debug for Expr { Self::Variable(i, _, x) => { f.write_str("Variable(")?; if let Some((_, ref namespace)) = x.1 { - write!(f, "{}", namespace)? + write!(f, "{}{}", namespace, Token::DoubleColon.literal_syntax())? } f.write_str(&x.2)?; if let Some(n) = i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) { @@ -2367,7 +2375,7 @@ impl Expr { } } Self::Custom(x, _) => { - for e in &x.keywords { + for e in &x.inputs { if !e.walk(path, on_node) { return false; } diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs index 62dc3869..ddbdbd92 100644 --- a/src/bin/rhai-repl.rs +++ b/src/bin/rhai-repl.rs @@ -114,7 +114,7 @@ fn main() { .map_err(|err| err.into()) .and_then(|mut ast| { ast.set_source(filename.to_string_lossy().to_string()); - Module::eval_ast_as_new(Default::default(), &ast, &engine) + Module::eval_ast_as_new(Scope::new(), &ast, &engine) }) { Err(err) => { let filename = filename.to_string_lossy(); @@ -163,9 +163,9 @@ fn main() { // REPL loop let mut input = String::new(); - let mut main_ast: AST = Default::default(); - let mut ast_u: AST = Default::default(); - let mut ast: AST = Default::default(); + let mut main_ast = AST::empty(); + let mut ast_u = AST::empty(); + let mut ast = AST::empty(); 'main_loop: loop { print!("rhai-repl> "); diff --git a/src/custom_syntax.rs b/src/custom_syntax.rs index 7f98ae18..9de6af3f 100644 --- a/src/custom_syntax.rs +++ b/src/custom_syntax.rs @@ -33,6 +33,8 @@ pub mod markers { pub const CUSTOM_SYNTAX_MARKER_FLOAT: &str = "$float$"; /// Special marker for matching a boolean value. pub const CUSTOM_SYNTAX_MARKER_BOOL: &str = "$bool$"; + /// Special marker for identifying the custom syntax variant. + pub const CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT: &str = "$$"; } /// A general expression evaluation trait object. diff --git a/src/dynamic.rs b/src/dynamic.rs index 76d99ad9..ae530593 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -283,14 +283,11 @@ enum DynamicWriteLockInner<'d, T: Clone> { /// A simple mutable reference to a non-shared value. Reference(&'d mut T), - /// A write guard to a shared [`RefCell`][std::cell::RefCell]. + /// A write guard to a shared value. + /// + /// Not available under `no_closure`. #[cfg(not(feature = "no_closure"))] - #[cfg(not(feature = "sync"))] - Guard(std::cell::RefMut<'d, Dynamic>), - /// A write guard to a shared [`RwLock`][std::sync::RwLock]. - #[cfg(not(feature = "no_closure"))] - #[cfg(feature = "sync")] - Guard(std::sync::RwLockWriteGuard<'d, Dynamic>), + Guard(crate::fn_native::LockGuard<'d, Dynamic>), } impl<'d, T: Any + Clone> Deref for DynamicWriteLock<'d, T> { @@ -1593,10 +1590,7 @@ impl Dynamic { match self.0 { #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, _, _) => { - #[cfg(not(feature = "sync"))] - let value = cell.borrow_mut(); - #[cfg(feature = "sync")] - let value = cell.write().unwrap(); + let value = crate::fn_native::shared_write_lock(cell); if (*value).type_id() != TypeId::of::() && TypeId::of::() != TypeId::of::() diff --git a/src/engine.rs b/src/engine.rs index b5349485..19f6b9ad 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -40,7 +40,7 @@ use crate::ast::FnCallHashes; pub type Precedence = NonZeroU8; -/// _(internals)_ A stack of imported [modules][Module]. +/// _(internals)_ A stack of imported [modules][Module] plus mutable runtime global states. /// Exported under the `internals` feature only. /// /// # Volatile Data Structure @@ -49,17 +49,38 @@ pub type Precedence = NonZeroU8; // // # Implementation Notes // -// We cannot use Cow here because `eval` may load a [module][Module] and -// the module name will live beyond the AST of the eval script text. -// The best we can do is a shared reference. -// // This implementation splits the module names from the shared modules to improve data locality. // Most usage will be looking up a particular key from the list and then getting the module that // corresponds to that key. -#[derive(Clone, Default)] +#[derive(Clone)] pub struct Imports { + /// Stack of module names. + // + // # Implementation Notes + // + // We cannot use Cow here because `eval` may load a [module][Module] and + // the module name will live beyond the AST of the eval script text. keys: StaticVec, + /// Stack of imported modules. modules: StaticVec>, + /// Source of the current context. + pub source: Option, + /// Number of operations performed. + pub num_operations: u64, + /// Number of modules loaded. + pub num_modules: usize, + /// Function call hashes to FN_IDX_GET and FN_IDX_SET. + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + fn_hash_indexing: (u64, u64), + /// Embedded module resolver. + /// + /// Not available under `no_module`. + #[cfg(not(feature = "no_module"))] + pub embedded_module_resolver: Option>, + /// Cache of globally-defined constants. + #[cfg(not(feature = "no_module"))] + #[cfg(not(feature = "no_function"))] + global_constants: Option>>>, } impl Imports { @@ -70,6 +91,16 @@ impl Imports { Self { keys: StaticVec::new(), modules: StaticVec::new(), + source: None, + num_operations: 0, + num_modules: 0, + #[cfg(not(feature = "no_module"))] + embedded_module_resolver: None, + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + fn_hash_indexing: (0, 0), + #[cfg(not(feature = "no_module"))] + #[cfg(not(feature = "no_function"))] + global_constants: None, } } /// Get the length of this stack of imported [modules][Module]. @@ -175,6 +206,61 @@ impl Imports { .rev() .find_map(|m| m.get_qualified_iter(id)) } + /// Get a mutable reference to the cache of globally-defined constants. + #[cfg(not(feature = "no_module"))] + #[cfg(not(feature = "no_function"))] + #[must_use] + pub(crate) fn global_constants_mut<'a>( + &'a mut self, + ) -> Option> + 'a> { + if let Some(ref global_constants) = self.global_constants { + Some(crate::fn_native::shared_write_lock(global_constants)) + } else { + None + } + } + /// Set a constant into the cache of globally-defined constants. + #[cfg(not(feature = "no_module"))] + #[cfg(not(feature = "no_function"))] + pub(crate) fn set_global_constant(&mut self, name: &str, value: Dynamic) { + if self.global_constants.is_none() { + let dict: crate::Locked<_> = BTreeMap::new().into(); + self.global_constants = Some(dict.into()); + } + + crate::fn_native::shared_write_lock( + self.global_constants + .as_mut() + .expect("`global_constants` is `Some`"), + ) + .insert(name.into(), value); + } + /// Get the pre-calculated index getter hash. + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + #[must_use] + pub(crate) fn fn_hash_idx_get(&mut self) -> u64 { + if self.fn_hash_indexing != (0, 0) { + self.fn_hash_indexing.0 + } else { + let n1 = crate::calc_fn_hash(FN_IDX_GET, 2); + let n2 = crate::calc_fn_hash(FN_IDX_SET, 3); + self.fn_hash_indexing = (n1, n2); + n1 + } + } + /// Get the pre-calculated index setter hash. + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + #[must_use] + pub(crate) fn fn_hash_idx_set(&mut self) -> u64 { + if self.fn_hash_indexing != (0, 0) { + self.fn_hash_indexing.1 + } else { + let n1 = crate::calc_fn_hash(FN_IDX_GET, 2); + let n2 = crate::calc_fn_hash(FN_IDX_SET, 3); + self.fn_hash_indexing = (n1, n2); + n2 + } + } } impl IntoIterator for Imports { @@ -262,6 +348,7 @@ pub const KEYWORD_IS_DEF_VAR: &str = "is_def_var"; pub const KEYWORD_IS_DEF_FN: &str = "is_def_fn"; pub const KEYWORD_THIS: &str = "this"; #[cfg(not(feature = "no_function"))] +#[cfg(not(feature = "no_module"))] pub const KEYWORD_GLOBAL: &str = "global"; #[cfg(not(feature = "no_object"))] pub const FN_GET: &str = "get$"; @@ -660,10 +747,8 @@ pub type FnResolutionCache = BTreeMap>>; /// # Volatile Data Structure /// /// This type is volatile and may change. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct EvalState { - /// Source of the current context. - pub source: Option, /// Normally, access to variables are parsed with a relative offset into the [`Scope`] to avoid a lookup. /// In some situation, e.g. after running an `eval` statement, or after a custom syntax statement, /// subsequent offsets may become mis-aligned. @@ -672,18 +757,8 @@ pub struct EvalState { /// Level of the current scope. The global (root) level is zero, a new block (or function call) /// is one level higher, and so on. pub scope_level: usize, - /// Number of operations performed. - pub num_operations: u64, - /// Number of modules loaded. - pub num_modules: usize, /// Stack of function resolution caches. fn_resolution_caches: StaticVec, - /// Embedded module resolver. - #[cfg(not(feature = "no_module"))] - pub embedded_module_resolver: Option>, - /// Function call hashes to FN_IDX_GET and FN_IDX_SET - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - fn_hash_indexing: (u64, u64), } impl EvalState { @@ -692,16 +767,9 @@ impl EvalState { #[must_use] pub fn new() -> Self { Self { - source: None, always_search_scope: false, scope_level: 0, - num_operations: 0, - num_modules: 0, - #[cfg(not(feature = "no_module"))] - embedded_module_resolver: None, fn_resolution_caches: StaticVec::new(), - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - fn_hash_indexing: (0, 0), } } /// Is the state currently at global (root) level? @@ -726,7 +794,7 @@ impl EvalState { #[allow(dead_code)] #[inline(always)] pub fn push_fn_resolution_cache(&mut self) { - self.fn_resolution_caches.push(Default::default()); + self.fn_resolution_caches.push(BTreeMap::new()); } /// Remove the current function resolution cache from the stack and make the last one current. /// @@ -739,32 +807,6 @@ impl EvalState { .pop() .expect("at least one function resolution cache"); } - /// Get the pre-calculated index getter hash. - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - #[must_use] - pub fn fn_hash_idx_get(&mut self) -> u64 { - if self.fn_hash_indexing != (0, 0) { - self.fn_hash_indexing.0 - } else { - let n1 = crate::calc_fn_hash(FN_IDX_GET, 2); - let n2 = crate::calc_fn_hash(FN_IDX_SET, 3); - self.fn_hash_indexing = (n1, n2); - n1 - } - } - /// Get the pre-calculated index setter hash. - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - #[must_use] - pub fn fn_hash_idx_set(&mut self) -> u64 { - if self.fn_hash_indexing != (0, 0) { - self.fn_hash_indexing.1 - } else { - let n1 = crate::calc_fn_hash(FN_IDX_GET, 2); - let n2 = crate::calc_fn_hash(FN_IDX_SET, 3); - self.fn_hash_indexing = (n1, n2); - n2 - } - } } /// _(internals)_ A type containing all the limits imposed by the [`Engine`]. @@ -814,8 +856,8 @@ pub struct Limits { } #[cfg(not(feature = "unchecked"))] -impl Default for Limits { - fn default() -> Self { +impl Limits { + pub const fn new() -> Self { Self { #[cfg(not(feature = "no_function"))] max_call_stack_depth: MAX_CALL_STACK_DEPTH, @@ -857,7 +899,7 @@ impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, 'pt> { #[inline(always)] #[must_use] pub fn source(&self) -> Option<&str> { - self.state.source.as_ref().map(|s| s.as_str()) + self.mods.source.as_ref().map(|s| s.as_str()) } /// The current [`Scope`]. #[inline(always)] @@ -1080,17 +1122,17 @@ impl Engine { #[must_use] pub fn new_raw() -> Self { let mut engine = Self { - global_modules: Default::default(), - global_sub_modules: Default::default(), + global_modules: StaticVec::new(), + global_sub_modules: BTreeMap::new(), #[cfg(not(feature = "no_module"))] module_resolver: None, - type_names: Default::default(), - empty_string: Default::default(), - disabled_symbols: Default::default(), - custom_keywords: Default::default(), - custom_syntax: Default::default(), + type_names: BTreeMap::new(), + empty_string: ImmutableString::new(), + disabled_symbols: BTreeSet::new(), + custom_keywords: BTreeMap::new(), + custom_syntax: BTreeMap::new(), resolve_var: None, token_mapper: None, @@ -1105,7 +1147,7 @@ impl Engine { optimization_level: Default::default(), #[cfg(not(feature = "unchecked"))] - limits: Default::default(), + limits: Limits::new(), }; // Add the global namespace module @@ -1169,32 +1211,74 @@ impl Engine { Expr::Variable(Some(_), _, _) => { self.search_scope_only(scope, mods, state, lib, this_ptr, expr) } - Expr::Variable(None, var_pos, v) => match v.as_ref() { + Expr::Variable(None, _var_pos, v) => match v.as_ref() { // Normal variable access (_, None, _) => self.search_scope_only(scope, mods, state, lib, this_ptr, expr), - // Qualified variable + + // Qualified variable access + #[cfg(not(feature = "no_module"))] (_, Some((namespace, hash_var)), var_name) => { - let module = self.search_imports(mods, state, namespace).ok_or_else(|| { - EvalAltResult::ErrorModuleNotFound( - namespace[0].name.to_string(), + if let Some(module) = self.search_imports(mods, state, namespace) { + // foo:bar::baz::VARIABLE + return match module.get_qualified_var(*hash_var) { + Ok(target) => { + let mut target = target.clone(); + // Module variables are constant + target.set_access_mode(AccessMode::ReadOnly); + Ok((target.into(), *_var_pos)) + } + Err(err) => Err(match *err { + EvalAltResult::ErrorVariableNotFound(_, _) => { + EvalAltResult::ErrorVariableNotFound( + format!( + "{}{}{}", + namespace, + Token::DoubleColon.literal_syntax(), + var_name + ), + namespace[0].pos, + ) + .into() + } + _ => err.fill_position(*_var_pos), + }), + }; + } + + #[cfg(not(feature = "no_function"))] + if namespace.len() == 1 && namespace[0].name == KEYWORD_GLOBAL { + // global::VARIABLE + let global_constants = mods.global_constants_mut(); + + if let Some(mut guard) = global_constants { + if let Some(value) = guard.get_mut(var_name) { + let mut target: Target = value.clone().into(); + // Module variables are constant + target.set_access_mode(AccessMode::ReadOnly); + return Ok((target.into(), *_var_pos)); + } + } + + return Err(EvalAltResult::ErrorVariableNotFound( + format!( + "{}{}{}", + namespace, + Token::DoubleColon.literal_syntax(), + var_name + ), namespace[0].pos, ) - })?; - let target = module.get_qualified_var(*hash_var).map_err(|mut err| { - match *err { - EvalAltResult::ErrorVariableNotFound(ref mut err_name, _) => { - *err_name = format!("{}{}", namespace, var_name); - } - _ => (), - } - err.fill_position(*var_pos) - })?; + .into()); + } - // Module variables are constant - let mut target = target.clone(); - target.set_access_mode(AccessMode::ReadOnly); - Ok((target.into(), *var_pos)) + Err( + EvalAltResult::ErrorModuleNotFound(namespace.to_string(), namespace[0].pos) + .into(), + ) } + + #[cfg(feature = "no_module")] + (_, Some((_, _)), _) => unreachable!("qualified access under no_module"), }, _ => unreachable!("Expr::Variable expected, but gets {:?}", expr), } @@ -1335,7 +1419,7 @@ impl Engine { if let Some(mut new_val) = try_setter { // Try to call index setter if value is changed - let hash_set = FnCallHashes::from_native(state.fn_hash_idx_set()); + let hash_set = FnCallHashes::from_native(mods.fn_hash_idx_set()); let args = &mut [target, &mut idx_val_for_setter, &mut new_val]; if let Err(err) = self.exec_fn_call( @@ -1382,7 +1466,7 @@ impl Engine { if let Some(mut new_val) = try_setter { // Try to call index setter - let hash_set = FnCallHashes::from_native(state.fn_hash_idx_set()); + let hash_set = FnCallHashes::from_native(mods.fn_hash_idx_set()); let args = &mut [target, &mut idx_val_for_setter, &mut new_val]; self.exec_fn_call( @@ -1513,7 +1597,7 @@ impl Engine { // Try an indexer if property does not exist EvalAltResult::ErrorDotExpr(_, _) => { let args = &mut [target, &mut name.into(), &mut new_val]; - let hash_set = FnCallHashes::from_native(state.fn_hash_idx_set()); + let hash_set = FnCallHashes::from_native(mods.fn_hash_idx_set()); let pos = Position::NONE; self.exec_fn_call( @@ -1609,7 +1693,7 @@ impl Engine { let rhs_chain = match_chaining_type(rhs); let hash_get = FnCallHashes::from_native(*hash_get); let hash_set = FnCallHashes::from_native(*hash_set); - let mut arg_values = [target.as_mut(), &mut Default::default()]; + let mut arg_values = [target.as_mut(), &mut Dynamic::UNIT.clone()]; let args = &mut arg_values[..1]; // Assume getters are always pure @@ -1672,7 +1756,7 @@ impl Engine { let args = &mut [target.as_mut(), &mut name.into(), val]; let hash_set = FnCallHashes::from_native( - state.fn_hash_idx_set(), + mods.fn_hash_idx_set(), ); self.exec_fn_call( mods, state, lib, FN_IDX_SET, hash_set, args, @@ -1751,7 +1835,7 @@ impl Engine { _ => unreachable!("index or dot chain expected, but gets {:?}", expr), }; - let idx_values = &mut Default::default(); + let idx_values = &mut StaticVec::new(); self.eval_dot_index_chain_arguments( scope, mods, state, lib, this_ptr, rhs, term, chain_type, idx_values, 0, level, @@ -1761,7 +1845,7 @@ impl Engine { // id.??? or id[???] Expr::Variable(_, var_pos, x) => { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, *var_pos)?; + self.inc_operations(&mut mods.num_operations, *var_pos)?; let (mut target, _) = self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?; @@ -1812,7 +1896,7 @@ impl Engine { level: usize, ) -> Result<(), Box> { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, expr.position())?; + self.inc_operations(&mut mods.num_operations, expr.position())?; let _parent_chain_type = parent_chain_type; @@ -1938,7 +2022,7 @@ impl Engine { level: usize, ) -> Result, Box> { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, Position::NONE)?; + self.inc_operations(&mut mods.num_operations, Position::NONE)?; let mut idx = idx; let _add_if_not_found = add_if_not_found; @@ -1992,7 +2076,7 @@ impl Engine { })?; if _add_if_not_found && !map.contains_key(index.as_str()) { - map.insert(index.clone().into(), Default::default()); + map.insert(index.clone().into(), Dynamic::UNIT); } Ok(map @@ -2078,7 +2162,7 @@ impl Engine { _ if use_indexers => { let args = &mut [target, &mut idx]; - let hash_get = FnCallHashes::from_native(state.fn_hash_idx_get()); + let hash_get = FnCallHashes::from_native(mods.fn_hash_idx_get()); let idx_pos = Position::NONE; self.exec_fn_call( @@ -2111,7 +2195,7 @@ impl Engine { level: usize, ) -> RhaiResult { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, expr.position())?; + self.inc_operations(&mut mods.num_operations, expr.position())?; let result = match expr { Expr::DynamicConstant(x, _) => Ok(x.as_ref().clone()), @@ -2271,7 +2355,7 @@ impl Engine { Expr::Unit(_) => Ok(Dynamic::UNIT), Expr::Custom(custom, _) => { - let expressions: StaticVec<_> = custom.keywords.iter().map(Into::into).collect(); + let expressions: StaticVec<_> = custom.inputs.iter().map(Into::into).collect(); let key_token = custom .tokens .first() @@ -2463,7 +2547,7 @@ impl Engine { level: usize, ) -> RhaiResult { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, stmt.position())?; + self.inc_operations(&mut mods.num_operations, stmt.position())?; let result = match stmt { // No-op @@ -2496,7 +2580,7 @@ impl Engine { } #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, pos)?; + self.inc_operations(&mut mods.num_operations, pos)?; self.eval_op_assignment( mods, @@ -2649,7 +2733,7 @@ impl Engine { } } else { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, body.position())?; + self.inc_operations(&mut mods.num_operations, body.position())?; } }, @@ -2737,7 +2821,6 @@ impl Engine { }); scope.push(unsafe_cast_var_name_to_lifetime(name), ()); let index = scope.len() - 1; - state.scope_level += 1; for (x, iter_value) in func(iter_obj).enumerate() { // Increment counter @@ -2774,7 +2857,7 @@ impl Engine { } #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, statements.position())?; + self.inc_operations(&mut mods.num_operations, statements.position())?; if statements.is_empty() { continue; @@ -2794,7 +2877,6 @@ impl Engine { } } - state.scope_level -= 1; scope.rewind(orig_scope_len); Ok(Dynamic::UNIT) } else { @@ -2869,7 +2951,7 @@ impl Engine { err_map.insert("message".into(), err.to_string().into()); - if let Some(ref source) = state.source { + if let Some(ref source) = mods.source { err_map.insert("source".into(), source.as_str().into()); } @@ -2897,7 +2979,6 @@ impl Engine { }; let orig_scope_len = scope.len(); - state.scope_level += 1; err_var.as_ref().map(|Ident { name, .. }| { scope.push(unsafe_cast_var_name_to_lifetime(name), err_value) @@ -2907,7 +2988,6 @@ impl Engine { scope, mods, state, lib, this_ptr, catch_stmt, true, level, ); - state.scope_level -= 1; scope.rewind(orig_scope_len); match result { @@ -2967,25 +3047,9 @@ impl Engine { let (var_name, _alias): (Cow<'_, str>, _) = if state.is_global() { #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_module"))] if entry_type == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) { - let global = if let Some(index) = mods.find(KEYWORD_GLOBAL) { - match mods.get_mut(index).expect("index is valid") { - m if m.internal => Some(m), - _ => None, - } - } else { - // Create automatic global module - let mut global = Module::new(); - global.internal = true; - mods.push(KEYWORD_GLOBAL, global); - Some(mods.get_mut(mods.len() - 1).expect("global module exists")) - }; - - if let Some(global) = global { - Shared::get_mut(global) - .expect("global module is not shared") - .set_var(name.clone(), value.clone()); - } + mods.set_global_constant(name, value.clone()); } ( @@ -3011,7 +3075,7 @@ impl Engine { Stmt::Import(expr, export, _pos) => { // Guard against too many modules #[cfg(not(feature = "unchecked"))] - if state.num_modules >= self.max_modules() { + if mods.num_modules >= self.max_modules() { return Err(EvalAltResult::ErrorTooManyModules(*_pos).into()); } @@ -3021,10 +3085,10 @@ impl Engine { { use crate::ModuleResolver; - let source = state.source.as_ref().map(|s| s.as_str()); + let source = mods.source.as_ref().map(|s| s.as_str()); let path_pos = expr.position(); - let module = state + let module = mods .embedded_module_resolver .as_ref() .and_then(|r| match r.resolve(self, source, &path, path_pos) { @@ -3058,7 +3122,7 @@ impl Engine { } } - state.num_modules += 1; + mods.num_modules += 1; Ok(Dynamic::UNIT) } else { @@ -3238,19 +3302,19 @@ impl Engine { #[cfg(not(feature = "unchecked"))] pub(crate) fn inc_operations( &self, - state: &mut EvalState, + num_operations: &mut u64, pos: Position, ) -> Result<(), Box> { - state.num_operations += 1; + *num_operations += 1; // Guard against too many operations - if self.max_operations() > 0 && state.num_operations > self.max_operations() { + if self.max_operations() > 0 && *num_operations > self.max_operations() { return Err(EvalAltResult::ErrorTooManyOperations(pos).into()); } // Report progress - only in steps if let Some(ref progress) = self.progress { - if let Some(token) = progress(state.num_operations) { + if let Some(token) = progress(*num_operations) { // Terminate script if progress returns a termination token return Err(EvalAltResult::ErrorTerminated(token, pos).into()); } diff --git a/src/engine_api.rs b/src/engine_api.rs index 50b720d7..60a5d94b 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -26,16 +26,16 @@ impl Engine { #[allow(dead_code)] pub(crate) fn global_namespace(&self) -> &Module { self.global_modules - .last() + .first() .expect("global_modules contains at least one module") } /// Get a mutable reference to the global namespace module - /// (which is the last module in `global_modules`). + /// (which is the first module in `global_modules`). #[inline(always)] pub(crate) fn global_namespace_mut(&mut self) -> &mut Module { Shared::get_mut( self.global_modules - .last_mut() + .first_mut() .expect("global_modules contains at least one module"), ) .expect("global namespace module is never shared") @@ -894,8 +894,9 @@ impl Engine { /// modules are searched in reverse order. #[inline(always)] pub fn register_global_module(&mut self, module: Shared) -> &mut Self { - // Insert the module into the front - self.global_modules.insert(0, module); + // Insert the module into the front. + // The first module is always the global namespace. + self.global_modules.insert(1, module); self } /// Register a shared [`Module`] as a static module namespace with the [`Engine`]. @@ -1001,7 +1002,7 @@ impl Engine { /// ``` #[inline(always)] pub fn compile(&self, script: &str) -> Result { - self.compile_with_scope(&Default::default(), script) + self.compile_with_scope(&Scope::new(), script) } /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation. /// @@ -1090,7 +1091,7 @@ impl Engine { if let Some(ref module_resolver) = self.module_resolver { let mut resolver = StaticModuleResolver::new(); - let mut imports = Default::default(); + let mut imports = BTreeSet::new(); collect_imports(&ast, &resolver, &mut imports); @@ -1255,7 +1256,7 @@ impl Engine { #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[inline(always)] pub fn compile_file(&self, path: std::path::PathBuf) -> Result> { - self.compile_file_with_scope(&Default::default(), path) + self.compile_file_with_scope(&Scope::new(), path) } /// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation. /// @@ -1423,7 +1424,7 @@ impl Engine { /// ``` #[inline(always)] pub fn compile_expression(&self, script: &str) -> Result { - self.compile_expression_with_scope(&Default::default(), script) + self.compile_expression_with_scope(&Scope::new(), script) } /// Compile a string containing an expression into an [`AST`] using own scope, /// which can be used later for evaluation. @@ -1557,7 +1558,7 @@ impl Engine { /// ``` #[inline(always)] pub fn eval(&self, script: &str) -> Result> { - self.eval_with_scope(&mut Default::default(), script) + self.eval_with_scope(&mut Scope::new(), script) } /// Evaluate a string with own scope. /// @@ -1614,7 +1615,7 @@ impl Engine { &self, script: &str, ) -> Result> { - self.eval_expression_with_scope(&mut Default::default(), script) + self.eval_expression_with_scope(&mut Scope::new(), script) } /// Evaluate a string containing an expression with own scope. /// @@ -1676,7 +1677,7 @@ impl Engine { /// ``` #[inline(always)] pub fn eval_ast(&self, ast: &AST) -> Result> { - self.eval_ast_with_scope(&mut Default::default(), ast) + self.eval_ast_with_scope(&mut Scope::new(), ast) } /// Evaluate an [`AST`] with own scope. /// @@ -1713,7 +1714,7 @@ impl Engine { scope: &mut Scope, ast: &AST, ) -> Result> { - let mods = &mut Default::default(); + let mods = &mut Imports::new(); let result = self.eval_ast_with_scope_raw(scope, mods, ast, 0)?; @@ -1738,10 +1739,12 @@ impl Engine { level: usize, ) -> RhaiResult { let mut state = EvalState::new(); - state.source = ast.source_raw().cloned(); + if ast.source_raw().is_some() { + mods.source = ast.source_raw().cloned(); + } #[cfg(not(feature = "no_module"))] { - state.embedded_module_resolver = ast.resolver(); + mods.embedded_module_resolver = ast.resolver(); } let statements = ast.statements(); @@ -1778,7 +1781,7 @@ impl Engine { /// Evaluate a script, returning any error (if any). #[inline(always)] pub fn run(&self, script: &str) -> Result<(), Box> { - self.run_with_scope(&mut Default::default(), script) + self.run_with_scope(&mut Scope::new(), script) } /// Evaluate a script with own scope, returning any error (if any). #[inline] @@ -1805,7 +1808,7 @@ impl Engine { /// Evaluate an AST, returning any error (if any). #[inline(always)] pub fn run_ast(&self, ast: &AST) -> Result<(), Box> { - self.run_ast_with_scope(&mut Default::default(), ast) + self.run_ast_with_scope(&mut Scope::new(), ast) } /// Evaluate an [`AST`] with own scope, returning any error (if any). #[inline] @@ -1814,12 +1817,14 @@ impl Engine { scope: &mut Scope, ast: &AST, ) -> Result<(), Box> { - let mods = &mut Default::default(); + let mods = &mut Imports::new(); let mut state = EvalState::new(); - state.source = ast.source_raw().cloned(); + if ast.source_raw().is_some() { + mods.source = ast.source_raw().cloned(); + } #[cfg(not(feature = "no_module"))] { - state.embedded_module_resolver = ast.resolver(); + mods.embedded_module_resolver = ast.resolver(); } let statements = ast.statements(); @@ -1988,7 +1993,7 @@ impl Engine { args: &mut FnCallArgs, ) -> RhaiResult { let state = &mut EvalState::new(); - let mods = &mut Default::default(); + let mods = &mut Imports::new(); let lib = &[ast.lib()]; let statements = ast.statements(); @@ -2058,7 +2063,7 @@ impl Engine { .collect(); #[cfg(feature = "no_function")] - let lib = Default::default(); + let lib = crate::StaticVec::new(); let stmt = std::mem::take(ast.statements_mut()); crate::optimize::optimize_into_ast(self, scope, stmt, lib, optimization_level) @@ -2206,8 +2211,6 @@ impl Engine { + SendSync + 'static, ) -> &mut Self { - use std::string::ParseError; - self.token_mapper = Some(Box::new(callback)); self } diff --git a/src/error.rs b/src/error.rs index 4cf608fa..f38a971c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -122,11 +122,11 @@ impl fmt::Display for EvalAltResult { Self::ErrorInModule(s, err, _) if s.is_empty() => { write!(f, "Error in module: {}", err)? } - Self::ErrorInModule(s, err, _) => write!(f, "Error in module '{}': {}", s, err)?, + Self::ErrorInModule(s, err, _) => write!(f, "Error in module {}: {}", s, err)?, Self::ErrorFunctionNotFound(s, _) => write!(f, "Function not found: {}", s)?, Self::ErrorVariableNotFound(s, _) => write!(f, "Variable not found: {}", s)?, - Self::ErrorModuleNotFound(s, _) => write!(f, "Module not found: '{}'", s)?, + Self::ErrorModuleNotFound(s, _) => write!(f, "Module not found: {}", s)?, Self::ErrorDataRace(s, _) => { write!(f, "Data race detected when accessing variable: {}", s)? } @@ -134,7 +134,7 @@ impl fmt::Display for EvalAltResult { "" => f.write_str("Malformed dot expression"), s => f.write_str(s), }?, - Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for '{}'", s)?, + Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for {}", s)?, Self::ErrorUnboundThis(_) => f.write_str("'this' is not bound")?, Self::ErrorFor(_) => f.write_str("For loop expects a type with an iterator defined")?, Self::ErrorTooManyOperations(_) => f.write_str("Too many operations")?, diff --git a/src/fn_builtin.rs b/src/fn_builtin.rs index 1315d366..d2328b0c 100644 --- a/src/fn_builtin.rs +++ b/src/fn_builtin.rs @@ -266,6 +266,24 @@ pub fn get_builtin_binary_op_fn( _ => None, }; } + // () op string + if types_pair == (TypeId::of::<()>(), TypeId::of::()) { + return match op { + "+" => Some(|_, args| Ok(args[1].clone())), + "==" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), + "!=" => Some(|_, _| Ok(Dynamic::TRUE)), + _ => None, + }; + } + // string op () + if types_pair == (TypeId::of::(), TypeId::of::<()>()) { + return match op { + "+" => Some(|_, args| Ok(args[0].clone())), + "==" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), + "!=" => Some(|_, _| Ok(Dynamic::TRUE)), + _ => None, + }; + } // map op string #[cfg(not(feature = "no_object"))] diff --git a/src/fn_call.rs b/src/fn_call.rs index 2bafb66b..50279236 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -9,14 +9,13 @@ use crate::engine::{ use crate::fn_builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; use crate::fn_native::FnAny; use crate::module::NamespaceRef; +use crate::token::Token; use crate::{ ast::{Expr, Stmt}, + calc_fn_hash, calc_fn_params_hash, combine_hashes, fn_native::CallableFunction, - RhaiResult, -}; -use crate::{ - calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, EvalAltResult, FnPtr, - Identifier, ImmutableString, Module, ParseErrorType, Position, Scope, StaticVec, + Dynamic, Engine, EvalAltResult, FnPtr, Identifier, ImmutableString, Module, ParseErrorType, + Position, RhaiResult, Scope, StaticVec, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -34,13 +33,20 @@ pub type FnCallArgs<'a> = [&'a mut Dynamic]; /// A type that temporarily stores a mutable reference to a `Dynamic`, /// replacing it with a cloned copy. -#[derive(Debug, Default)] +#[derive(Debug)] struct ArgBackup<'a> { orig_mut: Option<&'a mut Dynamic>, value_copy: Dynamic, } impl<'a> ArgBackup<'a> { + /// Create a new `ArgBackup`. + pub fn new() -> Self { + Self { + orig_mut: None, + value_copy: Dynamic::UNIT, + } + } /// This function replaces the first argument of a method call with a clone copy. /// This is to prevent a pure function unintentionally consuming the first argument. /// @@ -135,8 +141,13 @@ impl Engine { args: &[&mut Dynamic], ) -> String { format!( - "{}{} ({})", + "{}{}{} ({})", namespace.map_or(String::new(), |ns| ns.to_string()), + if namespace.is_some() { + Token::DoubleColon.literal_syntax() + } else { + "" + }, fn_name, args.iter() .map(|a| if a.is::() { @@ -168,7 +179,7 @@ impl Engine { args: Option<&mut FnCallArgs>, allow_dynamic: bool, is_op_assignment: bool, - ) -> &'s Option> { + ) -> Option<&'s FnResolutionCacheEntry> { let mut hash = args.as_ref().map_or(hash_script, |args| { combine_hashes( hash_script, @@ -176,7 +187,7 @@ impl Engine { ) }); - &*state + let result = state .fn_resolution_cache_mut() .entry(hash) .or_insert_with(|| { @@ -284,7 +295,9 @@ impl Engine { } } } - }) + }); + + result.as_ref().map(Box::as_ref) } /// Call a native Rust function registered with the [`Engine`]. @@ -296,7 +309,7 @@ impl Engine { /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! pub(crate) fn call_native_fn( &self, - mods: &Imports, + mods: &mut Imports, state: &mut EvalState, lib: &[&Module], name: &str, @@ -307,21 +320,20 @@ impl Engine { pos: Position, ) -> Result<(Dynamic, bool), Box> { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, pos)?; + self.inc_operations(&mut mods.num_operations, pos)?; - let state_source = state.source.clone(); + let parent_source = mods.source.clone(); // Check if function access already in the cache let func = self.resolve_fn(mods, state, lib, name, hash, Some(args), true, is_op_assign); - if let Some(f) = func { - let FnResolutionCacheEntry { func, source } = f.as_ref(); + if let Some(FnResolutionCacheEntry { func, source }) = func { assert!(func.is_native()); // Calling pure function but the first argument is a reference? let mut backup: Option = None; if is_method_call && func.is_pure() && !args.is_empty() { - backup = Some(Default::default()); + backup = Some(ArgBackup::new()); backup .as_mut() .expect("`backup` is `Some`") @@ -331,16 +343,17 @@ impl Engine { // Run external function let source = source .as_ref() - .or_else(|| state_source.as_ref()) + .or_else(|| parent_source.as_ref()) .map(|s| s.as_str()); + let context = (self, name, source, &*mods, lib, pos).into(); + let result = if func.is_plugin_fn() { func.get_plugin_fn() .expect("plugin function") - .call((self, name, source, mods, lib).into(), args) + .call(context, args) } else { - let func = func.get_native_fn().expect("native function"); - func((self, name, source, mods, lib).into(), args) + func.get_native_fn().expect("native function")(context, args) }; // Restore the original reference @@ -375,7 +388,7 @@ impl Engine { pos, ) })?; - let source = state.source.as_ref().map(|s| s.as_str()); + let source = mods.source.as_ref().map(|s| s.as_str()); (debug(&text, source, pos).into(), false) } else { (Dynamic::UNIT, false) @@ -485,7 +498,7 @@ impl Engine { fn make_error( name: String, fn_def: &crate::ast::ScriptFnDef, - state: &EvalState, + mods: &Imports, err: Box, pos: Position, ) -> RhaiResult { @@ -495,7 +508,7 @@ impl Engine { .lib .as_ref() .and_then(|m| m.id().map(|id| id.to_string())) - .or_else(|| state.source.as_ref().map(|s| s.to_string())) + .or_else(|| mods.source.as_ref().map(|s| s.to_string())) .unwrap_or_default(), err, pos, @@ -504,7 +517,7 @@ impl Engine { } #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, pos)?; + self.inc_operations(&mut mods.num_operations, pos)?; if fn_def.body.is_empty() { return Ok(Dynamic::UNIT); @@ -517,9 +530,6 @@ impl Engine { return Err(EvalAltResult::ErrorStackOverflow(pos).into()); } - let orig_scope_level = state.scope_level; - state.scope_level += 1; - let prev_scope_len = scope.len(); let prev_mods_len = mods.len(); @@ -559,7 +569,6 @@ impl Engine { // Evaluate the function let body = &fn_def.body; - let result = self .eval_stmt_block(scope, mods, state, unified_lib, this_ptr, body, true, level) .or_else(|err| match *err { @@ -573,7 +582,7 @@ impl Engine { format!("{} @ '{}' < {}", name, src, fn_def.name) }; - make_error(fn_name, fn_def, state, err, pos) + make_error(fn_name, fn_def, mods, err, pos) } // System errors are passed straight-through mut err if err.is_system_exception() => { @@ -581,13 +590,12 @@ impl Engine { Err(err.into()) } // Other errors are wrapped in `ErrorInFunctionCall` - _ => make_error(fn_def.name.to_string(), fn_def, state, err, pos), + _ => make_error(fn_def.name.to_string(), fn_def, mods, err, pos), }); // Remove all local variables scope.rewind(prev_scope_len); mods.truncate(prev_mods_len); - state.scope_level = orig_scope_level; if unified { state.pop_fn_resolution_cache(); @@ -614,7 +622,7 @@ impl Engine { // First check script-defined functions let result = lib.iter().any(|&m| m.contains_fn(hash_script)) - // Then check packages + // Then check the global namespace and packages || self.global_modules.iter().any(|m| m.contains_fn(hash_script)) // Then check imported modules || mods.map_or(false, |m| m.contains_fn(hash_script)) @@ -714,11 +722,10 @@ impl Engine { let hash_script = hashes.script; #[cfg(not(feature = "no_function"))] - if let Some(f) = hash_script.and_then(|hash| { + if let Some(FnResolutionCacheEntry { func, source }) = hash_script.and_then(|hash| { self.resolve_fn(mods, state, lib, fn_name, hash, None, false, false) - .clone() + .cloned() }) { - let FnResolutionCacheEntry { func, source } = *f; // Script function call assert!(func.is_script()); @@ -728,7 +735,7 @@ impl Engine { return Ok((Dynamic::UNIT, false)); } - let scope: &mut Scope = &mut Default::default(); + let scope = &mut Scope::new(); // Move captured variables into scope #[cfg(not(feature = "no_closure"))] @@ -750,8 +757,8 @@ impl Engine { .split_first_mut() .expect("method call has first parameter"); - let orig_source = state.source.take(); - state.source = source; + let orig_source = mods.source.take(); + mods.source = source; let level = _level + 1; @@ -768,7 +775,7 @@ impl Engine { ); // Restore the original source - state.source = orig_source; + mods.source = orig_source; result? } else { @@ -776,15 +783,15 @@ impl Engine { // The first argument is a reference? let mut backup: Option = None; if is_ref_mut && !args.is_empty() { - backup = Some(Default::default()); + backup = Some(ArgBackup::new()); backup .as_mut() .expect("`backup` is `Some`") .change_first_arg_to_copy(args); } - let orig_source = state.source.take(); - state.source = source; + let orig_source = mods.source.take(); + mods.source = source; let level = _level + 1; @@ -792,7 +799,7 @@ impl Engine { self.call_script_fn(scope, mods, state, lib, &mut None, func, args, pos, level); // Restore the original source - state.source = orig_source; + mods.source = orig_source; // Restore the original reference if let Some(bk) = backup { @@ -839,14 +846,13 @@ impl Engine { &self, scope: &mut Scope, mods: &mut Imports, - state: &mut EvalState, lib: &[&Module], script: &str, _pos: Position, level: usize, ) -> RhaiResult { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, _pos)?; + self.inc_operations(&mut mods.num_operations, _pos)?; let script = script.trim(); if script.is_empty() { @@ -856,7 +862,7 @@ impl Engine { // Compile the script text // No optimizations because we only run it once let ast = self.compile_with_scope_and_optimization_level( - &Default::default(), + &Scope::new(), &[script], #[cfg(not(feature = "no_optimize"))] crate::OptimizationLevel::None, @@ -873,16 +879,7 @@ impl Engine { } // Evaluate the AST - let mut new_state = EvalState::new(); - new_state.source = state.source.clone(); - new_state.num_operations = state.num_operations; - - let result = - self.eval_global_statements(scope, mods, &mut new_state, statements, lib, level); - - state.num_operations = new_state.num_operations; - - result + self.eval_global_statements(scope, mods, &mut EvalState::new(), statements, lib, level) } /// Call a dot method. @@ -1221,7 +1218,7 @@ impl Engine { .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; let result = - self.eval_script_expr_in_place(scope, mods, state, lib, script, pos, level + 1); + self.eval_script_expr_in_place(scope, mods, lib, script, pos, level + 1); // IMPORTANT! If the eval defines new variables in the current scope, // all variable offsets from this point on will be mis-aligned. @@ -1232,8 +1229,7 @@ impl Engine { return result.map_err(|err| { EvalAltResult::ErrorInFunctionCall( KEYWORD_EVAL.to_string(), - state - .source + mods.source .as_ref() .map(Identifier::to_string) .unwrap_or_default(), @@ -1280,7 +1276,7 @@ impl Engine { } #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, _pos)?; + self.inc_operations(&mut mods.num_operations, _pos)?; #[cfg(not(feature = "no_closure"))] let target_is_shared = target.is_shared(); @@ -1346,7 +1342,7 @@ impl Engine { // func(x, ...) -> x.func(...) for index in 0..args_expr.len() { if index == 0 { - arg_values.push(Default::default()); + arg_values.push(Dynamic::UNIT); } else { let (value, _) = self.get_arg_value( scope, mods, state, lib, this_ptr, level, args_expr, constants, index, @@ -1360,7 +1356,7 @@ impl Engine { self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?; #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, _pos)?; + self.inc_operations(&mut mods.num_operations, _pos)?; #[cfg(not(feature = "no_closure"))] let target_is_shared = target.is_shared(); @@ -1393,7 +1389,7 @@ impl Engine { } let module = self.search_imports(mods, state, namespace).ok_or_else(|| { - EvalAltResult::ErrorModuleNotFound(namespace[0].name.to_string(), namespace[0].pos) + EvalAltResult::ErrorModuleNotFound(namespace.to_string(), namespace[0].pos) })?; // First search in script-defined functions (can override built-in) @@ -1401,7 +1397,7 @@ impl Engine { // Then search in Rust functions None => { #[cfg(not(feature = "unchecked"))] - self.inc_operations(state, pos)?; + self.inc_operations(&mut mods.num_operations, pos)?; let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id())); let hash_qualified_fn = combine_hashes(hash, hash_params); @@ -1427,10 +1423,10 @@ impl Engine { if fn_def.body.is_empty() { Ok(Dynamic::UNIT) } else { - let new_scope = &mut Default::default(); + let new_scope = &mut Scope::new(); let mut source = module.id_raw().cloned(); - mem::swap(&mut state.source, &mut source); + mem::swap(&mut mods.source, &mut source); let level = level + 1; @@ -1438,23 +1434,25 @@ impl Engine { new_scope, mods, state, lib, &mut None, fn_def, &mut args, pos, level, ); - state.source = source; + mods.source = source; result } } - Some(f) if f.is_plugin_fn() => f - .get_plugin_fn() - .expect("plugin function") - .clone() - .call((self, fn_name, module.id(), &*mods, lib).into(), &mut args) - .map_err(|err| err.fill_position(pos)), + Some(f) if f.is_plugin_fn() => { + let context = (self, fn_name, module.id(), &*mods, lib, pos).into(); + f.get_plugin_fn() + .expect("plugin function") + .clone() + .call(context, &mut args) + .map_err(|err| err.fill_position(pos)) + } Some(f) if f.is_native() => { let func = f.get_native_fn().expect("native function"); - func((self, fn_name, module.id(), &*mods, lib).into(), &mut args) - .map_err(|err| err.fill_position(pos)) + let context = (self, fn_name, module.id(), &*mods, lib, pos).into(); + func(context, &mut args).map_err(|err| err.fill_position(pos)) } Some(f) => unreachable!("unknown function type: {:?}", f), diff --git a/src/fn_native.rs b/src/fn_native.rs index ac5ad4d5..9d8dab30 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,7 +1,7 @@ //! Module defining interfaces to native-Rust functions. use crate::ast::{FnAccess, FnCallHashes}; -use crate::engine::Imports; +use crate::engine::{EvalState, Imports}; use crate::fn_call::FnCallArgs; use crate::plugin::PluginFunction; use crate::token::{Token, TokenizeState}; @@ -34,18 +34,25 @@ pub use std::rc::Rc as Shared; pub use std::sync::Arc as Shared; /// Synchronized shared object. -/// -/// Not available under `no_closure`. -#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] +#[allow(dead_code)] pub use std::cell::RefCell as Locked; + +/// Lock guard for synchronized shared object. +#[cfg(not(feature = "sync"))] +#[allow(dead_code)] +pub type LockGuard<'a, T> = std::cell::RefMut<'a, T>; + /// Synchronized shared object. -/// -/// Not available under `no_closure`. -#[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] +#[allow(dead_code)] pub use std::sync::RwLock as Locked; +/// Lock guard for synchronized shared object. +#[cfg(feature = "sync")] +#[allow(dead_code)] +pub type LockGuard<'a, T> = std::sync::RwLockWriteGuard<'a, T>; + /// Context of a native Rust function call. #[derive(Debug)] pub struct NativeCallContext<'a> { @@ -54,19 +61,37 @@ pub struct NativeCallContext<'a> { source: Option<&'a str>, mods: Option<&'a Imports>, lib: &'a [&'a Module], + pos: Position, } impl<'a, M: AsRef<[&'a Module]> + ?Sized> - From<(&'a Engine, &'a str, Option<&'a str>, &'a Imports, &'a M)> for NativeCallContext<'a> + From<( + &'a Engine, + &'a str, + Option<&'a str>, + &'a Imports, + &'a M, + Position, + )> for NativeCallContext<'a> { #[inline(always)] - fn from(value: (&'a Engine, &'a str, Option<&'a str>, &'a Imports, &'a M)) -> Self { + fn from( + value: ( + &'a Engine, + &'a str, + Option<&'a str>, + &'a Imports, + &'a M, + Position, + ), + ) -> Self { Self { engine: value.0, fn_name: value.1, source: value.2, mods: Some(value.3), lib: value.4.as_ref(), + pos: value.5, } } } @@ -82,6 +107,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized> From<(&'a Engine, &'a str, &'a M)> source: None, mods: None, lib: value.2.as_ref(), + pos: Position::NONE, } } } @@ -97,6 +123,7 @@ impl<'a> NativeCallContext<'a> { source: None, mods: None, lib, + pos: Position::NONE, } } /// _(internals)_ Create a new [`NativeCallContext`]. @@ -113,6 +140,7 @@ impl<'a> NativeCallContext<'a> { source: Option<&'a str>, imports: &'a Imports, lib: &'a [&Module], + pos: Position, ) -> Self { Self { engine, @@ -120,6 +148,7 @@ impl<'a> NativeCallContext<'a> { source, mods: Some(imports), lib, + pos, } } /// The current [`Engine`]. @@ -134,6 +163,12 @@ impl<'a> NativeCallContext<'a> { pub const fn fn_name(&self) -> &str { self.fn_name } + /// [Position][`Position`] of the function call. + #[inline(always)] + #[must_use] + pub const fn position(&self) -> Position { + self.pos + } /// The current source. #[inline(always)] #[must_use] @@ -214,8 +249,8 @@ impl<'a> NativeCallContext<'a> { self.engine() .exec_fn_call( - &mut self.mods.cloned().unwrap_or_default(), - &mut Default::default(), + &mut self.mods.cloned().unwrap_or_else(|| Imports::new()), + &mut EvalState::new(), self.lib, fn_name, hash, @@ -234,6 +269,7 @@ impl<'a> NativeCallContext<'a> { /// If the resource is shared (i.e. has other outstanding references), a cloned copy is used. #[inline(always)] #[must_use] +#[allow(dead_code)] pub fn shared_make_mut(value: &mut Shared) -> &mut T { Shared::make_mut(value) } @@ -241,12 +277,14 @@ pub fn shared_make_mut(value: &mut Shared) -> &mut T { /// Consume a [`Shared`] resource if is unique (i.e. not shared), or clone it otherwise. #[inline] #[must_use] +#[allow(dead_code)] pub fn shared_take_or_clone(value: Shared) -> T { shared_try_take(value).unwrap_or_else(|v| v.as_ref().clone()) } /// Consume a [`Shared`] resource if is unique (i.e. not shared). #[inline(always)] +#[allow(dead_code)] pub fn shared_try_take(value: Shared) -> Result> { Shared::try_unwrap(value) } @@ -258,12 +296,25 @@ pub fn shared_try_take(value: Shared) -> Result> { /// Panics if the resource is shared (i.e. has other outstanding references). #[inline] #[must_use] +#[allow(dead_code)] pub fn shared_take(value: Shared) -> T { shared_try_take(value) .ok() .expect("no outstanding references") } +/// Lock a [`Shared`] resource. +#[inline(always)] +#[must_use] +#[allow(dead_code)] +pub fn shared_write_lock<'a, T>(value: &'a Locked) -> LockGuard<'a, T> { + #[cfg(not(feature = "sync"))] + return value.borrow_mut(); + + #[cfg(feature = "sync")] + return value.write().unwrap(); +} + /// A general function trail object. #[cfg(not(feature = "sync"))] pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult; diff --git a/src/fn_ptr.rs b/src/fn_ptr.rs index e0140579..8f091077 100644 --- a/src/fn_ptr.rs +++ b/src/fn_ptr.rs @@ -146,7 +146,7 @@ impl TryFrom for FnPtr { #[inline] fn try_from(value: Identifier) -> Result { if is_valid_identifier(value.chars()) { - Ok(Self(value, Default::default())) + Ok(Self(value, StaticVec::new())) } else { Err(EvalAltResult::ErrorFunctionNotFound(value.to_string(), Position::NONE).into()) } diff --git a/src/fn_register.rs b/src/fn_register.rs index fbc74237..60bcbec8 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -123,7 +123,7 @@ macro_rules! def_register { #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) { - return Err(EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into()); + return Err(EvalAltResult::ErrorAssignmentToConstant(String::new(), Position::NONE).into()); } // The arguments are assumed to be of the correct number and types! @@ -151,7 +151,7 @@ macro_rules! def_register { #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) { - return Err(EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into()); + return Err(EvalAltResult::ErrorAssignmentToConstant(String::new(), Position::NONE).into()); } // The arguments are assumed to be of the correct number and types! @@ -179,7 +179,7 @@ macro_rules! def_register { #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) { - return Err(EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into()); + return Err(EvalAltResult::ErrorAssignmentToConstant(String::new(), Position::NONE).into()); } // The arguments are assumed to be of the correct number and types! @@ -204,7 +204,7 @@ macro_rules! def_register { #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) { - return Err(EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into()); + return Err(EvalAltResult::ErrorAssignmentToConstant(String::new(), Position::NONE).into()); } // The arguments are assumed to be of the correct number and types! diff --git a/src/lib.rs b/src/lib.rs index 4a89ced2..96c1391e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,8 +171,8 @@ pub type Identifier = ImmutableString; /// Alias to [`Rc`][std::rc::Rc] or [`Arc`][std::sync::Arc] depending on the `sync` feature flag. pub use fn_native::Shared; -#[cfg(not(feature = "no_closure"))] -use fn_native::Locked; +//// Alias to [`RefCell`][std::cell::RefCell] or [`RwLock`][std::sync::RwLock] depending on the `sync` feature flag. +pub use fn_native::Locked; pub(crate) use fn_hash::{ calc_fn_hash, calc_fn_params_hash, calc_qualified_fn_hash, calc_qualified_var_hash, diff --git a/src/module/mod.rs b/src/module/mod.rs index 0a9ebe39..03ef891e 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -9,7 +9,7 @@ use crate::parse::IdentifierBuilder; use crate::token::Token; use crate::{ calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, EvalAltResult, - Identifier, ImmutableString, NativeCallContext, Position, Shared, StaticVec, + Identifier, ImmutableString, NativeCallContext, Shared, StaticVec, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -37,13 +37,6 @@ pub enum FnNamespace { Internal, } -impl Default for FnNamespace { - #[inline(always)] - fn default() -> Self { - Self::Internal - } -} - /// Data structure containing a single registered function. #[derive(Debug, Clone)] pub struct FuncInfo { @@ -131,6 +124,8 @@ pub struct Module { id: Option, /// Is this module internal? pub(crate) internal: bool, + /// Is this module part of a standard library? + pub(crate) standard: bool, /// Sub-modules. modules: BTreeMap>, /// [`Module`] variables. @@ -246,16 +241,17 @@ impl Module { Self { id: None, internal: false, - modules: Default::default(), - variables: Default::default(), - all_variables: Default::default(), - functions: Default::default(), - all_functions: Default::default(), - type_iterators: Default::default(), - all_type_iterators: Default::default(), + standard: false, + modules: BTreeMap::new(), + variables: BTreeMap::new(), + all_variables: BTreeMap::new(), + functions: BTreeMap::new(), + all_functions: BTreeMap::new(), + type_iterators: BTreeMap::new(), + all_type_iterators: BTreeMap::new(), indexed: true, contains_indexed_global_functions: false, - identifiers: Default::default(), + identifiers: IdentifierBuilder::new(), } } @@ -455,10 +451,11 @@ impl Module { /// Get a reference to a namespace-qualified variable. /// Name and Position in [`EvalAltResult`] are [`None`] and [`NONE`][Position::NONE] and must be set afterwards. + #[cfg(not(feature = "no_module"))] #[inline] pub(crate) fn get_qualified_var(&self, hash_var: u64) -> Result<&Dynamic, Box> { self.all_variables.get(&hash_var).ok_or_else(|| { - EvalAltResult::ErrorVariableNotFound(String::new(), Position::NONE).into() + EvalAltResult::ErrorVariableNotFound(String::new(), crate::Position::NONE).into() }) } @@ -482,7 +479,7 @@ impl Module { namespace: FnNamespace::Internal, access: fn_def.access, params: num_params, - param_types: Default::default(), + param_types: StaticVec::new(), #[cfg(feature = "metadata")] param_names, func: Into::::into(fn_def).into(), @@ -1561,9 +1558,9 @@ impl Module { if !self.indexed { let mut path = Vec::with_capacity(4); - let mut variables = Default::default(); - let mut functions = Default::default(); - let mut type_iterators = Default::default(); + let mut variables = BTreeMap::new(); + let mut functions = BTreeMap::new(); + let mut type_iterators = BTreeMap::new(); path.push(""); @@ -1677,17 +1674,21 @@ impl fmt::Debug for NamespaceRef { .iter() .map(|Ident { name, .. }| name.as_str()) .collect::>() - .join("::"), + .join(Token::DoubleColon.literal_syntax()), ) } } impl fmt::Display for NamespaceRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for Ident { name, .. } in self.path.iter() { - write!(f, "{}{}", name, Token::DoubleColon.syntax())?; - } - Ok(()) + f.write_str( + &self + .path + .iter() + .map(|Ident { name, .. }| name.as_str()) + .collect::>() + .join(Token::DoubleColon.literal_syntax()), + ) } } diff --git a/src/module/resolvers/collection.rs b/src/module/resolvers/collection.rs index 6a61ff3f..a3581a99 100644 --- a/src/module/resolvers/collection.rs +++ b/src/module/resolvers/collection.rs @@ -20,7 +20,6 @@ use std::prelude::v1::*; /// let mut engine = Engine::new(); /// engine.set_module_resolver(collection); /// ``` -#[derive(Default)] pub struct ModuleResolversCollection(Vec>); impl ModuleResolversCollection { @@ -43,7 +42,7 @@ impl ModuleResolversCollection { #[inline(always)] #[must_use] pub fn new() -> Self { - Default::default() + Self(Vec::new()) } /// Append a [module resolver][ModuleResolver] to the end. #[inline(always)] diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 276243fa..1cf096f8 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -1,4 +1,6 @@ -use crate::{Engine, EvalAltResult, Identifier, Module, ModuleResolver, Position, Shared}; +use crate::fn_native::shared_write_lock; +use crate::{Engine, EvalAltResult, Identifier, Module, ModuleResolver, Position, Scope, Shared}; + #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ @@ -51,13 +53,6 @@ pub struct FileModuleResolver { cache: std::sync::RwLock>>, } -impl Default for FileModuleResolver { - #[inline(always)] - fn default() -> Self { - Self::new() - } -} - impl FileModuleResolver { /// Create a new [`FileModuleResolver`] with the current directory as base path. /// @@ -209,19 +204,12 @@ impl FileModuleResolver { let file_path = self.get_file_path(path, source_path); - #[cfg(not(feature = "sync"))] - return self.cache.borrow_mut().contains_key(&file_path); - #[cfg(feature = "sync")] - return self.cache.write().unwrap().contains_key(&file_path); + shared_write_lock(&self.cache).contains_key(&file_path) } /// Empty the internal cache. #[inline] pub fn clear_cache(&mut self) -> &mut Self { - #[cfg(not(feature = "sync"))] - self.cache.borrow_mut().clear(); - #[cfg(feature = "sync")] - self.cache.write().unwrap().clear(); - + shared_write_lock(&self.cache).clear(); self } /// Remove the specified path from internal cache. @@ -236,19 +224,9 @@ impl FileModuleResolver { ) -> Option> { let file_path = self.get_file_path(path, source_path); - #[cfg(not(feature = "sync"))] - return self - .cache - .borrow_mut() + shared_write_lock(&self.cache) .remove_entry(&file_path) - .map(|(_, v)| v); - #[cfg(feature = "sync")] - return self - .cache - .write() - .unwrap() - .remove_entry(&file_path) - .map(|(_, v)| v); + .map(|(_, v)| v) } /// Construct a full file path. #[must_use] @@ -301,7 +279,7 @@ impl ModuleResolver for FileModuleResolver { } // Load the script file and compile it - let scope = Default::default(); + let scope = Scope::new(); let mut ast = engine .compile_file(file_path.clone()) @@ -321,10 +299,7 @@ impl ModuleResolver for FileModuleResolver { // Put it into the cache if self.is_cache_enabled() { - #[cfg(not(feature = "sync"))] - self.cache.borrow_mut().insert(file_path, m.clone()); - #[cfg(feature = "sync")] - self.cache.write().unwrap().insert(file_path, m.clone()); + shared_write_lock(&self.cache).insert(file_path, m.clone()); } Ok(m) diff --git a/src/module/resolvers/stat.rs b/src/module/resolvers/stat.rs index a5b4d0c9..90b33ed7 100644 --- a/src/module/resolvers/stat.rs +++ b/src/module/resolvers/stat.rs @@ -22,7 +22,7 @@ use std::{collections::BTreeMap, ops::AddAssign}; /// /// engine.set_module_resolver(resolver); /// ``` -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct StaticModuleResolver(BTreeMap>); impl StaticModuleResolver { @@ -45,7 +45,7 @@ impl StaticModuleResolver { #[inline(always)] #[must_use] pub fn new() -> Self { - Default::default() + Self(BTreeMap::new()) } /// Add a [module][Module] keyed by its path. #[inline] diff --git a/src/optimize.rs b/src/optimize.rs index 8479ecd4..899ce282 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -2,7 +2,9 @@ use crate::ast::{Expr, OpAssignment, Stmt, AST_OPTION_FLAGS::*}; use crate::dynamic::AccessMode; -use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF}; +use crate::engine::{ + EvalState, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, +}; use crate::fn_builtin::get_builtin_binary_op_fn; use crate::fn_hash::get_hasher; use crate::token::Token; @@ -131,8 +133,8 @@ impl<'a> OptimizerState<'a> { ) -> Option { self.engine .call_native_fn( - &Default::default(), - &mut Default::default(), + &mut Imports::new(), + &mut EvalState::new(), self.lib, fn_name, calc_fn_hash(fn_name, arg_values.len()), @@ -145,12 +147,13 @@ impl<'a> OptimizerState<'a> { .map(|(v, _)| v) } // Has a system function a Rust-native override? - pub fn has_native_fn(&self, hash_script: u64, arg_types: &[TypeId]) -> bool { + pub fn has_native_fn_override(&self, hash_script: u64, arg_types: &[TypeId]) -> bool { let hash_params = calc_fn_params_hash(arg_types.iter().cloned()); let hash = combine_hashes(hash_script, hash_params); - // First check packages - self.engine.global_modules.iter().any(|m| m.contains_fn(hash)) + // First check the global namespace and packages, but skip modules that are standard because + // they should never conflict with system functions. + self.engine.global_modules.iter().filter(|m| !m.standard).any(|m| m.contains_fn(hash)) // Then check sub-modules || self.engine.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash)) } @@ -482,9 +485,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // switch const { ... } Stmt::Switch(match_expr, x, pos) if match_expr.is_constant() => { - let value = match_expr - .get_literal_value() - .expect("`match_expr` is constant"); + let value = match_expr.get_literal_value().expect("constant"); let hasher = &mut get_hasher(); value.hash(hasher); let hash = hasher.finish(); @@ -878,7 +879,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { #[cfg(not(feature = "no_index"))] Expr::Array(_, _) if expr.is_constant() => { state.set_dirty(); - *expr = Expr::DynamicConstant(expr.get_literal_value().expect("`expr` is constant").into(), expr.position()); + *expr = Expr::DynamicConstant(expr.get_literal_value().expect("constant").into(), expr.position()); } // [ items .. ] #[cfg(not(feature = "no_index"))] @@ -887,7 +888,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { #[cfg(not(feature = "no_object"))] Expr::Map(_, _) if expr.is_constant() => { state.set_dirty(); - *expr = Expr::DynamicConstant(expr.get_literal_value().expect("`expr` is constant").into(), expr.position()); + *expr = Expr::DynamicConstant(expr.get_literal_value().expect("constant").into(), expr.position()); } // #{ key:value, .. } #[cfg(not(feature = "no_object"))] @@ -960,7 +961,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { state.set_dirty(); let fn_ptr = FnPtr::new_unchecked( fn_name.as_str_ref().expect("`fn_name` is `ImmutableString`").into(), - Default::default() + StaticVec::new() ); *expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos); } @@ -981,31 +982,37 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { => { let arg_values = &mut x.args.iter().map(|e| match e { Expr::Stack(slot, _) => x.constants[*slot].clone(), - _ => e.get_literal_value().expect("`e` is constant") + _ => e.get_literal_value().expect("constant") }).collect::>(); let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); - let result = match x.name.as_str() { - KEYWORD_TYPE_OF if arg_values.len() == 1 => Some(state.engine.map_type_name(arg_values[0].type_name()).into()), - #[cfg(not(feature = "no_closure"))] - KEYWORD_IS_SHARED if arg_values.len() == 1 => Some(Dynamic::FALSE), - // Overloaded operators can override built-in. - _ if x.args.len() == 2 && !state.has_native_fn(x.hashes.native, arg_types.as_ref()) => { - get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1]) - .and_then(|f| { - let ctx = (state.engine, x.name.as_ref(), state.lib).into(); - let (first, second) = arg_values.split_first_mut().expect("`arg_values` is not empty"); - (f)(ctx, &mut [ first, &mut second[0] ]).ok() - }) + match x.name.as_str() { + KEYWORD_TYPE_OF if arg_values.len() == 1 => { + state.set_dirty(); + *expr = Expr::from_dynamic(state.engine.map_type_name(arg_values[0].type_name()).into(), *pos); + return; } - _ => None - }; - - if let Some(result) = result { - state.set_dirty(); - *expr = Expr::from_dynamic(result, *pos); - return; + #[cfg(not(feature = "no_closure"))] + KEYWORD_IS_SHARED if arg_values.len() == 1 => { + state.set_dirty(); + *expr = Expr::from_dynamic(Dynamic::FALSE, *pos); + return; + } + // Overloaded operators can override built-in. + _ if x.args.len() == 2 && !state.has_native_fn_override(x.hashes.native, arg_types.as_ref()) => { + if let Some(result) = get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1]) + .and_then(|f| { + let context = (state.engine, x.name.as_ref(), state.lib).into(); + let (first, second) = arg_values.split_first_mut().expect("`arg_values` is not empty"); + (f)(context, &mut [ first, &mut second[0] ]).ok() + }) { + state.set_dirty(); + *expr = Expr::from_dynamic(result, *pos); + return; + } + } + _ => () } x.args.iter_mut().for_each(|a| optimize_expr(a, state, false)); @@ -1035,7 +1042,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { if !has_script_fn { let arg_values = &mut x.args.iter().map(|e| match e { Expr::Stack(slot, _) => x.constants[*slot].clone(), - _ => e.get_literal_value().expect("`e` is constant") + _ => e.get_literal_value().expect("constant") }).collect::>(); let result = match x.name.as_str() { @@ -1079,7 +1086,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { if x.scope_may_be_changed { state.propagate_constants = false; } - x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state, false)); + x.inputs.iter_mut().for_each(|expr| optimize_expr(expr, state, false)); } // All other expressions - skip @@ -1149,16 +1156,16 @@ pub fn optimize_into_ast( .map(|fn_def| crate::ast::ScriptFnDef { name: fn_def.name.clone(), access: fn_def.access, - body: Default::default(), + body: crate::ast::StmtBlock::empty(), params: fn_def.params.clone(), #[cfg(not(feature = "no_closure"))] externals: fn_def.externals.clone(), lib: None, #[cfg(not(feature = "no_module"))] - mods: Default::default(), + mods: crate::engine::Imports::new(), #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - comments: Default::default(), + comments: StaticVec::new(), }) .for_each(|fn_def| { lib2.set_script_fn(fn_def); @@ -1193,7 +1200,7 @@ pub fn optimize_into_ast( }; #[cfg(feature = "no_function")] - let lib = Default::default(); + let lib = Module::new(); statements.shrink_to_fit(); diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index f387d862..2d49b194 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -177,6 +177,8 @@ macro_rules! reg_functions { } def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { + lib.standard = true; + combine_with_exported_module!(lib, "int", int_functions); reg_functions!(lib += signed_basic; INT); diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 51c1bcc0..1fd0b94b 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -9,6 +9,8 @@ use std::prelude::v1::*; use std::{any::TypeId, cmp::Ordering, mem}; def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { + lib.standard = true; + combine_with_exported_module!(lib, "array", array_functions); // Register array iterator diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index a40c7b73..ab154217 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -4,6 +4,8 @@ use crate::{def_package, FnPtr, ImmutableString, NativeCallContext}; use std::prelude::v1::*; def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, { + lib.standard = true; + combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions); }); @@ -107,7 +109,12 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { list.push(make_metadata(dict, Some(namespace.clone()), f).into()) }); module.iter_sub_modules().for_each(|(ns, m)| { - let ns = format!("{}::{}", namespace, ns); + let ns = format!( + "{}{}{}", + namespace, + crate::token::Token::DoubleColon.literal_syntax(), + ns + ); scan_module(list, dict, ns.into(), m.as_ref()) }); } diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index c9f481d6..16d3d5e4 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -27,7 +27,7 @@ where if r == from { return Err(EvalAltResult::ErrorInFunctionCall( "range".to_string(), - Default::default(), + String::new(), EvalAltResult::ErrorArithmetic( "step value cannot be zero".to_string(), crate::Position::NONE, @@ -201,7 +201,7 @@ struct CharsStream(Vec, usize); impl CharsStream { pub fn new(string: &str, from: INT, len: INT) -> Self { if len <= 0 { - return Self(Default::default(), 0); + return Self(Vec::new(), 0); } if from >= 0 { return Self( @@ -298,6 +298,8 @@ macro_rules! reg_range { } def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { + lib.standard = true; + reg_range!(lib | "range" => INT); #[cfg(not(feature = "only_i32"))] diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index 6c4c76a5..91c78dba 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -43,5 +43,7 @@ mod core_functions { } def_package!(crate:LanguageCorePackage:"Language core functions.", lib, { + lib.standard = true; + combine_with_exported_module!(lib, "language_core", core_functions); }); diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 663b29d4..9e7c7e0d 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -38,6 +38,8 @@ macro_rules! reg_functions { } def_package!(crate:LogicPackage:"Logical operators.", lib, { + lib.standard = true; + #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 2b13c5d4..f8338043 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -10,6 +10,8 @@ use std::prelude::v1::*; use crate::Array; def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { + lib.standard = true; + combine_with_exported_module!(lib, "map", map_functions); }); diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index d008b8e4..b97fb9db 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -62,6 +62,8 @@ macro_rules! reg_functions { } def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { + lib.standard = true; + // Integer functions combine_with_exported_module!(lib, "int", int_functions); diff --git a/src/packages/pkg_core.rs b/src/packages/pkg_core.rs index 7f79b3f4..e82e7fc2 100644 --- a/src/packages/pkg_core.rs +++ b/src/packages/pkg_core.rs @@ -10,6 +10,8 @@ use std::prelude::v1::*; use crate::def_package; def_package!(crate:CorePackage:"_Core_ package containing basic facilities.", lib, { + lib.standard = true; + LanguageCorePackage::init(lib); ArithmeticPackage::init(lib); LogicPackage::init(lib); diff --git a/src/packages/pkg_std.rs b/src/packages/pkg_std.rs index 3c18d603..afdece41 100644 --- a/src/packages/pkg_std.rs +++ b/src/packages/pkg_std.rs @@ -13,6 +13,8 @@ use std::prelude::v1::*; use crate::def_package; def_package!(crate:StandardPackage:"_Standard_ package containing all built-in features.", lib, { + lib.standard = true; + CorePackage::init(lib); BasicMathPackage::init(lib); #[cfg(not(feature = "no_index"))] diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 00aeee89..1959decc 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -16,6 +16,8 @@ pub const FUNC_TO_STRING: &str = "to_string"; pub const FUNC_TO_DEBUG: &str = "to_debug"; def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, { + lib.standard = true; + combine_with_exported_module!(lib, "print_debug", print_debug_functions); combine_with_exported_module!(lib, "number_formatting", number_formatting); }); @@ -58,8 +60,8 @@ mod print_debug_functions { ctx.engine().map_type_name(&format!("{:?}", item)).into() } #[rhai_fn(name = "print", name = "debug")] - pub fn print_empty_string() -> ImmutableString { - Default::default() + pub fn print_empty_string(ctx: NativeCallContext) -> ImmutableString { + ctx.engine().const_empty_string() } #[rhai_fn(name = "print", name = "to_string")] pub fn print_string(s: ImmutableString) -> ImmutableString { diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 02955b09..67e9ff4f 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -9,6 +9,8 @@ use std::{any::TypeId, mem}; use super::string_basic::{print_with_func, FUNC_TO_STRING}; def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, { + lib.standard = true; + combine_with_exported_module!(lib, "string", string_functions); }); @@ -53,6 +55,10 @@ mod string_functions { pub fn add_append_char(string: ImmutableString, character: char) -> ImmutableString { string + character } + #[rhai_fn(name = "+")] + pub fn add_prepend_char(character: char, string: ImmutableString) -> ImmutableString { + format!("{}{}", character, string).into() + } #[rhai_fn(name = "+", name = "append")] pub fn add_append_unit(string: ImmutableString, item: ()) -> ImmutableString { diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index eeff7a50..7b5bbbcd 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -16,6 +16,8 @@ use std::time::{Duration, Instant}; use instant::{Duration, Instant}; def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { + lib.standard = true; + // Register date/time functions combine_with_exported_module!(lib, "time", time_functions); }); diff --git a/src/parse.rs b/src/parse.rs index e33d5c59..9e3be877 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -50,12 +50,21 @@ const NEVER_ENDS: &str = "`TokenStream` never ends"; /// When [`ImmutableString`] is used as [`Identifier`], this type acts as an interner which keeps a /// collection of strings and returns shared instances, only creating a new string when it is not /// yet interned. -#[derive(Debug, Clone, Default, Hash)] +#[derive(Debug, Clone, Hash)] pub struct IdentifierBuilder( #[cfg(feature = "no_smartstring")] std::collections::BTreeSet, ); impl IdentifierBuilder { + /// Create a new IdentifierBuilder. + #[inline] + #[must_use] + pub fn new() -> Self { + Self( + #[cfg(feature = "no_smartstring")] + std::collections::BTreeSet::new(), + ) + } /// Get an identifier from a text string. #[inline] #[must_use] @@ -121,14 +130,14 @@ impl<'e> ParseState<'e> { #[cfg(not(feature = "no_function"))] max_function_expr_depth: NonZeroUsize::new(engine.max_function_expr_depth()), #[cfg(not(feature = "no_closure"))] - external_vars: Default::default(), + external_vars: BTreeMap::new(), #[cfg(not(feature = "no_closure"))] allow_capture: true, - interned_strings: Default::default(), - stack: Default::default(), + interned_strings: IdentifierBuilder::new(), + stack: StaticVec::new(), entry_stack_len: 0, #[cfg(not(feature = "no_module"))] - modules: Default::default(), + modules: StaticVec::new(), } } @@ -398,7 +407,7 @@ fn parse_symbol(input: &mut TokenStream) -> Result<(String, Position), ParseErro // Bad identifier (Token::LexError(err), pos) => Err(err.into_err(pos)), // Not a symbol - (_, pos) => Err(PERR::MissingSymbol(Default::default()).into_err(pos)), + (_, pos) => Err(PERR::MissingSymbol(String::new()).into_err(pos)), } } @@ -902,7 +911,7 @@ fn parse_map_literal( let expr = parse_expr(input, state, lib, settings.level_up())?; let name = state.get_identifier(name); - template.insert(name.clone(), Default::default()); + template.insert(name.clone(), crate::Dynamic::UNIT); map.push((Ident { name, pos }, expr)); match input.peek().expect(NEVER_ENDS) { @@ -1379,6 +1388,7 @@ fn parse_primary( parse_fn_call(input, state, lib, name, false, ns, settings.level_up())? } // module access + #[cfg(not(feature = "no_module"))] (Expr::Variable(_, var_pos, x), Token::DoubleColon) => { let (id2, pos2) = parse_var_name(input)?; let (_, mut namespace, var_name) = *x; @@ -1572,13 +1582,18 @@ fn make_assignment_stmt( #[must_use] fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option { match expr { - Expr::Index(x, _, _) | Expr::Dot(x, _, _) if parent_is_dot => match x.lhs { - Expr::Property(_) => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))), + Expr::Index(x, term, _) | Expr::Dot(x, term, _) if parent_is_dot => match x.lhs { + Expr::Property(_) if !term => { + check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))) + } + Expr::Property(_) => None, + // Anything other than a property after dotting (e.g. a method call) is not an l-value ref e => Some(e.position()), }, - Expr::Index(x, _, _) | Expr::Dot(x, _, _) => match x.lhs { + Expr::Index(x, term, _) | Expr::Dot(x, term, _) => match x.lhs { Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"), - _ => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))), + _ if !term => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))), + _ => None, }, Expr::Property(_) if parent_is_dot => None, Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"), @@ -1618,19 +1633,30 @@ fn make_assignment_stmt( } } // xxx[???]... = rhs, xxx.prop... = rhs - Expr::Index(ref x, _, _) | Expr::Dot(ref x, _, _) => { - match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _, _))) { - None => match x.lhs { - // var[???] = rhs, var.??? = rhs - Expr::Variable(_, _, _) => { - Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos)) + Expr::Index(ref x, term, _) | Expr::Dot(ref x, term, _) => { + let valid_lvalue = if term { + None + } else { + check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _, _))) + }; + + match valid_lvalue { + None => { + match x.lhs { + // var[???] = rhs, var.??? = rhs + Expr::Variable(_, _, _) => { + Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos)) + } + // expr[???] = rhs, expr.??? = rhs + ref expr => { + Err(PERR::AssignmentToInvalidLHS("".to_string()) + .into_err(expr.position())) + } } - // expr[???] = rhs, expr.??? = rhs - ref expr => { - Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(expr.position())) - } - }, - Some(pos) => Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(pos)), + } + Some(err_pos) => { + Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(err_pos)) + } } } // ??? && ??? = rhs, ??? || ??? = rhs @@ -1978,7 +2004,7 @@ fn parse_custom_syntax( pos: Position, ) -> Result { let mut settings = settings; - let mut keywords = StaticVec::::new(); + let mut inputs = StaticVec::::new(); let mut segments = StaticVec::new(); let mut tokens = StaticVec::new(); @@ -2002,6 +2028,13 @@ fn parse_custom_syntax( let settings = settings.level_up(); required_token = match parse_func(&segments, fwd_token.syntax().as_ref()) { + Ok(Some(seg)) + if seg.starts_with(CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT) + && seg.len() > CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT.len() => + { + inputs.push(Expr::StringConstant(state.get_identifier(seg).into(), pos)); + break; + } Ok(Some(seg)) => seg, Ok(None) => break, Err(err) => return Err(err.0.into_err(settings.pos)), @@ -2013,24 +2046,24 @@ fn parse_custom_syntax( let name = state.get_identifier(name); segments.push(name.clone().into()); tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_IDENT)); - keywords.push(Expr::Variable(None, pos, (None, None, name).into())); + inputs.push(Expr::Variable(None, pos, (None, None, name).into())); } CUSTOM_SYNTAX_MARKER_SYMBOL => { let (symbol, pos) = parse_symbol(input)?; let symbol: ImmutableString = state.get_identifier(symbol).into(); segments.push(symbol.clone()); tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_SYMBOL)); - keywords.push(Expr::StringConstant(symbol, pos)); + inputs.push(Expr::StringConstant(symbol, pos)); } CUSTOM_SYNTAX_MARKER_EXPR => { - keywords.push(parse_expr(input, state, lib, settings)?); + inputs.push(parse_expr(input, state, lib, settings)?); let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_EXPR); segments.push(keyword.clone().into()); tokens.push(keyword); } CUSTOM_SYNTAX_MARKER_BLOCK => match parse_block(input, state, lib, settings)? { block @ Stmt::Block(_, _) => { - keywords.push(Expr::Stmt(Box::new(block.into()))); + inputs.push(Expr::Stmt(Box::new(block.into()))); let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_BLOCK); segments.push(keyword.clone().into()); tokens.push(keyword); @@ -2039,7 +2072,7 @@ fn parse_custom_syntax( }, CUSTOM_SYNTAX_MARKER_BOOL => match input.next().expect(NEVER_ENDS) { (b @ Token::True, pos) | (b @ Token::False, pos) => { - keywords.push(Expr::BoolConstant(b == Token::True, pos)); + inputs.push(Expr::BoolConstant(b == Token::True, pos)); segments.push(state.get_identifier(b.literal_syntax()).into()); tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_BOOL)); } @@ -2052,7 +2085,7 @@ fn parse_custom_syntax( }, CUSTOM_SYNTAX_MARKER_INT => match input.next().expect(NEVER_ENDS) { (Token::IntegerConstant(i), pos) => { - keywords.push(Expr::IntegerConstant(i, pos)); + inputs.push(Expr::IntegerConstant(i, pos)); segments.push(i.to_string().into()); tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_INT)); } @@ -2066,7 +2099,7 @@ fn parse_custom_syntax( #[cfg(not(feature = "no_float"))] CUSTOM_SYNTAX_MARKER_FLOAT => match input.next().expect(NEVER_ENDS) { (Token::FloatConstant(f), pos) => { - keywords.push(Expr::FloatConstant(f, pos)); + inputs.push(Expr::FloatConstant(f, pos)); segments.push(f.to_string().into()); tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_FLOAT)); } @@ -2080,7 +2113,7 @@ fn parse_custom_syntax( CUSTOM_SYNTAX_MARKER_STRING => match input.next().expect(NEVER_ENDS) { (Token::StringConstant(s), pos) => { let s: ImmutableString = state.get_identifier(s).into(); - keywords.push(Expr::StringConstant(s.clone(), pos)); + inputs.push(Expr::StringConstant(s.clone(), pos)); segments.push(s); tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_STRING)); } @@ -2105,7 +2138,7 @@ fn parse_custom_syntax( } } - keywords.shrink_to_fit(); + inputs.shrink_to_fit(); tokens.shrink_to_fit(); const KEYWORD_SEMICOLON: &str = Token::SemiColon.literal_syntax(); @@ -2121,7 +2154,7 @@ fn parse_custom_syntax( Ok(Expr::Custom( CustomExpr { - keywords, + inputs, tokens, scope_may_be_changed: syntax.scope_may_be_changed, self_terminated, @@ -2532,7 +2565,7 @@ fn parse_export( } (name, pos) } else { - (Default::default(), Position::NONE) + (String::new(), Position::NONE) }; exports.push(( @@ -3051,7 +3084,7 @@ fn parse_fn( body, lib: None, #[cfg(not(feature = "no_module"))] - mods: Default::default(), + mods: crate::engine::Imports::new(), #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] comments, @@ -3192,17 +3225,17 @@ fn parse_anon_fn( access: FnAccess::Public, params, #[cfg(not(feature = "no_closure"))] - externals: Default::default(), + externals: std::collections::BTreeSet::new(), body: body.into(), lib: None, #[cfg(not(feature = "no_module"))] - mods: Default::default(), + mods: crate::engine::Imports::new(), #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - comments: Default::default(), + comments: StaticVec::new(), }; - let fn_ptr = crate::FnPtr::new_unchecked(fn_name, Default::default()); + let fn_ptr = crate::FnPtr::new_unchecked(fn_name, StaticVec::new()); let expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), settings.pos); #[cfg(not(feature = "no_closure"))] @@ -3221,7 +3254,7 @@ impl Engine { #[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel, ) -> Result { let _scope = scope; - let mut functions = Default::default(); + let mut functions = BTreeMap::new(); let settings = ParseSettings { allow_if_expr: false, @@ -3254,7 +3287,7 @@ impl Engine { self, _scope, statements, - Default::default(), + StaticVec::new(), optimization_level, )); diff --git a/src/scope.rs b/src/scope.rs index 6f217481..ba5ce2fc 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -243,7 +243,7 @@ impl<'a> Scope<'a> { access: AccessMode, mut value: Dynamic, ) -> &mut Self { - self.names.push((name.into(), Default::default())); + self.names.push((name.into(), None)); value.set_access_mode(access); self.values.push(value); self @@ -307,10 +307,11 @@ impl<'a> Scope<'a> { pub(crate) fn get_index(&self, name: &str) -> Option<(usize, AccessMode)> { self.names .iter() - .enumerate() .rev() // Always search a Scope in reverse order + .enumerate() .find_map(|(index, (key, _))| { if name == key.as_ref() { + let index = self.len() - 1 - index; Some((index, self.values[index].access_mode())) } else { None @@ -334,10 +335,14 @@ impl<'a> Scope<'a> { pub fn get_value(&self, name: &str) -> Option { self.names .iter() - .enumerate() .rev() + .enumerate() .find(|(_, (key, _))| name == key.as_ref()) - .and_then(|(index, _)| self.values[index].flatten_clone().try_cast()) + .and_then(|(index, _)| { + self.values[self.len() - 1 - index] + .flatten_clone() + .try_cast() + }) } /// Check if the named entry in the [`Scope`] is constant. /// @@ -516,12 +521,14 @@ impl<'a> Scope<'a> { self.names .iter() - .enumerate() .rev() - .for_each(|(i, (name, alias))| { + .enumerate() + .for_each(|(index, (name, alias))| { if !entries.names.iter().any(|(key, _)| key == name) { entries.names.push((name.clone(), alias.clone())); - entries.values.push(self.values[i].clone()); + entries + .values + .push(self.values[self.len() - 1 - index].clone()); } }); @@ -585,7 +592,7 @@ impl<'a, K: Into>> Extend<(K, Dynamic)> for Scope<'a> { #[inline] fn extend>(&mut self, iter: T) { iter.into_iter().for_each(|(name, value)| { - self.names.push((name.into(), Default::default())); + self.names.push((name.into(), None)); self.values.push(value); }); } diff --git a/src/serde/de.rs b/src/serde/de.rs index b7c3e67a..e038c260 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -116,7 +116,7 @@ pub fn from_dynamic<'de, T: Deserialize<'de>>( impl Error for Box { fn custom(err: T) -> Self { - LexError::ImproperSymbol(Default::default(), err.to_string()) + LexError::ImproperSymbol(String::new(), err.to_string()) .into_err(Position::NONE) .into() } diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index 2ff11a9f..1e10e58a 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -168,7 +168,7 @@ impl From<&crate::module::FuncInfo> for FnMetadata { .to_vec() } } else { - Default::default() + Vec::new() }, } } @@ -198,7 +198,7 @@ impl From> for FnMetadata { } } -#[derive(Debug, Clone, Default, Serialize)] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] struct ModuleMetadata { #[serde(skip_serializing_if = "BTreeMap::is_empty")] @@ -210,7 +210,10 @@ struct ModuleMetadata { impl ModuleMetadata { #[inline(always)] pub fn new() -> Self { - Default::default() + Self { + modules: BTreeMap::new(), + functions: Vec::new(), + } } } @@ -281,6 +284,6 @@ impl Engine { /// 2) Functions in static modules /// 3) Functions in global modules (optional) pub fn gen_fn_metadata_to_json(&self, include_global: bool) -> serde_json::Result { - self.gen_fn_metadata_with_ast_to_json(&Default::default(), include_global) + self.gen_fn_metadata_with_ast_to_json(&AST::empty(), include_global) } } diff --git a/src/serde/ser.rs b/src/serde/ser.rs index bfd0d128..a784a47e 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -28,7 +28,7 @@ impl DynamicSerializer { #[must_use] pub fn new(_value: Dynamic) -> Self { Self { - _key: Default::default(), + _key: Dynamic::UNIT, _value, } } @@ -83,7 +83,7 @@ impl DynamicSerializer { /// # } /// ``` pub fn to_dynamic(value: T) -> RhaiResult { - let mut s = DynamicSerializer::new(Default::default()); + let mut s = DynamicSerializer::new(Dynamic::UNIT); value.serialize(&mut s) } @@ -397,7 +397,7 @@ impl Serializer for &mut DynamicSerializer { #[cfg(not(feature = "no_object"))] return Ok(StructVariantSerializer { variant: _variant, - map: Default::default(), + map: Map::new(), }); #[cfg(feature = "no_object")] return Err(EvalAltResult::ErrorMismatchDataType( diff --git a/src/tests.rs b/src/tests.rs index 98f23fdc..de960d48 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -36,4 +36,12 @@ fn check_struct_sizes() { if cfg!(feature = "no_position") { 8 } else { 16 } ); assert_eq!(size_of::(), 72); + assert_eq!( + size_of::(), + if cfg!(feature = "no_position") { + 64 + } else { + 72 + } + ); } diff --git a/src/token.rs b/src/token.rs index 5266b467..51c57726 100644 --- a/src/token.rs +++ b/src/token.rs @@ -33,13 +33,24 @@ use crate::engine::KEYWORD_IS_DEF_FN; /// # Volatile Data Structure /// /// This type is volatile and may change. -#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy, Default)] +#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)] pub struct TokenizerControlBlock { /// Is the current tokenizer position within an interpolated text string? /// This flag allows switching the tokenizer back to _text_ parsing after an interpolation stream. pub is_within_text: bool, } +impl TokenizerControlBlock { + /// Create a new `TokenizerControlBlock`. + #[inline(always)] + #[must_use] + pub const fn new() -> Self { + Self { + is_within_text: false, + } + } +} + /// _(internals)_ A shared object that allows control of the tokenizer from outside. pub type TokenizerControl = Rc>; @@ -805,55 +816,56 @@ impl Token { match self { LexError(_) | - LeftBrace | // {+expr} - is unary - // RightBrace | {expr} - expr not unary & is closing - LeftParen | // (-expr) - is unary - // RightParen | (expr) - expr not unary & is closing - LeftBracket | // [-expr] - is unary - // RightBracket | [expr] - expr not unary & is closing + SemiColon | // ; - is unary + Comma | // ( ... , -expr ) - is unary + //Period | + LeftBrace | // { -expr } - is unary + // RightBrace | { expr } - expr not unary & is closing + LeftParen | // ( -expr ) - is unary + // RightParen | // ( expr ) - expr not unary & is closing + LeftBracket | // [ -expr ] - is unary + // RightBracket | // [ expr ] - expr not unary & is closing Plus | + PlusAssign | UnaryPlus | Minus | + MinusAssign | UnaryMinus | Multiply | + MultiplyAssign | Divide | - Comma | - Period | + DivideAssign | + Modulo | + ModuloAssign | + PowerOf | + PowerOfAssign | + LeftShift | + LeftShiftAssign | + RightShift | + RightShiftAssign | Equals | + EqualsTo | + NotEqualsTo | LessThan | GreaterThan | Bang | LessThanEqualsTo | GreaterThanEqualsTo | - EqualsTo | - NotEqualsTo | Pipe | - Or | Ampersand | - And | If | - Do | + //Do | While | Until | - PlusAssign | - MinusAssign | - MultiplyAssign | - DivideAssign | - LeftShiftAssign | - RightShiftAssign | - PowerOf | - PowerOfAssign | + In | + And | AndAssign | + Or | OrAssign | - XOrAssign | - LeftShift | - RightShift | XOr | - Modulo | - ModuloAssign | + XOrAssign | Return | - Throw | - In => true, + Throw => true, _ => false, } @@ -1829,6 +1841,7 @@ fn get_next_token_inner( } ('=', _) => return Some((Token::Equals, start_pos)), + #[cfg(not(feature = "no_module"))] (':', ':') => { eat_next(stream, pos); @@ -2279,7 +2292,7 @@ impl Engine { input: impl IntoIterator, token_mapper: Option<&'a OnParseTokenCallback>, ) -> (TokenIterator<'a>, TokenizerControl) { - let buffer: TokenizerControl = Default::default(); + let buffer: TokenizerControl = Cell::new(TokenizerControlBlock::new()).into(); let buffer2 = buffer.clone(); ( diff --git a/tests/custom_syntax.rs b/tests/custom_syntax.rs index f1b5a297..f381bdf0 100644 --- a/tests/custom_syntax.rs +++ b/tests/custom_syntax.rs @@ -194,8 +194,9 @@ fn test_custom_syntax_raw() -> Result<(), Box> { 0 => unreachable!(), 1 => Ok(Some("$ident$".into())), 2 => match stream[1].as_str() { - "world" | "kitty" => Ok(None), - s => Err(LexError::ImproperSymbol(s.to_string(), Default::default()) + "world" => Ok(Some("$$hello".into())), + "kitty" => Ok(None), + s => Err(LexError::ImproperSymbol(s.to_string(), String::new()) .into_err(Position::NONE) .into()), }, @@ -206,7 +207,17 @@ fn test_custom_syntax_raw() -> Result<(), Box> { context.scope_mut().push("foo", 999 as INT); Ok(match inputs[0].get_variable_name().unwrap() { + "world" + if inputs + .last() + .unwrap() + .get_literal_value::() + .map_or(false, |s| s == "$$hello") => + { + 0 as INT + } "world" => 123 as INT, + "kitty" if inputs.len() > 1 => 999 as INT, "kitty" => 42 as INT, _ => unreachable!(), } @@ -214,7 +225,7 @@ fn test_custom_syntax_raw() -> Result<(), Box> { }, ); - assert_eq!(engine.eval::("hello world")?, 123); + assert_eq!(engine.eval::("hello world")?, 0); assert_eq!(engine.eval::("hello kitty")?, 42); assert_eq!( engine.eval::("let foo = 0; (hello kitty) + foo")?, diff --git a/tests/expressions.rs b/tests/expressions.rs index 13588d0d..6c0f16b1 100644 --- a/tests/expressions.rs +++ b/tests/expressions.rs @@ -59,7 +59,7 @@ fn test_expressions_eval() -> Result<(), Box> { engine.register_get("gender", AGENT::get_gender); engine.register_get("age", AGENT::get_age); - // Create your context, add the agent as a constant + // Create your scope, add the agent as a constant let mut scope = Scope::new(); scope.push_constant("agent", my_agent); diff --git a/tests/functions.rs b/tests/functions.rs index cd40c8ee..e0b4af5a 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, FnNamespace, Module, Shared, INT}; +use rhai::{Engine, EvalAltResult, FnNamespace, Module, NativeCallContext, Shared, INT}; #[cfg(not(feature = "no_object"))] #[test] @@ -51,33 +51,95 @@ fn test_functions_namespaces() -> Result<(), Box> { engine.register_static_module("hello", m.into()); + let mut m = Module::new(); + m.set_var("ANSWER", 123 as INT); + assert_eq!(engine.eval::("test()")?, 999); - #[cfg(not(feature = "no_function"))] assert_eq!(engine.eval::("fn test() { 123 } test()")?, 123); } engine.register_fn("test", || 42 as INT); + assert_eq!(engine.eval::("fn test() { 123 } test()")?, 123); + assert_eq!(engine.eval::("test()")?, 42); - #[cfg(not(feature = "no_function"))] - { - assert_eq!(engine.eval::("fn test() { 123 } test()")?, 123); - - assert_eq!( - engine.eval::( - " - const ANSWER = 42; - - fn foo() { global::ANSWER } - - foo() - " - )?, - 42 - ); - } - + Ok(()) +} + +#[cfg(not(feature = "no_module"))] +#[test] +fn test_functions_global_module() -> Result<(), Box> { + let mut engine = Engine::new(); + + assert_eq!( + engine.eval::( + " + const ANSWER = 42; + fn foo() { global::ANSWER } + foo() + " + )?, + 42 + ); + + assert!(matches!(*engine.run(" + fn foo() { global::ANSWER } + + { + const ANSWER = 42; + foo() + } + ").expect_err("should error"), + EvalAltResult::ErrorInFunctionCall(_, _, err, _) + if matches!(&*err, EvalAltResult::ErrorVariableNotFound(v, _) if v == "global::ANSWER") + )); + + engine.register_result_fn( + "do_stuff", + |context: NativeCallContext, callback: rhai::FnPtr| { + callback.call_dynamic(&context, None, []) + }, + ); + + #[cfg(not(feature = "no_closure"))] + assert!(matches!(*engine.run(" + do_stuff(|| { + const LOCAL_VALUE = 42; + global::LOCAL_VALUE + }); + ").expect_err("should error"), + EvalAltResult::ErrorInFunctionCall(_, _, err, _) + if matches!(&*err, EvalAltResult::ErrorVariableNotFound(v, _) if v == "global::LOCAL_VALUE") + )); + + #[cfg(not(feature = "no_closure"))] + assert_eq!( + engine.eval::( + " + const GLOBAL_VALUE = 42; + do_stuff(|| global::GLOBAL_VALUE); + " + )?, + 42 + ); + + // Override global + let mut module = Module::new(); + module.set_var("ANSWER", 123 as INT); + engine.register_static_module("global", module.into()); + + assert_eq!( + engine.eval::( + " + const ANSWER = 42; + fn foo() { global::ANSWER } + foo() + " + )?, + 123 + ); + Ok(()) } diff --git a/tests/modules.rs b/tests/modules.rs index 8a6f8d67..e42198d9 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -444,7 +444,7 @@ fn test_module_ast_namespace() -> Result<(), Box> { let ast = engine.compile(script)?; - let module = Module::eval_ast_as_new(Default::default(), &ast, &engine)?; + let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?; let mut resolver = StaticModuleResolver::new(); resolver.insert("testing", module); @@ -512,6 +512,6 @@ fn test_module_file() -> Result<(), Box> { print("top"); "#, )?; - Module::eval_ast_as_new(Default::default(), &ast, &engine)?; + Module::eval_ast_as_new(Scope::new(), &ast, &engine)?; Ok(()) } diff --git a/tests/native.rs b/tests/native.rs index 349cebf8..28b00680 100644 --- a/tests/native.rs +++ b/tests/native.rs @@ -1,4 +1,4 @@ -use rhai::{Dynamic, Engine, EvalAltResult, NativeCallContext, INT}; +use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString, NativeCallContext, INT}; use std::any::TypeId; #[cfg(not(feature = "no_module"))] @@ -49,3 +49,41 @@ fn test_native_context_fn_name() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_native_overload() -> Result<(), Box> { + let mut engine = Engine::new(); + + assert_eq!( + engine.eval::(r#"let x = "hello, "; let y = "world"; x + y"#)?, + "hello, world" + ); + assert_eq!( + engine.eval::(r#"let x = "hello"; let y = (); x + y"#)?, + "hello" + ); + + // Overload the `+` operator for strings + + engine + .register_fn( + "+", + |s1: ImmutableString, s2: ImmutableString| -> ImmutableString { + format!("{}***{}", s1, s2).into() + }, + ) + .register_fn("+", |s1: ImmutableString, _: ()| -> ImmutableString { + format!("{} Foo!", s1).into() + }); + + assert_eq!( + engine.eval::(r#"let x = "hello"; let y = "world"; x + y"#)?, + "hello***world" + ); + assert_eq!( + engine.eval::(r#"let x = "hello"; let y = (); x + y"#)?, + "hello Foo!" + ); + + Ok(()) +}