diff --git a/src/api/mod.rs b/src/api/mod.rs index a8a2a3c3..7accbaf6 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -83,17 +83,15 @@ impl Engine { .map(|f| { f.func .get_script_fn_def() - .expect("scripted function") + .expect("script-defined function") .clone() }) .collect(); - let statements = std::mem::take(ast.statements_mut()); - crate::optimizer::optimize_into_ast( self, scope, - statements, + ast.take_statements(), #[cfg(not(feature = "no_function"))] lib, optimization_level, diff --git a/src/ast.rs b/src/ast.rs deleted file mode 100644 index d0c7a68a..00000000 --- a/src/ast.rs +++ /dev/null @@ -1,2590 +0,0 @@ -//! Module defining the AST (abstract syntax tree). - -use crate::calc_fn_hash; -use crate::engine::{OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE}; -use crate::func::hashing::ALT_ZERO_HASH; -use crate::module::NamespaceRef; -use crate::tokenizer::Token; -use crate::types::dynamic::Union; -use crate::{ - Dynamic, FnNamespace, Identifier, ImmutableString, Module, Position, Shared, StaticVec, INT, -}; -#[cfg(feature = "no_std")] -use std::prelude::v1::*; -use std::{ - collections::BTreeMap, - fmt, - hash::Hash, - mem, - num::{NonZeroU8, NonZeroUsize}, - ops::{ - Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, Deref, DerefMut, Not, Sub, - SubAssign, - }, -}; - -#[cfg(not(feature = "no_float"))] -use std::str::FromStr; - -#[cfg(not(feature = "no_float"))] -use num_traits::Float; - -/// A type representing the access mode of a function. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub enum FnAccess { - /// Public function. - Public, - /// Private function. - Private, -} - -/// _(internals)_ A type containing information on a scripted function. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone)] -pub struct ScriptFnDef { - /// Function body. - pub body: StmtBlock, - /// Encapsulated running environment, if any. - pub lib: Option>, - /// Encapsulated imported modules. - /// - /// Not available under `no_module`. - #[cfg(not(feature = "no_module"))] - pub mods: crate::engine::Imports, - /// Function name. - pub name: Identifier, - /// Function access mode. - pub access: FnAccess, - /// Names of function parameters. - pub params: StaticVec, - /// _(metadata)_ Function doc-comments (if any). - /// Exported under the `metadata` feature only. - #[cfg(feature = "metadata")] - pub comments: Option]>>, -} - -impl fmt::Display for ScriptFnDef { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}{}({})", - match self.access { - FnAccess::Public => "", - FnAccess::Private => "private ", - }, - self.name, - self.params - .iter() - .map(|s| s.as_str()) - .collect::>() - .join(", ") - ) - } -} - -/// A type containing the metadata of a script-defined function. -/// -/// Not available under `no_function`. -/// -/// Created by [`AST::iter_functions`]. -#[cfg(not(feature = "no_function"))] -#[derive(Debug, Eq, PartialEq, Clone, Hash)] -pub struct ScriptFnMetadata<'a> { - /// _(metadata)_ Function doc-comments (if any). - /// Exported under the `metadata` feature only. - /// - /// Block doc-comments are kept in a single string slice with line-breaks within. - /// - /// Line doc-comments are kept in one string slice per line without the termination line-break. - /// - /// Leading white-spaces are stripped, and each string slice always starts with the corresponding - /// doc-comment leader: `///` or `/**`. - #[cfg(feature = "metadata")] - pub comments: Vec<&'a str>, - /// Function access mode. - pub access: FnAccess, - /// Function name. - pub name: &'a str, - /// Function parameters (if any). - pub params: Vec<&'a str>, -} - -#[cfg(not(feature = "no_function"))] -impl fmt::Display for ScriptFnMetadata<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}{}({})", - match self.access { - FnAccess::Public => "", - FnAccess::Private => "private ", - }, - self.name, - self.params - .iter() - .cloned() - .collect::>() - .join(", ") - ) - } -} - -#[cfg(not(feature = "no_function"))] -impl<'a> From<&'a ScriptFnDef> for ScriptFnMetadata<'a> { - #[inline] - fn from(value: &'a ScriptFnDef) -> Self { - Self { - #[cfg(not(feature = "no_function"))] - #[cfg(feature = "metadata")] - comments: value - .comments - .as_ref() - .map_or_else(|| Vec::new(), |v| v.iter().map(Box::as_ref).collect()), - access: value.access, - name: &value.name, - params: value.params.iter().map(|s| s.as_str()).collect(), - } - } -} - -#[cfg(not(feature = "no_function"))] -impl std::cmp::PartialOrd for ScriptFnMetadata<'_> { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -#[cfg(not(feature = "no_function"))] -impl std::cmp::Ord for ScriptFnMetadata<'_> { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - match self.name.cmp(other.name) { - std::cmp::Ordering::Equal => self.params.len().cmp(&other.params.len()), - cmp => cmp, - } - } -} - -/// Compiled AST (abstract syntax tree) of a Rhai script. -/// -/// # Thread Safety -/// -/// Currently, [`AST`] is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. -#[derive(Debug, Clone)] -pub struct AST { - /// Source of the [`AST`]. - source: Option, - /// Global statements. - body: StmtBlock, - /// Script-defined functions. - #[cfg(not(feature = "no_function"))] - functions: Shared, - /// Embedded module resolver, if any. - #[cfg(not(feature = "no_module"))] - resolver: Option>, -} - -impl Default for AST { - #[inline(always)] - fn default() -> Self { - Self::empty() - } -} - -impl AST { - /// Create a new [`AST`]. - #[cfg(not(feature = "internals"))] - #[inline(always)] - #[must_use] - pub(crate) fn new( - statements: impl IntoIterator, - #[cfg(not(feature = "no_function"))] functions: impl Into>, - ) -> Self { - Self { - source: None, - body: StmtBlock::new(statements, Position::NONE), - #[cfg(not(feature = "no_function"))] - functions: functions.into(), - #[cfg(not(feature = "no_module"))] - resolver: None, - } - } - /// _(internals)_ Create a new [`AST`]. - /// Exported under the `internals` feature only. - #[cfg(feature = "internals")] - #[inline(always)] - #[must_use] - pub fn new( - statements: impl IntoIterator, - #[cfg(not(feature = "no_function"))] functions: impl Into>, - ) -> Self { - Self { - source: None, - body: StmtBlock::new(statements, Position::NONE), - #[cfg(not(feature = "no_function"))] - functions: functions.into(), - #[cfg(not(feature = "no_module"))] - resolver: None, - } - } - /// Create a new [`AST`] with a source name. - #[cfg(not(feature = "internals"))] - #[inline(always)] - #[must_use] - pub(crate) fn new_with_source( - statements: impl IntoIterator, - #[cfg(not(feature = "no_function"))] functions: impl Into>, - source: impl Into, - ) -> Self { - let mut ast = Self::new( - statements, - #[cfg(not(feature = "no_function"))] - functions, - ); - ast.set_source(source); - ast - } - /// _(internals)_ Create a new [`AST`] with a source name. - /// Exported under the `internals` feature only. - #[cfg(feature = "internals")] - #[inline(always)] - #[must_use] - pub fn new_with_source( - statements: impl IntoIterator, - #[cfg(not(feature = "no_function"))] functions: impl Into>, - source: impl Into, - ) -> Self { - let mut ast = Self::new( - statements, - #[cfg(not(feature = "no_function"))] - functions, - ); - ast.set_source(source); - ast - } - /// Create an empty [`AST`]. - #[inline] - #[must_use] - pub fn empty() -> Self { - Self { - source: None, - body: StmtBlock::NONE, - #[cfg(not(feature = "no_function"))] - functions: Module::new().into(), - #[cfg(not(feature = "no_module"))] - resolver: None, - } - } - /// Get the source, if any. - #[inline(always)] - #[must_use] - pub fn source(&self) -> Option<&str> { - self.source.as_ref().map(|s| s.as_str()) - } - /// Get a reference to the source. - #[inline(always)] - #[must_use] - pub(crate) fn source_raw(&self) -> Option<&Identifier> { - self.source.as_ref() - } - /// Set the source. - #[inline] - pub fn set_source(&mut self, source: impl Into) -> &mut Self { - let source = source.into(); - #[cfg(not(feature = "no_function"))] - Shared::get_mut(&mut self.functions) - .as_mut() - .map(|m| m.set_id(source.clone())); - self.source = Some(source); - self - } - /// Clear the source. - #[inline(always)] - pub fn clear_source(&mut self) -> &mut Self { - self.source = None; - self - } - /// Get the statements. - #[cfg(not(feature = "internals"))] - #[inline(always)] - #[must_use] - pub(crate) fn statements(&self) -> &[Stmt] { - &self.body.0 - } - /// _(internals)_ Get the statements. - /// Exported under the `internals` feature only. - #[cfg(feature = "internals")] - #[inline(always)] - #[must_use] - pub fn statements(&self) -> &[Stmt] { - &self.body.0 - } - /// Get a mutable reference to the statements. - #[allow(dead_code)] - #[inline(always)] - #[must_use] - pub(crate) fn statements_mut(&mut self) -> &mut StaticVec { - &mut self.body.0 - } - /// Does this [`AST`] contain script-defined functions? - /// - /// Not available under `no_function`. - #[cfg(not(feature = "no_function"))] - #[inline(always)] - #[must_use] - pub fn has_functions(&self) -> bool { - !self.functions.is_empty() - } - /// Get the internal shared [`Module`] containing all script-defined functions. - #[cfg(not(feature = "internals"))] - #[cfg(not(feature = "no_function"))] - #[inline(always)] - #[must_use] - pub(crate) fn shared_lib(&self) -> &Shared { - &self.functions - } - /// _(internals)_ Get the internal shared [`Module`] containing all script-defined functions. - /// Exported under the `internals` feature only. - /// - /// Not available under `no_function`. - #[cfg(feature = "internals")] - #[cfg(not(feature = "no_function"))] - #[inline(always)] - #[must_use] - pub fn shared_lib(&self) -> &Shared { - &self.functions - } - /// Get the embedded [module resolver][`ModuleResolver`]. - #[cfg(not(feature = "internals"))] - #[cfg(not(feature = "no_module"))] - #[inline(always)] - #[must_use] - pub(crate) fn resolver( - &self, - ) -> Option<&Shared> { - self.resolver.as_ref() - } - /// _(internals)_ Get the embedded [module resolver][crate::ModuleResolver]. - /// Exported under the `internals` feature only. - /// - /// Not available under `no_module`. - #[cfg(feature = "internals")] - #[cfg(not(feature = "no_module"))] - #[inline(always)] - #[must_use] - pub fn resolver(&self) -> Option<&Shared> { - self.resolver.as_ref() - } - /// Set the embedded [module resolver][`ModuleResolver`]. - #[cfg(not(feature = "no_module"))] - #[inline(always)] - pub(crate) fn set_resolver( - &mut self, - resolver: impl Into>, - ) -> &mut Self { - self.resolver = Some(resolver.into()); - self - } - /// Clone the [`AST`]'s functions into a new [`AST`]. - /// No statements are cloned. - /// - /// Not available under `no_function`. - /// - /// This operation is cheap because functions are shared. - #[cfg(not(feature = "no_function"))] - #[inline(always)] - #[must_use] - pub fn clone_functions_only(&self) -> Self { - self.clone_functions_only_filtered(|_, _, _, _, _| true) - } - /// Clone the [`AST`]'s functions into a new [`AST`] based on a filter predicate. - /// No statements are cloned. - /// - /// Not available under `no_function`. - /// - /// This operation is cheap because functions are shared. - #[cfg(not(feature = "no_function"))] - #[inline] - #[must_use] - pub fn clone_functions_only_filtered( - &self, - filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, - ) -> Self { - let mut functions = Module::new(); - functions.merge_filtered(&self.functions, &filter); - Self { - source: self.source.clone(), - body: StmtBlock::NONE, - functions: functions.into(), - #[cfg(not(feature = "no_module"))] - resolver: self.resolver.clone(), - } - } - /// Clone the [`AST`]'s script statements into a new [`AST`]. - /// No functions are cloned. - #[inline(always)] - #[must_use] - pub fn clone_statements_only(&self) -> Self { - Self { - source: self.source.clone(), - body: self.body.clone(), - #[cfg(not(feature = "no_function"))] - functions: Module::new().into(), - #[cfg(not(feature = "no_module"))] - resolver: self.resolver.clone(), - } - } - /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, - /// version is returned. - /// - /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. - /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. - /// Of course, if the first [`AST`] uses a `return` statement at the end, then - /// the second [`AST`] will essentially be dead code. - /// - /// All script-defined functions in the second [`AST`] overwrite similarly-named functions - /// in the first [`AST`] with the same number of parameters. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// let ast1 = engine.compile(" - /// fn foo(x) { 42 + x } - /// foo(1) - /// ")?; - /// - /// let ast2 = engine.compile(r#" - /// fn foo(n) { `hello${n}` } - /// foo("!") - /// "#)?; - /// - /// let ast = ast1.merge(&ast2); // Merge 'ast2' into 'ast1' - /// - /// // Notice that using the '+' operator also works: - /// // let ast = &ast1 + &ast2; - /// - /// // 'ast' is essentially: - /// // - /// // fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten - /// // foo(1) // <- notice this will be "hello1" instead of 43, - /// // // but it is no longer the return value - /// // foo("!") // returns "hello!" - /// - /// // Evaluate it - /// assert_eq!(engine.eval_ast::(&ast)?, "hello!"); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - #[must_use] - pub fn merge(&self, other: &Self) -> Self { - self.merge_filtered_impl(other, |_, _, _, _, _| true) - } - /// Combine one [`AST`] with another. The second [`AST`] is consumed. - /// - /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. - /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. - /// Of course, if the first [`AST`] uses a `return` statement at the end, then - /// the second [`AST`] will essentially be dead code. - /// - /// All script-defined functions in the second [`AST`] overwrite similarly-named functions - /// in the first [`AST`] with the same number of parameters. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// let mut ast1 = engine.compile(" - /// fn foo(x) { 42 + x } - /// foo(1) - /// ")?; - /// - /// let ast2 = engine.compile(r#" - /// fn foo(n) { `hello${n}` } - /// foo("!") - /// "#)?; - /// - /// ast1.combine(ast2); // Combine 'ast2' into 'ast1' - /// - /// // Notice that using the '+=' operator also works: - /// // ast1 += ast2; - /// - /// // 'ast1' is essentially: - /// // - /// // fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten - /// // foo(1) // <- notice this will be "hello1" instead of 43, - /// // // but it is no longer the return value - /// // foo("!") // returns "hello!" - /// - /// // Evaluate it - /// assert_eq!(engine.eval_ast::(&ast1)?, "hello!"); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn combine(&mut self, other: Self) -> &mut Self { - self.combine_filtered_impl(other, |_, _, _, _, _| true) - } - /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version - /// is returned. - /// - /// Not available under `no_function`. - /// - /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. - /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. - /// Of course, if the first [`AST`] uses a `return` statement at the end, then - /// the second [`AST`] will essentially be dead code. - /// - /// All script-defined functions in the second [`AST`] are first selected based on a filter - /// predicate, then overwrite similarly-named functions in the first [`AST`] with the - /// same number of parameters. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// let ast1 = engine.compile(" - /// fn foo(x) { 42 + x } - /// foo(1) - /// ")?; - /// - /// let ast2 = engine.compile(r#" - /// fn foo(n) { `hello${n}` } - /// fn error() { 0 } - /// foo("!") - /// "#)?; - /// - /// // Merge 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1' - /// let ast = ast1.merge_filtered(&ast2, |_, _, script, name, params| - /// script && name == "error" && params == 0); - /// - /// // 'ast' is essentially: - /// // - /// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten - /// // // because 'ast2::foo' is filtered away - /// // foo(1) // <- notice this will be 43 instead of "hello1", - /// // // but it is no longer the return value - /// // fn error() { 0 } // <- this function passes the filter and is merged - /// // foo("!") // <- returns "42!" - /// - /// // Evaluate it - /// assert_eq!(engine.eval_ast::(&ast)?, "42!"); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_function"))] - #[inline(always)] - #[must_use] - pub fn merge_filtered( - &self, - other: &Self, - filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, - ) -> Self { - self.merge_filtered_impl(other, filter) - } - /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version - /// is returned. - #[inline] - #[must_use] - fn merge_filtered_impl( - &self, - other: &Self, - _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, - ) -> Self { - let merged = match (self.body.is_empty(), other.body.is_empty()) { - (false, false) => { - let mut body = self.body.clone(); - body.0.extend(other.body.0.iter().cloned()); - body - } - (false, true) => self.body.clone(), - (true, false) => other.body.clone(), - (true, true) => StmtBlock::NONE, - }; - - let source = other.source.clone().or_else(|| self.source.clone()); - - #[cfg(not(feature = "no_function"))] - let functions = { - let mut functions = self.functions.as_ref().clone(); - functions.merge_filtered(&other.functions, &_filter); - functions - }; - - if let Some(source) = source { - Self::new_with_source( - merged.0, - #[cfg(not(feature = "no_function"))] - functions, - source, - ) - } else { - Self::new( - merged.0, - #[cfg(not(feature = "no_function"))] - functions, - ) - } - } - /// Combine one [`AST`] with another. The second [`AST`] is consumed. - /// - /// Not available under `no_function`. - /// - /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. - /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. - /// Of course, if the first [`AST`] uses a `return` statement at the end, then - /// the second [`AST`] will essentially be dead code. - /// - /// All script-defined functions in the second [`AST`] are first selected based on a filter - /// predicate, then overwrite similarly-named functions in the first [`AST`] with the - /// same number of parameters. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// let mut ast1 = engine.compile(" - /// fn foo(x) { 42 + x } - /// foo(1) - /// ")?; - /// - /// let ast2 = engine.compile(r#" - /// fn foo(n) { `hello${n}` } - /// fn error() { 0 } - /// foo("!") - /// "#)?; - /// - /// // Combine 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1' - /// ast1.combine_filtered(ast2, |_, _, script, name, params| - /// script && name == "error" && params == 0); - /// - /// // 'ast1' is essentially: - /// // - /// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten - /// // // because 'ast2::foo' is filtered away - /// // foo(1) // <- notice this will be 43 instead of "hello1", - /// // // but it is no longer the return value - /// // fn error() { 0 } // <- this function passes the filter and is merged - /// // foo("!") // <- returns "42!" - /// - /// // Evaluate it - /// assert_eq!(engine.eval_ast::(&ast1)?, "42!"); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_function"))] - #[inline(always)] - pub fn combine_filtered( - &mut self, - other: Self, - filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, - ) -> &mut Self { - self.combine_filtered_impl(other, filter) - } - /// Combine one [`AST`] with another. The second [`AST`] is consumed. - #[inline] - fn combine_filtered_impl( - &mut self, - other: Self, - _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, - ) -> &mut Self { - self.body.0.extend(other.body.0.into_iter()); - - #[cfg(not(feature = "no_function"))] - if !other.functions.is_empty() { - crate::func::native::shared_make_mut(&mut self.functions) - .merge_filtered(&other.functions, &_filter); - } - self - } - /// Filter out the functions, retaining only some based on a filter predicate. - /// - /// Not available under `no_function`. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// let mut ast = engine.compile(r#" - /// fn foo(n) { n + 1 } - /// fn bar() { print("hello"); } - /// "#)?; - /// - /// // Remove all functions except 'foo(_)' - /// ast.retain_functions(|_, _, name, params| name == "foo" && params == 1); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_function"))] - #[inline] - pub fn retain_functions( - &mut self, - filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool, - ) -> &mut Self { - if !self.functions.is_empty() { - crate::func::native::shared_make_mut(&mut self.functions) - .retain_script_functions(filter); - } - self - } - /// Iterate through all function definitions. - /// - /// Not available under `no_function`. - #[cfg(not(feature = "no_function"))] - #[allow(dead_code)] - #[inline] - pub(crate) fn iter_fn_def(&self) -> impl Iterator { - self.functions - .iter_script_fn() - .map(|(_, _, _, _, fn_def)| fn_def.as_ref()) - } - /// Iterate through all function definitions. - /// - /// Not available under `no_function`. - #[cfg(not(feature = "no_function"))] - #[inline] - pub fn iter_functions<'a>(&'a self) -> impl Iterator + 'a { - self.functions - .iter_script_fn() - .map(|(_, _, _, _, fn_def)| fn_def.as_ref().into()) - } - /// Clear all function definitions in the [`AST`]. - /// - /// Not available under `no_function`. - #[cfg(not(feature = "no_function"))] - #[inline(always)] - pub fn clear_functions(&mut self) -> &mut Self { - 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 = StmtBlock::NONE; - self - } - /// Extract all top-level literal constant and/or variable definitions. - /// This is useful for extracting all global constants from a script without actually running it. - /// - /// A literal constant/variable definition takes the form of: - /// `const VAR = `_value_`;` and `let VAR = `_value_`;` - /// where _value_ is a literal expression or will be optimized into a literal. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Scope}; - /// - /// let engine = Engine::new(); - /// - /// let ast = engine.compile( - /// " - /// const A = 40 + 2; // constant that optimizes into a literal - /// let b = 123; // literal variable - /// const B = b * A; // non-literal constant - /// const C = 999; // literal constant - /// b = A + C; // expression - /// - /// { // <- new block scope - /// const Z = 0; // <- literal constant not at top-level - /// } - /// ")?; - /// - /// let mut iter = ast.iter_literal_variables(true, false) - /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); - /// - /// # #[cfg(not(feature = "no_optimize"))] - /// assert_eq!(iter.next(), Some(("A", true, 42))); - /// assert_eq!(iter.next(), Some(("C", true, 999))); - /// assert_eq!(iter.next(), None); - /// - /// let mut iter = ast.iter_literal_variables(false, true) - /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); - /// - /// assert_eq!(iter.next(), Some(("b", false, 123))); - /// assert_eq!(iter.next(), None); - /// - /// let mut iter = ast.iter_literal_variables(true, true) - /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); - /// - /// # #[cfg(not(feature = "no_optimize"))] - /// assert_eq!(iter.next(), Some(("A", true, 42))); - /// assert_eq!(iter.next(), Some(("b", false, 123))); - /// assert_eq!(iter.next(), Some(("C", true, 999))); - /// assert_eq!(iter.next(), None); - /// - /// let scope: Scope = ast.iter_literal_variables(true, false).collect(); - /// - /// # #[cfg(not(feature = "no_optimize"))] - /// assert_eq!(scope.len(), 2); - /// - /// Ok(()) - /// # } - /// ``` - pub fn iter_literal_variables( - &self, - include_constants: bool, - include_variables: bool, - ) -> impl Iterator { - self.statements().iter().filter_map(move |stmt| match stmt { - Stmt::Var(expr, name, options, _) - if options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT) && include_constants - || !options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT) - && include_variables => - { - if let Some(value) = expr.get_literal_value() { - Some(( - name.as_str(), - options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT), - value, - )) - } else { - None - } - } - _ => None, - }) - } - /// Recursively walk the [`AST`], including function bodies (if any). - /// Return `false` from the callback to terminate the walk. - #[cfg(not(feature = "internals"))] - #[cfg(not(feature = "no_module"))] - #[inline] - pub(crate) fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool { - let path = &mut Vec::new(); - - for stmt in self.statements() { - if !stmt.walk(path, on_node) { - return false; - } - } - #[cfg(not(feature = "no_function"))] - for stmt in self.iter_fn_def().flat_map(|f| f.body.0.iter()) { - if !stmt.walk(path, on_node) { - return false; - } - } - - true - } - /// _(internals)_ Recursively walk the [`AST`], including function bodies (if any). - /// Return `false` from the callback to terminate the walk. - /// Exported under the `internals` feature only. - #[cfg(feature = "internals")] - #[inline] - pub fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool { - let path = &mut Vec::new(); - - for stmt in self.statements() { - if !stmt.walk(path, on_node) { - return false; - } - } - #[cfg(not(feature = "no_function"))] - for stmt in self.iter_fn_def().flat_map(|f| f.body.0.iter()) { - if !stmt.walk(path, on_node) { - return false; - } - } - - true - } -} - -impl> Add for &AST { - type Output = AST; - - #[inline(always)] - fn add(self, rhs: A) -> Self::Output { - self.merge(rhs.as_ref()) - } -} - -impl> AddAssign for AST { - #[inline(always)] - fn add_assign(&mut self, rhs: A) { - self.combine(rhs.into()); - } -} - -impl AsRef<[Stmt]> for AST { - #[inline(always)] - fn as_ref(&self) -> &[Stmt] { - self.statements() - } -} - -#[cfg(not(feature = "no_function"))] -impl AsRef for AST { - #[inline(always)] - fn as_ref(&self) -> &Module { - self.shared_lib().as_ref() - } -} - -#[cfg(not(feature = "no_function"))] -impl AsRef> for AST { - #[inline(always)] - fn as_ref(&self) -> &Shared { - self.shared_lib() - } -} - -/// _(internals)_ An identifier containing a name and a [position][Position]. -/// Exported under the `internals` feature only. -#[derive(Clone, Eq, PartialEq, Hash)] -pub struct Ident { - /// Identifier name. - pub name: Identifier, - /// Position. - pub pos: Position, -} - -impl fmt::Debug for Ident { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self.name)?; - self.pos.debug_print(f) - } -} - -impl AsRef for Ident { - #[inline(always)] - fn as_ref(&self) -> &str { - self.name.as_ref() - } -} - -impl Ident { - #[inline(always)] - pub fn as_str(&self) -> &str { - self.name.as_str() - } -} - -/// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`]. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone, Hash)] -pub enum ASTNode<'a> { - /// A statement ([`Stmt`]). - Stmt(&'a Stmt), - /// An expression ([`Expr`]). - Expr(&'a Expr), -} - -impl<'a> From<&'a Stmt> for ASTNode<'a> { - fn from(stmt: &'a Stmt) -> Self { - Self::Stmt(stmt) - } -} - -impl<'a> From<&'a Expr> for ASTNode<'a> { - fn from(expr: &'a Expr) -> Self { - Self::Expr(expr) - } -} - -impl ASTNode<'_> { - /// Get the [`Position`] of this [`ASTNode`]. - pub const fn position(&self) -> Position { - match self { - ASTNode::Stmt(stmt) => stmt.position(), - ASTNode::Expr(expr) => expr.position(), - } - } -} - -/// _(internals)_ A scoped block of statements. -/// Exported under the `internals` feature only. -#[derive(Clone, Hash, Default)] -pub struct StmtBlock(StaticVec, Position); - -impl StmtBlock { - /// A [`StmtBlock`] that does not exist. - pub const NONE: Self = Self::empty(Position::NONE); - - /// Create a new [`StmtBlock`]. - #[must_use] - pub fn new(statements: impl IntoIterator, pos: Position) -> Self { - let mut statements: StaticVec<_> = statements.into_iter().collect(); - statements.shrink_to_fit(); - Self(statements, pos) - } - /// Create an empty [`StmtBlock`]. - #[inline(always)] - #[must_use] - pub const fn empty(pos: Position) -> Self { - Self(StaticVec::new_const(), pos) - } - /// Is this statements block empty? - #[inline(always)] - #[must_use] - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - /// Number of statements in this statements block. - #[inline(always)] - #[must_use] - pub fn len(&self) -> usize { - self.0.len() - } - /// Get the statements of this statements block. - #[inline(always)] - #[must_use] - pub fn statements(&self) -> &[Stmt] { - &self.0 - } - /// Get the position (location of the beginning `{`) of this statements block. - #[inline(always)] - #[must_use] - pub const fn position(&self) -> Position { - self.1 - } -} - -impl Deref for StmtBlock { - type Target = StaticVec; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for StmtBlock { - #[inline(always)] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl fmt::Debug for StmtBlock { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Block")?; - fmt::Debug::fmt(&self.0, f)?; - self.1.debug_print(f) - } -} - -impl From for Stmt { - #[inline] - fn from(block: StmtBlock) -> Self { - let block_pos = block.position(); - Self::Block(block.0.into_boxed_slice(), block_pos) - } -} - -/// A type that holds a configuration option with bit-flags. -/// Exported under the `internals` feature only. -#[derive(PartialEq, Eq, Copy, Clone, Hash, Default)] -pub struct OptionFlags(u8); - -impl OptionFlags { - /// Does this [`OptionFlags`] contain a particular option flag? - #[inline(always)] - #[must_use] - pub const fn contains(self, flag: Self) -> bool { - self.0 & flag.0 != 0 - } -} - -impl Not for OptionFlags { - type Output = Self; - - /// Return the negation of the [`OptionFlags`]. - #[inline(always)] - fn not(self) -> Self::Output { - Self(!self.0) & AST_OPTION_FLAGS::AST_OPTION_ALL - } -} - -impl Add for OptionFlags { - type Output = Self; - - /// Return the union of two [`OptionFlags`]. - #[inline(always)] - fn add(self, rhs: Self) -> Self::Output { - Self(self.0 | rhs.0) - } -} - -impl AddAssign for OptionFlags { - /// Add the option flags in one [`OptionFlags`] to another. - #[inline(always)] - fn add_assign(&mut self, rhs: Self) { - self.0 |= rhs.0 - } -} - -impl BitOr for OptionFlags { - type Output = Self; - - /// Return the union of two [`OptionFlags`]. - #[inline(always)] - fn bitor(self, rhs: Self) -> Self::Output { - Self(self.0 | rhs.0) - } -} - -impl BitOrAssign for OptionFlags { - /// Add the option flags in one [`OptionFlags`] to another. - #[inline(always)] - fn bitor_assign(&mut self, rhs: Self) { - self.0 |= rhs.0 - } -} - -impl Sub for OptionFlags { - type Output = Self; - - /// Return the difference of two [`OptionFlags`]. - #[inline(always)] - fn sub(self, rhs: Self) -> Self::Output { - Self(self.0 & !rhs.0) - } -} - -impl SubAssign for OptionFlags { - /// Remove the option flags in one [`OptionFlags`] from another. - #[inline(always)] - fn sub_assign(&mut self, rhs: Self) { - self.0 &= !rhs.0 - } -} - -impl BitAnd for OptionFlags { - type Output = Self; - - /// Return the intersection of two [`OptionFlags`]. - #[inline(always)] - fn bitand(self, rhs: Self) -> Self::Output { - Self(self.0 & !rhs.0) - } -} - -impl BitAndAssign for OptionFlags { - /// Keep only the intersection of one [`OptionFlags`] with another. - #[inline(always)] - fn bitand_assign(&mut self, rhs: Self) { - self.0 &= !rhs.0 - } -} - -/// Option bit-flags for [`AST`] nodes. -#[allow(non_snake_case)] -pub mod AST_OPTION_FLAGS { - use super::OptionFlags; - - /// _(internals)_ No options for the [`AST`][crate::AST] node. - /// Exported under the `internals` feature only. - pub const AST_OPTION_NONE: OptionFlags = OptionFlags(0b0000_0000); - /// _(internals)_ The [`AST`][crate::AST] node is constant. - /// Exported under the `internals` feature only. - pub const AST_OPTION_CONSTANT: OptionFlags = OptionFlags(0b0000_0001); - /// _(internals)_ The [`AST`][crate::AST] node is public. - /// Exported under the `internals` feature only. - pub const AST_OPTION_PUBLIC: OptionFlags = OptionFlags(0b0000_0010); - /// _(internals)_ The [`AST`][crate::AST] node is in negated mode. - /// Exported under the `internals` feature only. - pub const AST_OPTION_NEGATED: OptionFlags = OptionFlags(0b0000_0100); - /// _(internals)_ The [`AST`][crate::AST] node breaks out of normal control flow. - /// Exported under the `internals` feature only. - pub const AST_OPTION_BREAK_OUT: OptionFlags = OptionFlags(0b0000_1000); - /// _(internals)_ Mask of all options. - /// Exported under the `internals` feature only. - pub(crate) const AST_OPTION_ALL: OptionFlags = OptionFlags( - AST_OPTION_CONSTANT.0 | AST_OPTION_PUBLIC.0 | AST_OPTION_NEGATED.0 | AST_OPTION_BREAK_OUT.0, - ); - - impl std::fmt::Debug for OptionFlags { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - fn write_option( - options: &OptionFlags, - f: &mut std::fmt::Formatter<'_>, - num_flags: &mut usize, - flag: OptionFlags, - name: &str, - ) -> std::fmt::Result { - if options.contains(flag) { - if *num_flags > 0 { - f.write_str("+")?; - } - f.write_str(name)?; - *num_flags += 1; - } - Ok(()) - } - - let num_flags = &mut 0; - - f.write_str("(")?; - write_option(self, f, num_flags, AST_OPTION_CONSTANT, "Constant")?; - write_option(self, f, num_flags, AST_OPTION_PUBLIC, "Public")?; - write_option(self, f, num_flags, AST_OPTION_NEGATED, "Negated")?; - write_option(self, f, num_flags, AST_OPTION_BREAK_OUT, "Break")?; - f.write_str(")")?; - - Ok(()) - } - } -} - -/// _(internals)_ A statement. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone, Hash)] -pub enum Stmt { - /// No-op. - Noop(Position), - /// `if` expr `{` stmt `}` `else` `{` stmt `}` - If(Expr, Box<(StmtBlock, StmtBlock)>, Position), - /// `switch` expr `if` condition `{` literal or _ `=>` stmt `,` ... `}` - Switch( - Expr, - Box<( - BTreeMap, StmtBlock)>>, - StmtBlock, - StaticVec<(INT, INT, bool, Option, StmtBlock)>, - )>, - Position, - ), - /// `while` expr `{` stmt `}` | `loop` `{` stmt `}` - /// - /// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement. - While(Expr, Box, Position), - /// `do` `{` stmt `}` `while`|`until` expr - /// - /// ### Option Flags - /// - /// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `while` - /// * [`AST_OPTION_NEGATED`][AST_OPTION_FLAGS::AST_OPTION_NEGATED] = `until` - Do(Box, Expr, OptionFlags, Position), - /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` - For(Expr, Box<(Ident, Option, StmtBlock)>, Position), - /// \[`export`\] `let`|`const` id `=` expr - /// - /// ### Option Flags - /// - /// * [`AST_OPTION_PUBLIC`][AST_OPTION_FLAGS::AST_OPTION_PUBLIC] = `export` - /// * [`AST_OPTION_CONSTANT`][AST_OPTION_FLAGS::AST_OPTION_CONSTANT] = `const` - Var(Expr, Box, OptionFlags, Position), - /// expr op`=` expr - Assignment(Box<(Expr, Option>, Expr)>, Position), - /// func `(` expr `,` ... `)` - /// - /// Note - this is a duplicate of [`Expr::FnCall`] to cover the very common pattern of a single - /// function call forming one statement. - FnCall(Box, Position), - /// `{` stmt`;` ... `}` - Block(Box<[Stmt]>, Position), - /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` - TryCatch(Box<(StmtBlock, Option, StmtBlock)>, Position), - /// [expression][Expr] - Expr(Expr), - /// `continue`/`break` - /// - /// ### Option Flags - /// - /// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `continue` - /// * [`AST_OPTION_BREAK_OUT`][AST_OPTION_FLAGS::AST_OPTION_BREAK_OUT] = `break` - BreakLoop(OptionFlags, Position), - /// `return`/`throw` - /// - /// ### Option Flags - /// - /// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `return` - /// * [`AST_OPTION_BREAK_OUT`][AST_OPTION_FLAGS::AST_OPTION_BREAK_OUT] = `throw` - Return(OptionFlags, Option, Position), - /// `import` expr `as` var - /// - /// Not available under `no_module`. - #[cfg(not(feature = "no_module"))] - Import(Expr, Option>, Position), - /// `export` var `as` var `,` ... - /// - /// Not available under `no_module`. - #[cfg(not(feature = "no_module"))] - Export(Box<[(Ident, Ident)]>, Position), - /// Convert a variable to shared. - /// - /// Not available under `no_closure`. - /// - /// # Notes - /// - /// This variant does not map to any language structure. It is currently only used only to - /// convert a normal variable into a shared variable when the variable is _captured_ by a closure. - #[cfg(not(feature = "no_closure"))] - Share(Identifier), -} - -impl Default for Stmt { - #[inline(always)] - fn default() -> Self { - Self::Noop(Position::NONE) - } -} - -impl From for StmtBlock { - #[inline] - 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(StaticVec::new_const(), pos), - _ => { - let pos = stmt.position(); - Self(vec![stmt].into(), pos) - } - } - } -} - -impl Stmt { - /// Is this statement [`Noop`][Stmt::Noop]? - #[inline(always)] - #[must_use] - pub const fn is_noop(&self) -> bool { - matches!(self, Self::Noop(_)) - } - /// Get the [position][Position] of this statement. - #[must_use] - pub const fn position(&self) -> Position { - match self { - Self::Noop(pos) - | Self::BreakLoop(_, pos) - | Self::Block(_, pos) - | Self::Assignment(_, pos) - | Self::FnCall(_, pos) - | Self::If(_, _, pos) - | Self::Switch(_, _, pos) - | Self::While(_, _, pos) - | Self::Do(_, _, _, pos) - | Self::For(_, _, pos) - | Self::Return(_, _, pos) - | Self::Var(_, _, _, pos) - | Self::TryCatch(_, pos) => *pos, - - Self::Expr(x) => x.position(), - - #[cfg(not(feature = "no_module"))] - Self::Import(_, _, pos) => *pos, - #[cfg(not(feature = "no_module"))] - Self::Export(_, pos) => *pos, - - #[cfg(not(feature = "no_closure"))] - Self::Share(_) => Position::NONE, - } - } - /// Override the [position][Position] of this statement. - pub fn set_position(&mut self, new_pos: Position) -> &mut Self { - match self { - Self::Noop(pos) - | Self::BreakLoop(_, pos) - | Self::Block(_, pos) - | Self::Assignment(_, pos) - | Self::FnCall(_, pos) - | Self::If(_, _, pos) - | Self::Switch(_, _, pos) - | Self::While(_, _, pos) - | Self::Do(_, _, _, pos) - | Self::For(_, _, pos) - | Self::Return(_, _, pos) - | Self::Var(_, _, _, pos) - | Self::TryCatch(_, pos) => *pos = new_pos, - - Self::Expr(x) => { - x.set_position(new_pos); - } - - #[cfg(not(feature = "no_module"))] - Self::Import(_, _, pos) => *pos = new_pos, - #[cfg(not(feature = "no_module"))] - Self::Export(_, pos) => *pos = new_pos, - - #[cfg(not(feature = "no_closure"))] - Self::Share(_) => (), - } - - self - } - /// Does this statement return a value? - #[must_use] - pub const fn returns_value(&self) -> bool { - match self { - Self::If(_, _, _) - | Self::Switch(_, _, _) - | Self::Block(_, _) - | Self::Expr(_) - | Self::FnCall(_, _) => true, - - Self::Noop(_) - | Self::While(_, _, _) - | Self::Do(_, _, _, _) - | Self::For(_, _, _) - | Self::TryCatch(_, _) => false, - - Self::Var(_, _, _, _) - | Self::Assignment(_, _) - | Self::BreakLoop(_, _) - | Self::Return(_, _, _) => false, - - #[cfg(not(feature = "no_module"))] - Self::Import(_, _, _) | Self::Export(_, _) => false, - - #[cfg(not(feature = "no_closure"))] - Self::Share(_) => false, - } - } - /// Is this statement self-terminated (i.e. no need for a semicolon terminator)? - #[must_use] - pub const fn is_self_terminated(&self) -> bool { - match self { - Self::If(_, _, _) - | Self::Switch(_, _, _) - | Self::While(_, _, _) - | Self::For(_, _, _) - | Self::Block(_, _) - | Self::TryCatch(_, _) => true, - - // A No-op requires a semicolon in order to know it is an empty statement! - Self::Noop(_) => false, - - Self::Expr(Expr::Custom(x, _)) if x.is_self_terminated() => true, - - Self::Var(_, _, _, _) - | Self::Assignment(_, _) - | Self::Expr(_) - | Self::FnCall(_, _) - | Self::Do(_, _, _, _) - | Self::BreakLoop(_, _) - | Self::Return(_, _, _) => false, - - #[cfg(not(feature = "no_module"))] - Self::Import(_, _, _) | Self::Export(_, _) => false, - - #[cfg(not(feature = "no_closure"))] - Self::Share(_) => false, - } - } - /// Is this statement _pure_? - /// - /// A pure statement has no side effects. - #[must_use] - pub fn is_pure(&self) -> bool { - match self { - Self::Noop(_) => true, - Self::Expr(expr) => expr.is_pure(), - Self::If(condition, x, _) => { - condition.is_pure() - && (x.0).0.iter().all(Stmt::is_pure) - && (x.1).0.iter().all(Stmt::is_pure) - } - Self::Switch(expr, x, _) => { - expr.is_pure() - && x.0.values().all(|block| { - block.0.as_ref().map(Expr::is_pure).unwrap_or(true) - && (block.1).0.iter().all(Stmt::is_pure) - }) - && (x.2).iter().all(|(_, _, _, condition, stmt)| { - condition.as_ref().map(Expr::is_pure).unwrap_or(true) - && stmt.0.iter().all(Stmt::is_pure) - }) - && (x.1).0.iter().all(Stmt::is_pure) - } - - // Loops that exit can be pure because it can never be infinite. - Self::While(Expr::BoolConstant(false, _), _, _) => true, - Self::Do(body, Expr::BoolConstant(x, _), options, _) - if *x == options.contains(AST_OPTION_FLAGS::AST_OPTION_NEGATED) => - { - body.iter().all(Stmt::is_pure) - } - - // Loops are never pure since they can be infinite - and that's a side effect. - Self::While(_, _, _) | Self::Do(_, _, _, _) => false, - - // For loops can be pure because if the iterable is pure, it is finite, - // so infinite loops can never occur. - Self::For(iterable, x, _) => iterable.is_pure() && (x.2).0.iter().all(Stmt::is_pure), - - Self::Var(_, _, _, _) | Self::Assignment(_, _) | Self::FnCall(_, _) => false, - Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), - Self::BreakLoop(_, _) | Self::Return(_, _, _) => false, - Self::TryCatch(x, _) => { - (x.0).0.iter().all(Stmt::is_pure) && (x.2).0.iter().all(Stmt::is_pure) - } - - #[cfg(not(feature = "no_module"))] - Self::Import(_, _, _) => false, - #[cfg(not(feature = "no_module"))] - Self::Export(_, _) => false, - - #[cfg(not(feature = "no_closure"))] - Self::Share(_) => false, - } - } - /// Is this statement _pure_ within the containing block? - /// - /// An internally pure statement only has side effects that disappear outside the block. - /// - /// Currently only variable definitions (i.e. `let` and `const`) and `import`/`export` - /// statements are internally pure. - #[inline] - #[must_use] - pub fn is_internally_pure(&self) -> bool { - match self { - Self::Var(expr, _, _, _) => expr.is_pure(), - - #[cfg(not(feature = "no_module"))] - Self::Import(expr, _, _) => expr.is_pure(), - #[cfg(not(feature = "no_module"))] - Self::Export(_, _) => true, - - _ => self.is_pure(), - } - } - /// Does this statement break the current control flow through the containing block? - /// - /// Currently this is only true for `return`, `throw`, `break` and `continue`. - /// - /// All statements following this statement will essentially be dead code. - #[inline] - #[must_use] - pub const fn is_control_flow_break(&self) -> bool { - match self { - Self::Return(_, _, _) | Self::BreakLoop(_, _) => true, - _ => false, - } - } - /// Recursively walk this statement. - /// Return `false` from the callback to terminate the walk. - pub fn walk<'a>( - &'a self, - path: &mut Vec>, - on_node: &mut impl FnMut(&[ASTNode]) -> bool, - ) -> bool { - // Push the current node onto the path - path.push(self.into()); - - if !on_node(path) { - return false; - } - - match self { - Self::Var(e, _, _, _) => { - if !e.walk(path, on_node) { - return false; - } - } - Self::If(e, x, _) => { - if !e.walk(path, on_node) { - return false; - } - for s in &(x.0).0 { - if !s.walk(path, on_node) { - return false; - } - } - for s in &(x.1).0 { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::Switch(e, x, _) => { - if !e.walk(path, on_node) { - return false; - } - for b in x.0.values() { - if !b.0.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) { - return false; - } - for s in &(b.1).0 { - if !s.walk(path, on_node) { - return false; - } - } - } - for (_, _, _, c, stmt) in &x.2 { - if !c.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) { - return false; - } - for s in &stmt.0 { - if !s.walk(path, on_node) { - return false; - } - } - } - for s in &(x.1).0 { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::While(e, s, _) | Self::Do(s, e, _, _) => { - if !e.walk(path, on_node) { - return false; - } - for s in &s.0 { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::For(e, x, _) => { - if !e.walk(path, on_node) { - return false; - } - for s in &(x.2).0 { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::Assignment(x, _) => { - if !x.0.walk(path, on_node) { - return false; - } - if !x.2.walk(path, on_node) { - return false; - } - } - Self::FnCall(x, _) => { - for s in &x.args { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::Block(x, _) => { - for s in x.iter() { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::TryCatch(x, _) => { - for s in &(x.0).0 { - if !s.walk(path, on_node) { - return false; - } - } - for s in &(x.2).0 { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::Expr(e) | Self::Return(_, Some(e), _) => { - if !e.walk(path, on_node) { - return false; - } - } - #[cfg(not(feature = "no_module"))] - Self::Import(e, _, _) => { - if !e.walk(path, on_node) { - return false; - } - } - _ => (), - } - - path.pop().expect("contains current node"); - - true - } -} - -/// _(internals)_ A custom syntax expression. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone, Hash)] -pub struct CustomExpr { - /// List of keywords. - pub inputs: StaticVec, - /// List of tokens actually parsed. - pub tokens: 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, - /// Is this custom syntax self-terminated? - pub self_terminated: bool, -} - -impl CustomExpr { - /// Is this custom syntax self-terminated (i.e. no need for a semicolon terminator)? - /// - /// A self-terminated custom syntax always ends in `$block$`, `}` or `;` - #[must_use] - #[inline(always)] - pub const fn is_self_terminated(&self) -> bool { - self.self_terminated - } -} - -/// _(internals)_ A binary expression. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone, Hash)] -pub struct BinaryExpr { - /// LHS expression. - pub lhs: Expr, - /// RHS expression. - pub rhs: Expr, -} - -/// _(internals)_ An op-assignment operator. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub struct OpAssignment<'a> { - /// Hash of the op-assignment call. - pub hash_op_assign: u64, - /// Hash of the underlying operator call (for fallback). - pub hash_op: u64, - /// Op-assignment operator. - pub op: &'a str, -} - -impl OpAssignment<'_> { - /// Create a new [`OpAssignment`]. - /// - /// # Panics - /// - /// Panics if the operator name is not an op-assignment operator. - #[must_use] - pub fn new(op: Token) -> Self { - let op_raw = op - .map_op_assignment() - .expect("op-assignment") - .literal_syntax(); - let op_assignment = op.literal_syntax(); - - Self { - hash_op_assign: calc_fn_hash(op_assignment, 2), - hash_op: calc_fn_hash(op_raw, 2), - op: op_assignment, - } - } -} - -/// _(internals)_ A set of function call hashes. Exported under the `internals` feature only. -/// -/// Two separate hashes are pre-calculated because of the following patterns: -/// -/// ```ignore -/// func(a, b, c); // Native: func(a, b, c) - 3 parameters -/// // Script: func(a, b, c) - 3 parameters -/// -/// a.func(b, c); // Native: func(&mut a, b, c) - 3 parameters -/// // Script: func(b, c) - 2 parameters -/// ``` -/// -/// For normal function calls, the native hash equals the script hash. -/// -/// For method-style calls, the script hash contains one fewer parameter. -/// -/// Function call hashes are used in the following manner: -/// -/// * First, the script hash is tried, which contains only the called function's name plus the -/// number of parameters. -/// -/// * Next, the actual types of arguments are hashed and _combined_ with the native hash, which is -/// then used to search for a native function. In other words, a complete native function call -/// hash always contains the called function's name plus the types of the arguments. This is due -/// to possible function overloading for different parameter types. -#[derive(Clone, Copy, Eq, PartialEq, Hash, Default)] -pub struct FnCallHashes { - /// Pre-calculated hash for a script-defined function (zero if native functions only). - #[cfg(not(feature = "no_function"))] - pub script: u64, - /// Pre-calculated hash for a native Rust function with no parameter types. - pub native: u64, -} - -impl fmt::Debug for FnCallHashes { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - #[cfg(not(feature = "no_function"))] - if self.script != 0 { - return if self.script == self.native { - fmt::Debug::fmt(&self.native, f) - } else { - write!(f, "({}, {})", self.script, self.native) - }; - } - - write!(f, "{} (native only)", self.native) - } -} - -impl From for FnCallHashes { - #[inline(always)] - fn from(hash: u64) -> Self { - let hash = if hash == 0 { ALT_ZERO_HASH } else { hash }; - - Self { - #[cfg(not(feature = "no_function"))] - script: hash, - native: hash, - } - } -} - -impl FnCallHashes { - /// Create a [`FnCallHashes`] with only the native Rust hash. - #[inline(always)] - #[must_use] - pub const fn from_native(hash: u64) -> Self { - Self { - #[cfg(not(feature = "no_function"))] - script: 0, - native: if hash == 0 { ALT_ZERO_HASH } else { hash }, - } - } - /// Create a [`FnCallHashes`] with both native Rust and script function hashes. - #[inline(always)] - #[must_use] - pub const fn from_all(#[cfg(not(feature = "no_function"))] script: u64, native: u64) -> Self { - Self { - #[cfg(not(feature = "no_function"))] - script: if script == 0 { ALT_ZERO_HASH } else { script }, - native: if native == 0 { ALT_ZERO_HASH } else { native }, - } - } - /// Is this [`FnCallHashes`] native Rust only? - #[inline(always)] - #[must_use] - pub const fn is_native_only(&self) -> bool { - #[cfg(not(feature = "no_function"))] - return self.script == 0; - - #[cfg(feature = "no_function")] - return true; - } -} - -/// _(internals)_ A function call. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone, Default, Hash)] -pub struct FnCallExpr { - /// Namespace of the function, if any. - pub namespace: Option, - /// Function name. - pub name: Identifier, - /// Pre-calculated hashes. - pub hashes: FnCallHashes, - /// List of function call argument expressions. - pub args: StaticVec, - /// List of function call arguments that are constants. - /// - /// Any arguments in `args` that is [`Expr::Stack`] indexes into this - /// array to find the constant for use as its argument value. - /// - /// # Notes - /// - /// Constant arguments are very common in function calls, and keeping each constant in - /// an [`Expr::DynamicConstant`] involves an additional allocation. Keeping the constant - /// values in an inlined array avoids these extra allocations. - pub constants: StaticVec, - /// Does this function call capture the parent scope? - pub capture_parent_scope: bool, -} - -impl FnCallExpr { - /// Does this function call contain a qualified namespace? - #[inline(always)] - #[must_use] - pub const fn is_qualified(&self) -> bool { - self.namespace.is_some() - } - /// Convert this into an [`Expr::FnCall`]. - #[inline(always)] - #[must_use] - pub fn into_fn_call_expr(self, pos: Position) -> Expr { - Expr::FnCall(self.into(), pos) - } -} - -/// A type that wraps a floating-point number and implements [`Hash`]. -/// -/// Not available under `no_float`. -#[cfg(not(feature = "no_float"))] -#[derive(Clone, Copy, PartialEq, PartialOrd)] -pub struct FloatWrapper(F); - -#[cfg(not(feature = "no_float"))] -impl Hash for FloatWrapper { - #[inline(always)] - fn hash(&self, state: &mut H) { - self.0.to_ne_bytes().hash(state); - } -} - -#[cfg(not(feature = "no_float"))] -impl AsRef for FloatWrapper { - #[inline(always)] - fn as_ref(&self) -> &F { - &self.0 - } -} - -#[cfg(not(feature = "no_float"))] -impl AsMut for FloatWrapper { - #[inline(always)] - fn as_mut(&mut self) -> &mut F { - &mut self.0 - } -} - -#[cfg(not(feature = "no_float"))] -impl std::ops::Deref for FloatWrapper { - type Target = F; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[cfg(not(feature = "no_float"))] -impl std::ops::DerefMut for FloatWrapper { - #[inline(always)] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -#[cfg(not(feature = "no_float"))] -impl fmt::Debug for FloatWrapper { - #[inline(always)] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self.0, f) - } -} - -#[cfg(not(feature = "no_float"))] -impl> fmt::Display for FloatWrapper { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let abs = self.0.abs(); - if abs.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) - } else { - fmt::Display::fmt(&self.0, f)?; - if abs.fract().is_zero() { - f.write_str(".0")?; - } - Ok(()) - } - } -} - -#[cfg(not(feature = "no_float"))] -impl From for FloatWrapper { - #[inline(always)] - fn from(value: F) -> Self { - Self::new(value) - } -} - -#[cfg(not(feature = "no_float"))] -impl FromStr for FloatWrapper { - type Err = ::Err; - - #[inline] - fn from_str(s: &str) -> Result { - F::from_str(s).map(Into::::into) - } -} - -#[cfg(not(feature = "no_float"))] -impl FloatWrapper { - /// Maximum floating-point number for natural display before switching to scientific notation. - pub const MAX_NATURAL_FLOAT_FOR_DISPLAY: f32 = 10000000000000.0; - - /// Minimum floating-point number for natural display before switching to scientific notation. - pub const MIN_NATURAL_FLOAT_FOR_DISPLAY: f32 = 0.0000000000001; - - /// Create a new [`FloatWrapper`]. - #[inline(always)] - #[must_use] - pub fn new(value: F) -> Self { - Self(value) - } -} - -#[cfg(not(feature = "no_float"))] -impl FloatWrapper { - /// Create a new [`FloatWrapper`]. - #[inline(always)] - #[must_use] - pub const fn new_const(value: crate::FLOAT) -> Self { - Self(value) - } -} - -/// _(internals)_ An expression sub-tree. -/// Exported under the `internals` feature only. -#[derive(Clone, Hash)] -pub enum Expr { - /// Dynamic constant. - /// - /// Used to hold complex constants such as [`Array`][crate::Array] or [`Map`][crate::Map] for quick cloning. - /// Primitive data types should use the appropriate variants to avoid an allocation. - DynamicConstant(Box, Position), - /// Boolean constant. - BoolConstant(bool, Position), - /// Integer constant. - IntegerConstant(INT, Position), - /// Floating-point constant. - /// - /// Not available under `no_float`. - #[cfg(not(feature = "no_float"))] - FloatConstant(FloatWrapper, Position), - /// Character constant. - CharConstant(char, Position), - /// [String][ImmutableString] constant. - StringConstant(ImmutableString, Position), - /// An interpolated [string][ImmutableString]. - InterpolatedString(Box>, Position), - /// [ expr, ... ] - Array(Box>, Position), - /// #{ name:expr, ... } - Map( - Box<(StaticVec<(Ident, Expr)>, BTreeMap)>, - Position, - ), - /// () - Unit(Position), - /// Variable access - optional short index, position, (optional index, optional (hash, modules), variable name) - /// - /// The short index is [`u8`] which is used when the index is <= 255, which should be the vast - /// majority of cases (unless there are more than 255 variables defined!). - /// This is to avoid reading a pointer redirection during each variable access. - Variable( - Option, - Position, - Box<( - Option, - Option<(NamespaceRef, u64)>, - Identifier, - )>, - ), - /// Property access - ((getter, hash), (setter, hash), prop) - Property( - Box<( - (Identifier, u64), - (Identifier, u64), - (ImmutableString, Position), - )>, - ), - /// Stack slot for function calls. See [`FnCallExpr`] for more details. - /// - /// This variant does not map to any language structure. It is used in function calls with - /// constant arguments where the `usize` number indexes into an array containing a list of - /// constant arguments for the function call. - Stack(usize, Position), - /// { [statement][Stmt] ... } - Stmt(Box), - /// func `(` expr `,` ... `)` - FnCall(Box, Position), - /// lhs `.` rhs - bool variable is a dummy - Dot(Box, bool, Position), - /// expr `[` expr `]` - boolean indicates whether the dotting/indexing chain stops - Index(Box, bool, Position), - /// lhs `&&` rhs - And(Box, Position), - /// lhs `||` rhs - Or(Box, Position), - /// Custom syntax - Custom(Box, Position), -} - -impl Default for Expr { - #[inline(always)] - fn default() -> Self { - Self::Unit(Position::NONE) - } -} - -impl fmt::Debug for Expr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut display_pos = self.position(); - - match self { - Self::DynamicConstant(value, _) => write!(f, "{:?}", value), - Self::BoolConstant(value, _) => write!(f, "{:?}", value), - Self::IntegerConstant(value, _) => write!(f, "{:?}", value), - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(value, _) => write!(f, "{:?}", value), - Self::CharConstant(value, _) => write!(f, "{:?}", value), - Self::StringConstant(value, _) => write!(f, "{:?}", value), - Self::Unit(_) => f.write_str("()"), - - Self::InterpolatedString(x, _) => { - f.write_str("InterpolatedString")?; - return f.debug_list().entries(x.iter()).finish(); - } - Self::Array(x, _) => { - f.write_str("Array")?; - f.debug_list().entries(x.iter()).finish() - } - Self::Map(x, _) => { - f.write_str("Map")?; - f.debug_map() - .entries(x.0.iter().map(|(k, v)| (k, v))) - .finish() - } - Self::Variable(i, _, x) => { - f.write_str("Variable(")?; - if let Some((_, ref namespace)) = x.1 { - 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)) { - write!(f, " #{}", n)? - } - f.write_str(")") - } - Self::Property(x) => write!(f, "Property({})", (x.2).0), - Self::Stack(x, _) => write!(f, "StackSlot({})", x), - Self::Stmt(x) => { - f.write_str("ExprStmtBlock")?; - f.debug_list().entries(x.0.iter()).finish() - } - Self::FnCall(x, _) => { - let mut ff = f.debug_struct("FnCall"); - x.namespace.as_ref().map(|ns| ff.field("namespace", ns)); - ff.field("name", &x.name) - .field("hash", &x.hashes) - .field("args", &x.args); - if !x.constants.is_empty() { - ff.field("constants", &x.constants); - } - if x.capture_parent_scope { - ff.field("capture_parent_scope", &x.capture_parent_scope); - } - ff.finish() - } - Self::Index(x, term, pos) => { - display_pos = *pos; - - f.debug_struct("Index") - .field("lhs", &x.lhs) - .field("rhs", &x.rhs) - .field("terminate", term) - .finish() - } - Self::Dot(x, _, pos) | Self::And(x, pos) | Self::Or(x, pos) => { - let op_name = match self { - Self::Dot(_, _, _) => "Dot", - Self::And(_, _) => "And", - Self::Or(_, _) => "Or", - _ => unreachable!(), - }; - - display_pos = *pos; - - f.debug_struct(op_name) - .field("lhs", &x.lhs) - .field("rhs", &x.rhs) - .finish() - } - Self::Custom(x, _) => f.debug_tuple("Custom").field(x).finish(), - }?; - - display_pos.debug_print(f) - } -} - -impl Expr { - /// Get the [`Dynamic`] value of a literal constant expression. - /// - /// Returns [`None`] if the expression is not a literal constant. - #[inline] - #[must_use] - pub fn get_literal_value(&self) -> Option { - Some(match self { - Self::DynamicConstant(x, _) => x.as_ref().clone(), - Self::IntegerConstant(x, _) => (*x).into(), - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(x, _) => (*x).into(), - Self::CharConstant(x, _) => (*x).into(), - Self::StringConstant(x, _) => x.clone().into(), - Self::BoolConstant(x, _) => (*x).into(), - Self::Unit(_) => Dynamic::UNIT, - - #[cfg(not(feature = "no_index"))] - Self::Array(x, _) if self.is_constant() => { - let mut arr = crate::Array::with_capacity(x.len()); - arr.extend( - x.iter() - .map(|v| v.get_literal_value().expect("constant value")), - ); - Dynamic::from_array(arr) - } - - #[cfg(not(feature = "no_object"))] - Self::Map(x, _) if self.is_constant() => { - Dynamic::from_map(x.0.iter().fold(x.1.clone(), |mut map, (k, v)| { - let value_ref = map.get_mut(k.name.as_str()).expect("contains all keys"); - *value_ref = v.get_literal_value().expect("constant value"); - map - })) - } - - // Binary operators - Self::FnCall(x, _) if x.args.len() == 2 => match x.name.as_str() { - // x..y - OP_EXCLUSIVE_RANGE => { - if let Expr::IntegerConstant(ref start, _) = x.args[0] { - if let Expr::IntegerConstant(ref end, _) = x.args[1] { - (*start..*end).into() - } else { - return None; - } - } else { - return None; - } - } - // x..=y - OP_INCLUSIVE_RANGE => { - if let Expr::IntegerConstant(ref start, _) = x.args[0] { - if let Expr::IntegerConstant(ref end, _) = x.args[1] { - (*start..=*end).into() - } else { - return None; - } - } else { - return None; - } - } - _ => return None, - }, - - _ => return None, - }) - } - /// Create an [`Expr`] from a [`Dynamic`] value. - #[inline] - #[must_use] - pub fn from_dynamic(value: Dynamic, pos: Position) -> Self { - match value.0 { - Union::Unit(_, _, _) => Self::Unit(pos), - Union::Bool(b, _, _) => Self::BoolConstant(b, pos), - Union::Str(s, _, _) => Self::StringConstant(s, pos), - Union::Char(c, _, _) => Self::CharConstant(c, pos), - Union::Int(i, _, _) => Self::IntegerConstant(i, pos), - - #[cfg(feature = "decimal")] - Union::Decimal(value, _, _) => Self::DynamicConstant(Box::new((*value).into()), pos), - - #[cfg(not(feature = "no_float"))] - Union::Float(f, _, _) => Self::FloatConstant(f, pos), - - #[cfg(not(feature = "no_index"))] - Union::Array(a, _, _) => Self::DynamicConstant(Box::new((*a).into()), pos), - - #[cfg(not(feature = "no_object"))] - Union::Map(m, _, _) => Self::DynamicConstant(Box::new((*m).into()), pos), - - _ => Self::DynamicConstant(value.into(), pos), - } - } - /// Is the expression a simple variable access? - #[inline] - #[must_use] - pub(crate) const fn is_variable_access(&self, non_qualified: bool) -> bool { - match self { - Self::Variable(_, _, x) => !non_qualified || x.1.is_none(), - _ => false, - } - } - /// Return the variable name if the expression a simple variable access. - #[inline] - #[must_use] - pub(crate) fn get_variable_name(&self, non_qualified: bool) -> Option<&str> { - match self { - Self::Variable(_, _, x) if !non_qualified || x.1.is_none() => Some(x.2.as_str()), - _ => None, - } - } - /// Get the [position][Position] of the expression. - #[inline] - #[must_use] - pub const fn position(&self) -> Position { - match self { - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(_, pos) => *pos, - - Self::DynamicConstant(_, pos) - | Self::BoolConstant(_, pos) - | Self::IntegerConstant(_, pos) - | Self::CharConstant(_, pos) - | Self::Unit(pos) - | Self::StringConstant(_, pos) - | Self::Array(_, pos) - | Self::Map(_, pos) - | Self::Variable(_, pos, _) - | Self::Stack(_, pos) - | Self::FnCall(_, pos) - | Self::Custom(_, pos) - | Self::InterpolatedString(_, pos) => *pos, - - Self::Property(x) => (x.2).1, - Self::Stmt(x) => x.1, - - Self::And(x, _) | Self::Or(x, _) | Self::Dot(x, _, _) | Self::Index(x, _, _) => { - x.lhs.position() - } - } - } - /// Override the [position][Position] of the expression. - #[inline] - pub fn set_position(&mut self, new_pos: Position) -> &mut Self { - match self { - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(_, pos) => *pos = new_pos, - - Self::DynamicConstant(_, pos) - | Self::BoolConstant(_, pos) - | Self::IntegerConstant(_, pos) - | Self::CharConstant(_, pos) - | Self::Unit(pos) - | Self::StringConstant(_, pos) - | Self::Array(_, pos) - | Self::Map(_, pos) - | Self::And(_, pos) - | Self::Or(_, pos) - | Self::Dot(_, _, pos) - | Self::Index(_, _, pos) - | Self::Variable(_, pos, _) - | Self::Stack(_, pos) - | Self::FnCall(_, pos) - | Self::Custom(_, pos) - | Self::InterpolatedString(_, pos) => *pos = new_pos, - - Self::Property(x) => (x.2).1 = new_pos, - Self::Stmt(x) => x.1 = new_pos, - } - - self - } - /// Is the expression pure? - /// - /// A pure expression has no side effects. - #[inline] - #[must_use] - pub fn is_pure(&self) -> bool { - match self { - Self::InterpolatedString(x, _) | Self::Array(x, _) => x.iter().all(Self::is_pure), - - Self::Map(x, _) => x.0.iter().map(|(_, v)| v).all(Self::is_pure), - - Self::And(x, _) | Self::Or(x, _) => x.lhs.is_pure() && x.rhs.is_pure(), - - Self::Stmt(x) => x.0.iter().all(Stmt::is_pure), - - Self::Variable(_, _, _) | Self::Stack(_, _) => true, - - _ => self.is_constant(), - } - } - /// Is the expression the unit `()` literal? - #[inline(always)] - #[must_use] - pub const fn is_unit(&self) -> bool { - matches!(self, Self::Unit(_)) - } - /// Is the expression a constant? - #[inline] - #[must_use] - pub fn is_constant(&self) -> bool { - match self { - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(_, _) => true, - - Self::DynamicConstant(_, _) - | Self::BoolConstant(_, _) - | Self::IntegerConstant(_, _) - | Self::CharConstant(_, _) - | Self::StringConstant(_, _) - | Self::Unit(_) - | Self::Stack(_, _) => true, - - Self::InterpolatedString(x, _) | Self::Array(x, _) => x.iter().all(Self::is_constant), - - Self::Map(x, _) => x.0.iter().map(|(_, expr)| expr).all(Self::is_constant), - - _ => false, - } - } - /// Is a particular [token][Token] allowed as a postfix operator to this expression? - #[inline] - #[must_use] - pub const fn is_valid_postfix(&self, token: &Token) -> bool { - match token { - #[cfg(not(feature = "no_object"))] - Token::Period => return true, - _ => (), - } - - match self { - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(_, _) => false, - - Self::DynamicConstant(_, _) - | Self::BoolConstant(_, _) - | Self::CharConstant(_, _) - | Self::And(_, _) - | Self::Or(_, _) - | Self::Unit(_) => false, - - Self::IntegerConstant(_, _) - | Self::StringConstant(_, _) - | Self::InterpolatedString(_, _) - | Self::FnCall(_, _) - | Self::Stmt(_) - | Self::Dot(_, _, _) - | Self::Index(_, _, _) - | Self::Array(_, _) - | Self::Map(_, _) - | Self::Custom(_, _) => match token { - #[cfg(not(feature = "no_index"))] - Token::LeftBracket => true, - _ => false, - }, - - Self::Variable(_, _, _) => match token { - #[cfg(not(feature = "no_index"))] - Token::LeftBracket => true, - Token::LeftParen => true, - Token::Bang => true, - Token::DoubleColon => true, - _ => false, - }, - - Self::Property(_) => match token { - #[cfg(not(feature = "no_index"))] - Token::LeftBracket => true, - Token::LeftParen => true, - _ => false, - }, - - Self::Stack(_, _) => false, - } - } - /// Recursively walk this expression. - /// Return `false` from the callback to terminate the walk. - pub fn walk<'a>( - &'a self, - path: &mut Vec>, - on_node: &mut impl FnMut(&[ASTNode]) -> bool, - ) -> bool { - // Push the current node onto the path - path.push(self.into()); - - if !on_node(path) { - return false; - } - - match self { - Self::Stmt(x) => { - for s in &x.0 { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::InterpolatedString(x, _) | Self::Array(x, _) => { - for e in x.as_ref() { - if !e.walk(path, on_node) { - return false; - } - } - } - Self::Map(x, _) => { - for (_, e) in &x.0 { - if !e.walk(path, on_node) { - return false; - } - } - } - Self::Index(x, _, _) | Self::Dot(x, _, _) | Expr::And(x, _) | Expr::Or(x, _) => { - if !x.lhs.walk(path, on_node) { - return false; - } - if !x.rhs.walk(path, on_node) { - return false; - } - } - Self::FnCall(x, _) => { - for e in &x.args { - if !e.walk(path, on_node) { - return false; - } - } - } - Self::Custom(x, _) => { - for e in &x.inputs { - if !e.walk(path, on_node) { - return false; - } - } - } - _ => (), - } - - path.pop().expect("contains current node"); - - true - } -} - -impl AST { - /// _(internals)_ Get the internal [`Module`] containing all script-defined functions. - /// Exported under the `internals` feature only. - /// - /// Not available under `no_function`. - /// - /// # Deprecated - /// - /// This method is deprecated. Use [`shared_lib`][AST::shared_lib] instead. - /// - /// This method will be removed in the next major version. - #[deprecated(since = "1.3.0", note = "use `shared_lib` instead")] - #[cfg(feature = "internals")] - #[cfg(not(feature = "no_function"))] - #[inline(always)] - #[must_use] - pub fn lib(&self) -> &crate::Module { - &self.functions - } -} diff --git a/src/ast/ast.rs b/src/ast/ast.rs new file mode 100644 index 00000000..82ca8087 --- /dev/null +++ b/src/ast/ast.rs @@ -0,0 +1,861 @@ +//! Module defining the AST (abstract syntax tree). + +use super::{Expr, FnAccess, Stmt, StmtBlock, AST_OPTION_FLAGS}; +use crate::{Dynamic, FnNamespace, Identifier, Position, StaticVec}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; +use std::{ + hash::Hash, + ops::{Add, AddAssign}, +}; + +/// Compiled AST (abstract syntax tree) of a Rhai script. +/// +/// # Thread Safety +/// +/// Currently, [`AST`] is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. +#[derive(Debug, Clone)] +pub struct AST { + /// Source of the [`AST`]. + source: Option, + /// Global statements. + body: StmtBlock, + /// Script-defined functions. + #[cfg(not(feature = "no_function"))] + functions: crate::Shared, + /// Embedded module resolver, if any. + #[cfg(not(feature = "no_module"))] + resolver: Option>, +} + +impl Default for AST { + #[inline(always)] + fn default() -> Self { + Self::empty() + } +} + +impl AST { + /// Create a new [`AST`]. + #[cfg(not(feature = "internals"))] + #[inline(always)] + #[must_use] + pub(crate) fn new( + statements: impl IntoIterator, + #[cfg(not(feature = "no_function"))] functions: impl Into>, + ) -> Self { + Self { + source: None, + body: StmtBlock::new(statements, Position::NONE), + #[cfg(not(feature = "no_function"))] + functions: functions.into(), + #[cfg(not(feature = "no_module"))] + resolver: None, + } + } + /// _(internals)_ Create a new [`AST`]. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] + #[inline(always)] + #[must_use] + pub fn new( + statements: impl IntoIterator, + #[cfg(not(feature = "no_function"))] functions: impl Into>, + ) -> Self { + Self { + source: None, + body: StmtBlock::new(statements, Position::NONE), + #[cfg(not(feature = "no_function"))] + functions: functions.into(), + #[cfg(not(feature = "no_module"))] + resolver: None, + } + } + /// Create a new [`AST`] with a source name. + #[cfg(not(feature = "internals"))] + #[inline(always)] + #[must_use] + pub(crate) fn new_with_source( + statements: impl IntoIterator, + #[cfg(not(feature = "no_function"))] functions: impl Into>, + source: impl Into, + ) -> Self { + let mut ast = Self::new( + statements, + #[cfg(not(feature = "no_function"))] + functions, + ); + ast.set_source(source); + ast + } + /// _(internals)_ Create a new [`AST`] with a source name. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] + #[inline(always)] + #[must_use] + pub fn new_with_source( + statements: impl IntoIterator, + #[cfg(not(feature = "no_function"))] functions: impl Into>, + source: impl Into, + ) -> Self { + let mut ast = Self::new( + statements, + #[cfg(not(feature = "no_function"))] + functions, + ); + ast.set_source(source); + ast + } + /// Create an empty [`AST`]. + #[inline] + #[must_use] + pub fn empty() -> Self { + Self { + source: None, + body: StmtBlock::NONE, + #[cfg(not(feature = "no_function"))] + functions: crate::Module::new().into(), + #[cfg(not(feature = "no_module"))] + resolver: None, + } + } + /// Get the source, if any. + #[inline(always)] + #[must_use] + pub fn source(&self) -> Option<&str> { + self.source.as_ref().map(|s| s.as_str()) + } + /// Get a reference to the source. + #[inline(always)] + #[must_use] + pub(crate) fn source_raw(&self) -> Option<&Identifier> { + self.source.as_ref() + } + /// Set the source. + #[inline] + pub fn set_source(&mut self, source: impl Into) -> &mut Self { + let source = source.into(); + #[cfg(not(feature = "no_function"))] + crate::Shared::get_mut(&mut self.functions) + .as_mut() + .map(|m| m.set_id(source.clone())); + self.source = Some(source); + self + } + /// Clear the source. + #[inline(always)] + pub fn clear_source(&mut self) -> &mut Self { + self.source = None; + self + } + /// Get the statements. + #[cfg(not(feature = "internals"))] + #[inline(always)] + #[must_use] + pub(crate) fn statements(&self) -> &[Stmt] { + self.body.statements() + } + /// _(internals)_ Get the statements. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] + #[inline(always)] + #[must_use] + pub fn statements(&self) -> &[Stmt] { + self.body.statements() + } + /// Extract the statements. + #[allow(dead_code)] + #[inline(always)] + #[must_use] + pub(crate) fn take_statements(&mut self) -> StaticVec { + self.body.take_statements() + } + /// Does this [`AST`] contain script-defined functions? + /// + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub fn has_functions(&self) -> bool { + !self.functions.is_empty() + } + /// Get the internal shared [`Module`][crate::Module] containing all script-defined functions. + #[cfg(not(feature = "internals"))] + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub(crate) fn shared_lib(&self) -> &crate::Shared { + &self.functions + } + /// _(internals)_ Get the internal shared [`Module`][crate::Module] containing all script-defined functions. + /// Exported under the `internals` feature only. + /// + /// Not available under `no_function`. + #[cfg(feature = "internals")] + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub fn shared_lib(&self) -> &crate::Shared { + &self.functions + } + /// Get the embedded [module resolver][`ModuleResolver`]. + #[cfg(not(feature = "internals"))] + #[cfg(not(feature = "no_module"))] + #[inline(always)] + #[must_use] + pub(crate) fn resolver( + &self, + ) -> Option<&crate::Shared> { + self.resolver.as_ref() + } + /// _(internals)_ Get the embedded [module resolver][crate::ModuleResolver]. + /// Exported under the `internals` feature only. + /// + /// Not available under `no_module`. + #[cfg(feature = "internals")] + #[cfg(not(feature = "no_module"))] + #[inline(always)] + #[must_use] + pub fn resolver( + &self, + ) -> Option<&crate::Shared> { + self.resolver.as_ref() + } + /// Set the embedded [module resolver][`ModuleResolver`]. + #[cfg(not(feature = "no_module"))] + #[inline(always)] + pub(crate) fn set_resolver( + &mut self, + resolver: impl Into>, + ) -> &mut Self { + self.resolver = Some(resolver.into()); + self + } + /// Clone the [`AST`]'s functions into a new [`AST`]. + /// No statements are cloned. + /// + /// Not available under `no_function`. + /// + /// This operation is cheap because functions are shared. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub fn clone_functions_only(&self) -> Self { + self.clone_functions_only_filtered(|_, _, _, _, _| true) + } + /// Clone the [`AST`]'s functions into a new [`AST`] based on a filter predicate. + /// No statements are cloned. + /// + /// Not available under `no_function`. + /// + /// This operation is cheap because functions are shared. + #[cfg(not(feature = "no_function"))] + #[inline] + #[must_use] + pub fn clone_functions_only_filtered( + &self, + filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, + ) -> Self { + let mut functions = crate::Module::new(); + functions.merge_filtered(&self.functions, &filter); + Self { + source: self.source.clone(), + body: StmtBlock::NONE, + functions: functions.into(), + #[cfg(not(feature = "no_module"))] + resolver: self.resolver.clone(), + } + } + /// Clone the [`AST`]'s script statements into a new [`AST`]. + /// No functions are cloned. + #[inline(always)] + #[must_use] + pub fn clone_statements_only(&self) -> Self { + Self { + source: self.source.clone(), + body: self.body.clone(), + #[cfg(not(feature = "no_function"))] + functions: crate::Module::new().into(), + #[cfg(not(feature = "no_module"))] + resolver: self.resolver.clone(), + } + } + /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, + /// version is returned. + /// + /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. + /// Of course, if the first [`AST`] uses a `return` statement at the end, then + /// the second [`AST`] will essentially be dead code. + /// + /// All script-defined functions in the second [`AST`] overwrite similarly-named functions + /// in the first [`AST`] with the same number of parameters. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let ast1 = engine.compile(" + /// fn foo(x) { 42 + x } + /// foo(1) + /// ")?; + /// + /// let ast2 = engine.compile(r#" + /// fn foo(n) { `hello${n}` } + /// foo("!") + /// "#)?; + /// + /// let ast = ast1.merge(&ast2); // Merge 'ast2' into 'ast1' + /// + /// // Notice that using the '+' operator also works: + /// // let ast = &ast1 + &ast2; + /// + /// // 'ast' is essentially: + /// // + /// // fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten + /// // foo(1) // <- notice this will be "hello1" instead of 43, + /// // // but it is no longer the return value + /// // foo("!") // returns "hello!" + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast)?, "hello!"); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + #[must_use] + pub fn merge(&self, other: &Self) -> Self { + self.merge_filtered_impl(other, |_, _, _, _, _| true) + } + /// Combine one [`AST`] with another. The second [`AST`] is consumed. + /// + /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. + /// Of course, if the first [`AST`] uses a `return` statement at the end, then + /// the second [`AST`] will essentially be dead code. + /// + /// All script-defined functions in the second [`AST`] overwrite similarly-named functions + /// in the first [`AST`] with the same number of parameters. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let mut ast1 = engine.compile(" + /// fn foo(x) { 42 + x } + /// foo(1) + /// ")?; + /// + /// let ast2 = engine.compile(r#" + /// fn foo(n) { `hello${n}` } + /// foo("!") + /// "#)?; + /// + /// ast1.combine(ast2); // Combine 'ast2' into 'ast1' + /// + /// // Notice that using the '+=' operator also works: + /// // ast1 += ast2; + /// + /// // 'ast1' is essentially: + /// // + /// // fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten + /// // foo(1) // <- notice this will be "hello1" instead of 43, + /// // // but it is no longer the return value + /// // foo("!") // returns "hello!" + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast1)?, "hello!"); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn combine(&mut self, other: Self) -> &mut Self { + self.combine_filtered_impl(other, |_, _, _, _, _| true) + } + /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version + /// is returned. + /// + /// Not available under `no_function`. + /// + /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. + /// Of course, if the first [`AST`] uses a `return` statement at the end, then + /// the second [`AST`] will essentially be dead code. + /// + /// All script-defined functions in the second [`AST`] are first selected based on a filter + /// predicate, then overwrite similarly-named functions in the first [`AST`] with the + /// same number of parameters. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let ast1 = engine.compile(" + /// fn foo(x) { 42 + x } + /// foo(1) + /// ")?; + /// + /// let ast2 = engine.compile(r#" + /// fn foo(n) { `hello${n}` } + /// fn error() { 0 } + /// foo("!") + /// "#)?; + /// + /// // Merge 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1' + /// let ast = ast1.merge_filtered(&ast2, |_, _, script, name, params| + /// script && name == "error" && params == 0); + /// + /// // 'ast' is essentially: + /// // + /// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten + /// // // because 'ast2::foo' is filtered away + /// // foo(1) // <- notice this will be 43 instead of "hello1", + /// // // but it is no longer the return value + /// // fn error() { 0 } // <- this function passes the filter and is merged + /// // foo("!") // <- returns "42!" + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast)?, "42!"); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub fn merge_filtered( + &self, + other: &Self, + filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, + ) -> Self { + self.merge_filtered_impl(other, filter) + } + /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version + /// is returned. + #[inline] + #[must_use] + fn merge_filtered_impl( + &self, + other: &Self, + _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, + ) -> Self { + let merged = match (self.body.is_empty(), other.body.is_empty()) { + (false, false) => { + let mut body = self.body.clone(); + body.extend(other.body.iter().cloned()); + body + } + (false, true) => self.body.clone(), + (true, false) => other.body.clone(), + (true, true) => StmtBlock::NONE, + }; + + let source = other.source.clone().or_else(|| self.source.clone()); + + #[cfg(not(feature = "no_function"))] + let functions = { + let mut functions = self.functions.as_ref().clone(); + functions.merge_filtered(&other.functions, &_filter); + functions + }; + + if let Some(source) = source { + Self::new_with_source( + merged, + #[cfg(not(feature = "no_function"))] + functions, + source, + ) + } else { + Self::new( + merged, + #[cfg(not(feature = "no_function"))] + functions, + ) + } + } + /// Combine one [`AST`] with another. The second [`AST`] is consumed. + /// + /// Not available under `no_function`. + /// + /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. + /// Of course, if the first [`AST`] uses a `return` statement at the end, then + /// the second [`AST`] will essentially be dead code. + /// + /// All script-defined functions in the second [`AST`] are first selected based on a filter + /// predicate, then overwrite similarly-named functions in the first [`AST`] with the + /// same number of parameters. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let mut ast1 = engine.compile(" + /// fn foo(x) { 42 + x } + /// foo(1) + /// ")?; + /// + /// let ast2 = engine.compile(r#" + /// fn foo(n) { `hello${n}` } + /// fn error() { 0 } + /// foo("!") + /// "#)?; + /// + /// // Combine 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1' + /// ast1.combine_filtered(ast2, |_, _, script, name, params| + /// script && name == "error" && params == 0); + /// + /// // 'ast1' is essentially: + /// // + /// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten + /// // // because 'ast2::foo' is filtered away + /// // foo(1) // <- notice this will be 43 instead of "hello1", + /// // // but it is no longer the return value + /// // fn error() { 0 } // <- this function passes the filter and is merged + /// // foo("!") // <- returns "42!" + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast1)?, "42!"); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_function"))] + #[inline(always)] + pub fn combine_filtered( + &mut self, + other: Self, + filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, + ) -> &mut Self { + self.combine_filtered_impl(other, filter) + } + /// Combine one [`AST`] with another. The second [`AST`] is consumed. + #[inline] + fn combine_filtered_impl( + &mut self, + other: Self, + _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, + ) -> &mut Self { + self.body.extend(other.body.into_iter()); + + #[cfg(not(feature = "no_function"))] + if !other.functions.is_empty() { + crate::func::native::shared_make_mut(&mut self.functions) + .merge_filtered(&other.functions, &_filter); + } + self + } + /// Filter out the functions, retaining only some based on a filter predicate. + /// + /// Not available under `no_function`. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let mut ast = engine.compile(r#" + /// fn foo(n) { n + 1 } + /// fn bar() { print("hello"); } + /// "#)?; + /// + /// // Remove all functions except 'foo(_)' + /// ast.retain_functions(|_, _, name, params| name == "foo" && params == 1); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_function"))] + #[inline] + pub fn retain_functions( + &mut self, + filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool, + ) -> &mut Self { + if !self.functions.is_empty() { + crate::func::native::shared_make_mut(&mut self.functions) + .retain_script_functions(filter); + } + self + } + /// Iterate through all function definitions. + /// + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] + #[allow(dead_code)] + #[inline] + pub(crate) fn iter_fn_def(&self) -> impl Iterator { + self.functions + .iter_script_fn() + .map(|(_, _, _, _, fn_def)| fn_def.as_ref()) + } + /// Iterate through all function definitions. + /// + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] + #[inline] + pub fn iter_functions<'a>(&'a self) -> impl Iterator + 'a { + self.functions + .iter_script_fn() + .map(|(_, _, _, _, fn_def)| fn_def.as_ref().into()) + } + /// Clear all function definitions in the [`AST`]. + /// + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + pub fn clear_functions(&mut self) -> &mut Self { + self.functions = crate::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 = StmtBlock::NONE; + self + } + /// Extract all top-level literal constant and/or variable definitions. + /// This is useful for extracting all global constants from a script without actually running it. + /// + /// A literal constant/variable definition takes the form of: + /// `const VAR = `_value_`;` and `let VAR = `_value_`;` + /// where _value_ is a literal expression or will be optimized into a literal. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Scope}; + /// + /// let engine = Engine::new(); + /// + /// let ast = engine.compile( + /// " + /// const A = 40 + 2; // constant that optimizes into a literal + /// let b = 123; // literal variable + /// const B = b * A; // non-literal constant + /// const C = 999; // literal constant + /// b = A + C; // expression + /// + /// { // <- new block scope + /// const Z = 0; // <- literal constant not at top-level + /// } + /// ")?; + /// + /// let mut iter = ast.iter_literal_variables(true, false) + /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); + /// + /// # #[cfg(not(feature = "no_optimize"))] + /// assert_eq!(iter.next(), Some(("A", true, 42))); + /// assert_eq!(iter.next(), Some(("C", true, 999))); + /// assert_eq!(iter.next(), None); + /// + /// let mut iter = ast.iter_literal_variables(false, true) + /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); + /// + /// assert_eq!(iter.next(), Some(("b", false, 123))); + /// assert_eq!(iter.next(), None); + /// + /// let mut iter = ast.iter_literal_variables(true, true) + /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); + /// + /// # #[cfg(not(feature = "no_optimize"))] + /// assert_eq!(iter.next(), Some(("A", true, 42))); + /// assert_eq!(iter.next(), Some(("b", false, 123))); + /// assert_eq!(iter.next(), Some(("C", true, 999))); + /// assert_eq!(iter.next(), None); + /// + /// let scope: Scope = ast.iter_literal_variables(true, false).collect(); + /// + /// # #[cfg(not(feature = "no_optimize"))] + /// assert_eq!(scope.len(), 2); + /// + /// Ok(()) + /// # } + /// ``` + pub fn iter_literal_variables( + &self, + include_constants: bool, + include_variables: bool, + ) -> impl Iterator { + self.statements().iter().filter_map(move |stmt| match stmt { + Stmt::Var(expr, name, options, _) + if options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT) && include_constants + || !options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT) + && include_variables => + { + if let Some(value) = expr.get_literal_value() { + Some(( + name.as_str(), + options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT), + value, + )) + } else { + None + } + } + _ => None, + }) + } + /// Recursively walk the [`AST`], including function bodies (if any). + /// Return `false` from the callback to terminate the walk. + #[cfg(not(feature = "internals"))] + #[cfg(not(feature = "no_module"))] + #[inline] + pub(crate) fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool { + let path = &mut Vec::new(); + + for stmt in self.statements() { + if !stmt.walk(path, on_node) { + return false; + } + } + #[cfg(not(feature = "no_function"))] + for stmt in self.iter_fn_def().flat_map(|f| f.body.iter()) { + if !stmt.walk(path, on_node) { + return false; + } + } + + true + } + /// _(internals)_ Recursively walk the [`AST`], including function bodies (if any). + /// Return `false` from the callback to terminate the walk. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] + #[inline] + pub fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool { + let path = &mut Vec::new(); + + for stmt in self.statements() { + if !stmt.walk(path, on_node) { + return false; + } + } + #[cfg(not(feature = "no_function"))] + for stmt in self.iter_fn_def().flat_map(|f| f.body.iter()) { + if !stmt.walk(path, on_node) { + return false; + } + } + + true + } +} + +impl> Add for &AST { + type Output = AST; + + #[inline(always)] + fn add(self, rhs: A) -> Self::Output { + self.merge(rhs.as_ref()) + } +} + +impl> AddAssign for AST { + #[inline(always)] + fn add_assign(&mut self, rhs: A) { + self.combine(rhs.into()); + } +} + +impl AsRef<[Stmt]> for AST { + #[inline(always)] + fn as_ref(&self) -> &[Stmt] { + self.statements() + } +} + +#[cfg(not(feature = "no_function"))] +impl AsRef for AST { + #[inline(always)] + fn as_ref(&self) -> &crate::Module { + self.shared_lib().as_ref() + } +} + +#[cfg(not(feature = "no_function"))] +impl AsRef> for AST { + #[inline(always)] + fn as_ref(&self) -> &crate::Shared { + self.shared_lib() + } +} + +/// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`]. +/// Exported under the `internals` feature only. +#[derive(Debug, Clone, Hash)] +pub enum ASTNode<'a> { + /// A statement ([`Stmt`]). + Stmt(&'a Stmt), + /// An expression ([`Expr`]). + Expr(&'a Expr), +} + +impl<'a> From<&'a Stmt> for ASTNode<'a> { + fn from(stmt: &'a Stmt) -> Self { + Self::Stmt(stmt) + } +} + +impl<'a> From<&'a Expr> for ASTNode<'a> { + fn from(expr: &'a Expr) -> Self { + Self::Expr(expr) + } +} + +impl ASTNode<'_> { + /// Get the [`Position`] of this [`ASTNode`]. + pub const fn position(&self) -> Position { + match self { + ASTNode::Stmt(stmt) => stmt.position(), + ASTNode::Expr(expr) => expr.position(), + } + } +} + +impl AST { + /// _(internals)_ Get the internal [`Module`][crate::Module] containing all script-defined functions. + /// Exported under the `internals` feature only. + /// + /// Not available under `no_function`. + /// + /// # Deprecated + /// + /// This method is deprecated. Use [`shared_lib`][AST::shared_lib] instead. + /// + /// This method will be removed in the next major version. + #[deprecated(since = "1.3.0", note = "use `shared_lib` instead")] + #[cfg(feature = "internals")] + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub fn lib(&self) -> &crate::Module { + &self.functions + } +} diff --git a/src/ast/expr.rs b/src/ast/expr.rs new file mode 100644 index 00000000..66aba2f7 --- /dev/null +++ b/src/ast/expr.rs @@ -0,0 +1,835 @@ +//! Module defining script expressions. + +use super::{ASTNode, Ident, Stmt, StmtBlock}; +use crate::engine::{OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE}; +use crate::func::hashing::ALT_ZERO_HASH; +use crate::module::NamespaceRef; +use crate::tokenizer::Token; +use crate::types::dynamic::Union; +use crate::{Dynamic, Identifier, ImmutableString, Position, StaticVec, INT}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; +use std::{ + collections::BTreeMap, + fmt, + hash::Hash, + num::{NonZeroU8, NonZeroUsize}, +}; + +#[cfg(not(feature = "no_float"))] +use std::str::FromStr; + +#[cfg(not(feature = "no_float"))] +use num_traits::Float; + +/// _(internals)_ A binary expression. +/// Exported under the `internals` feature only. +#[derive(Debug, Clone, Hash)] +pub struct BinaryExpr { + /// LHS expression. + pub lhs: Expr, + /// RHS expression. + pub rhs: Expr, +} + +/// _(internals)_ A custom syntax expression. +/// Exported under the `internals` feature only. +#[derive(Debug, Clone, Hash)] +pub struct CustomExpr { + /// List of keywords. + pub inputs: StaticVec, + /// List of tokens actually parsed. + pub tokens: 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, + /// Is this custom syntax self-terminated? + pub self_terminated: bool, +} + +impl CustomExpr { + /// Is this custom syntax self-terminated (i.e. no need for a semicolon terminator)? + /// + /// A self-terminated custom syntax always ends in `$block$`, `}` or `;` + #[must_use] + #[inline(always)] + pub const fn is_self_terminated(&self) -> bool { + self.self_terminated + } +} + +/// _(internals)_ A set of function call hashes. Exported under the `internals` feature only. +/// +/// Two separate hashes are pre-calculated because of the following patterns: +/// +/// ```ignore +/// func(a, b, c); // Native: func(a, b, c) - 3 parameters +/// // Script: func(a, b, c) - 3 parameters +/// +/// a.func(b, c); // Native: func(&mut a, b, c) - 3 parameters +/// // Script: func(b, c) - 2 parameters +/// ``` +/// +/// For normal function calls, the native hash equals the script hash. +/// +/// For method-style calls, the script hash contains one fewer parameter. +/// +/// Function call hashes are used in the following manner: +/// +/// * First, the script hash is tried, which contains only the called function's name plus the +/// number of parameters. +/// +/// * Next, the actual types of arguments are hashed and _combined_ with the native hash, which is +/// then used to search for a native function. In other words, a complete native function call +/// hash always contains the called function's name plus the types of the arguments. This is due +/// to possible function overloading for different parameter types. +#[derive(Clone, Copy, Eq, PartialEq, Hash, Default)] +pub struct FnCallHashes { + /// Pre-calculated hash for a script-defined function (zero if native functions only). + #[cfg(not(feature = "no_function"))] + pub script: u64, + /// Pre-calculated hash for a native Rust function with no parameter types. + pub native: u64, +} + +impl fmt::Debug for FnCallHashes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[cfg(not(feature = "no_function"))] + if self.script != 0 { + return if self.script == self.native { + fmt::Debug::fmt(&self.native, f) + } else { + write!(f, "({}, {})", self.script, self.native) + }; + } + + write!(f, "{} (native only)", self.native) + } +} + +impl From for FnCallHashes { + #[inline(always)] + fn from(hash: u64) -> Self { + let hash = if hash == 0 { ALT_ZERO_HASH } else { hash }; + + Self { + #[cfg(not(feature = "no_function"))] + script: hash, + native: hash, + } + } +} + +impl FnCallHashes { + /// Create a [`FnCallHashes`] with only the native Rust hash. + #[inline(always)] + #[must_use] + pub const fn from_native(hash: u64) -> Self { + Self { + #[cfg(not(feature = "no_function"))] + script: 0, + native: if hash == 0 { ALT_ZERO_HASH } else { hash }, + } + } + /// Create a [`FnCallHashes`] with both native Rust and script function hashes. + #[inline(always)] + #[must_use] + pub const fn from_all(#[cfg(not(feature = "no_function"))] script: u64, native: u64) -> Self { + Self { + #[cfg(not(feature = "no_function"))] + script: if script == 0 { ALT_ZERO_HASH } else { script }, + native: if native == 0 { ALT_ZERO_HASH } else { native }, + } + } + /// Is this [`FnCallHashes`] native Rust only? + #[inline(always)] + #[must_use] + pub const fn is_native_only(&self) -> bool { + #[cfg(not(feature = "no_function"))] + return self.script == 0; + + #[cfg(feature = "no_function")] + return true; + } +} + +/// _(internals)_ A function call. +/// Exported under the `internals` feature only. +#[derive(Debug, Clone, Default, Hash)] +pub struct FnCallExpr { + /// Namespace of the function, if any. + pub namespace: Option, + /// Function name. + pub name: Identifier, + /// Pre-calculated hashes. + pub hashes: FnCallHashes, + /// List of function call argument expressions. + pub args: StaticVec, + /// List of function call arguments that are constants. + /// + /// Any arguments in `args` that is [`Expr::Stack`] indexes into this + /// array to find the constant for use as its argument value. + /// + /// # Notes + /// + /// Constant arguments are very common in function calls, and keeping each constant in + /// an [`Expr::DynamicConstant`] involves an additional allocation. Keeping the constant + /// values in an inlined array avoids these extra allocations. + pub constants: StaticVec, + /// Does this function call capture the parent scope? + pub capture_parent_scope: bool, +} + +impl FnCallExpr { + /// Does this function call contain a qualified namespace? + #[inline(always)] + #[must_use] + pub const fn is_qualified(&self) -> bool { + self.namespace.is_some() + } + /// Convert this into an [`Expr::FnCall`]. + #[inline(always)] + #[must_use] + pub fn into_fn_call_expr(self, pos: Position) -> Expr { + Expr::FnCall(self.into(), pos) + } +} + +/// A type that wraps a floating-point number and implements [`Hash`]. +/// +/// Not available under `no_float`. +#[cfg(not(feature = "no_float"))] +#[derive(Clone, Copy, PartialEq, PartialOrd)] +pub struct FloatWrapper(F); + +#[cfg(not(feature = "no_float"))] +impl Hash for FloatWrapper { + #[inline(always)] + fn hash(&self, state: &mut H) { + self.0.to_ne_bytes().hash(state); + } +} + +#[cfg(not(feature = "no_float"))] +impl AsRef for FloatWrapper { + #[inline(always)] + fn as_ref(&self) -> &F { + &self.0 + } +} + +#[cfg(not(feature = "no_float"))] +impl AsMut for FloatWrapper { + #[inline(always)] + fn as_mut(&mut self) -> &mut F { + &mut self.0 + } +} + +#[cfg(not(feature = "no_float"))] +impl std::ops::Deref for FloatWrapper { + type Target = F; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[cfg(not(feature = "no_float"))] +impl std::ops::DerefMut for FloatWrapper { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[cfg(not(feature = "no_float"))] +impl fmt::Debug for FloatWrapper { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } +} + +#[cfg(not(feature = "no_float"))] +impl> fmt::Display for FloatWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let abs = self.0.abs(); + if abs.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) + } else { + fmt::Display::fmt(&self.0, f)?; + if abs.fract().is_zero() { + f.write_str(".0")?; + } + Ok(()) + } + } +} + +#[cfg(not(feature = "no_float"))] +impl From for FloatWrapper { + #[inline(always)] + fn from(value: F) -> Self { + Self::new(value) + } +} + +#[cfg(not(feature = "no_float"))] +impl FromStr for FloatWrapper { + type Err = ::Err; + + #[inline] + fn from_str(s: &str) -> Result { + F::from_str(s).map(Into::::into) + } +} + +#[cfg(not(feature = "no_float"))] +impl FloatWrapper { + /// Maximum floating-point number for natural display before switching to scientific notation. + pub const MAX_NATURAL_FLOAT_FOR_DISPLAY: f32 = 10000000000000.0; + + /// Minimum floating-point number for natural display before switching to scientific notation. + pub const MIN_NATURAL_FLOAT_FOR_DISPLAY: f32 = 0.0000000000001; + + /// Create a new [`FloatWrapper`]. + #[inline(always)] + #[must_use] + pub fn new(value: F) -> Self { + Self(value) + } +} + +#[cfg(not(feature = "no_float"))] +impl FloatWrapper { + /// Create a new [`FloatWrapper`]. + #[inline(always)] + #[must_use] + pub const fn new_const(value: crate::FLOAT) -> Self { + Self(value) + } +} + +/// _(internals)_ An expression sub-tree. +/// Exported under the `internals` feature only. +#[derive(Clone, Hash)] +pub enum Expr { + /// Dynamic constant. + /// + /// Used to hold complex constants such as [`Array`][crate::Array] or [`Map`][crate::Map] for quick cloning. + /// Primitive data types should use the appropriate variants to avoid an allocation. + DynamicConstant(Box, Position), + /// Boolean constant. + BoolConstant(bool, Position), + /// Integer constant. + IntegerConstant(INT, Position), + /// Floating-point constant. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + FloatConstant(FloatWrapper, Position), + /// Character constant. + CharConstant(char, Position), + /// [String][ImmutableString] constant. + StringConstant(ImmutableString, Position), + /// An interpolated [string][ImmutableString]. + InterpolatedString(Box>, Position), + /// [ expr, ... ] + Array(Box>, Position), + /// #{ name:expr, ... } + Map( + Box<(StaticVec<(Ident, Expr)>, BTreeMap)>, + Position, + ), + /// () + Unit(Position), + /// Variable access - optional short index, position, (optional index, optional (hash, modules), variable name) + /// + /// The short index is [`u8`] which is used when the index is <= 255, which should be the vast + /// majority of cases (unless there are more than 255 variables defined!). + /// This is to avoid reading a pointer redirection during each variable access. + Variable( + Option, + Position, + Box<( + Option, + Option<(NamespaceRef, u64)>, + Identifier, + )>, + ), + /// Property access - ((getter, hash), (setter, hash), prop) + Property( + Box<( + (Identifier, u64), + (Identifier, u64), + (ImmutableString, Position), + )>, + ), + /// Stack slot for function calls. See [`FnCallExpr`] for more details. + /// + /// This variant does not map to any language structure. It is used in function calls with + /// constant arguments where the `usize` number indexes into an array containing a list of + /// constant arguments for the function call. + Stack(usize, Position), + /// { [statement][Stmt] ... } + Stmt(Box), + /// func `(` expr `,` ... `)` + FnCall(Box, Position), + /// lhs `.` rhs - bool variable is a dummy + Dot(Box, bool, Position), + /// expr `[` expr `]` - boolean indicates whether the dotting/indexing chain stops + Index(Box, bool, Position), + /// lhs `&&` rhs + And(Box, Position), + /// lhs `||` rhs + Or(Box, Position), + /// Custom syntax + Custom(Box, Position), +} + +impl Default for Expr { + #[inline(always)] + fn default() -> Self { + Self::Unit(Position::NONE) + } +} + +impl fmt::Debug for Expr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut display_pos = self.position(); + + match self { + Self::DynamicConstant(value, _) => write!(f, "{:?}", value), + Self::BoolConstant(value, _) => write!(f, "{:?}", value), + Self::IntegerConstant(value, _) => write!(f, "{:?}", value), + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(value, _) => write!(f, "{:?}", value), + Self::CharConstant(value, _) => write!(f, "{:?}", value), + Self::StringConstant(value, _) => write!(f, "{:?}", value), + Self::Unit(_) => f.write_str("()"), + + Self::InterpolatedString(x, _) => { + f.write_str("InterpolatedString")?; + return f.debug_list().entries(x.iter()).finish(); + } + Self::Array(x, _) => { + f.write_str("Array")?; + f.debug_list().entries(x.iter()).finish() + } + Self::Map(x, _) => { + f.write_str("Map")?; + f.debug_map() + .entries(x.0.iter().map(|(k, v)| (k, v))) + .finish() + } + Self::Variable(i, _, x) => { + f.write_str("Variable(")?; + if let Some((_, ref namespace)) = x.1 { + 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)) { + write!(f, " #{}", n)? + } + f.write_str(")") + } + Self::Property(x) => write!(f, "Property({})", (x.2).0), + Self::Stack(x, _) => write!(f, "StackSlot({})", x), + Self::Stmt(x) => { + f.write_str("ExprStmtBlock")?; + f.debug_list().entries(x.iter()).finish() + } + Self::FnCall(x, _) => { + let mut ff = f.debug_struct("FnCall"); + x.namespace.as_ref().map(|ns| ff.field("namespace", ns)); + ff.field("name", &x.name) + .field("hash", &x.hashes) + .field("args", &x.args); + if !x.constants.is_empty() { + ff.field("constants", &x.constants); + } + if x.capture_parent_scope { + ff.field("capture_parent_scope", &x.capture_parent_scope); + } + ff.finish() + } + Self::Index(x, term, pos) => { + display_pos = *pos; + + f.debug_struct("Index") + .field("lhs", &x.lhs) + .field("rhs", &x.rhs) + .field("terminate", term) + .finish() + } + Self::Dot(x, _, pos) | Self::And(x, pos) | Self::Or(x, pos) => { + let op_name = match self { + Self::Dot(_, _, _) => "Dot", + Self::And(_, _) => "And", + Self::Or(_, _) => "Or", + _ => unreachable!(), + }; + + display_pos = *pos; + + f.debug_struct(op_name) + .field("lhs", &x.lhs) + .field("rhs", &x.rhs) + .finish() + } + Self::Custom(x, _) => f.debug_tuple("Custom").field(x).finish(), + }?; + + display_pos.debug_print(f) + } +} + +impl Expr { + /// Get the [`Dynamic`] value of a literal constant expression. + /// + /// Returns [`None`] if the expression is not a literal constant. + #[inline] + #[must_use] + pub fn get_literal_value(&self) -> Option { + Some(match self { + Self::DynamicConstant(x, _) => x.as_ref().clone(), + Self::IntegerConstant(x, _) => (*x).into(), + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(x, _) => (*x).into(), + Self::CharConstant(x, _) => (*x).into(), + Self::StringConstant(x, _) => x.clone().into(), + Self::BoolConstant(x, _) => (*x).into(), + Self::Unit(_) => Dynamic::UNIT, + + #[cfg(not(feature = "no_index"))] + Self::Array(x, _) if self.is_constant() => { + let mut arr = crate::Array::with_capacity(x.len()); + arr.extend( + x.iter() + .map(|v| v.get_literal_value().expect("constant value")), + ); + Dynamic::from_array(arr) + } + + #[cfg(not(feature = "no_object"))] + Self::Map(x, _) if self.is_constant() => { + Dynamic::from_map(x.0.iter().fold(x.1.clone(), |mut map, (k, v)| { + let value_ref = map.get_mut(k.name.as_str()).expect("contains all keys"); + *value_ref = v.get_literal_value().expect("constant value"); + map + })) + } + + // Binary operators + Self::FnCall(x, _) if x.args.len() == 2 => match x.name.as_str() { + // x..y + OP_EXCLUSIVE_RANGE => { + if let Expr::IntegerConstant(ref start, _) = x.args[0] { + if let Expr::IntegerConstant(ref end, _) = x.args[1] { + (*start..*end).into() + } else { + return None; + } + } else { + return None; + } + } + // x..=y + OP_INCLUSIVE_RANGE => { + if let Expr::IntegerConstant(ref start, _) = x.args[0] { + if let Expr::IntegerConstant(ref end, _) = x.args[1] { + (*start..=*end).into() + } else { + return None; + } + } else { + return None; + } + } + _ => return None, + }, + + _ => return None, + }) + } + /// Create an [`Expr`] from a [`Dynamic`] value. + #[inline] + #[must_use] + pub fn from_dynamic(value: Dynamic, pos: Position) -> Self { + match value.0 { + Union::Unit(_, _, _) => Self::Unit(pos), + Union::Bool(b, _, _) => Self::BoolConstant(b, pos), + Union::Str(s, _, _) => Self::StringConstant(s, pos), + Union::Char(c, _, _) => Self::CharConstant(c, pos), + Union::Int(i, _, _) => Self::IntegerConstant(i, pos), + + #[cfg(feature = "decimal")] + Union::Decimal(value, _, _) => Self::DynamicConstant(Box::new((*value).into()), pos), + + #[cfg(not(feature = "no_float"))] + Union::Float(f, _, _) => Self::FloatConstant(f, pos), + + #[cfg(not(feature = "no_index"))] + Union::Array(a, _, _) => Self::DynamicConstant(Box::new((*a).into()), pos), + + #[cfg(not(feature = "no_object"))] + Union::Map(m, _, _) => Self::DynamicConstant(Box::new((*m).into()), pos), + + _ => Self::DynamicConstant(value.into(), pos), + } + } + /// Is the expression a simple variable access? + #[inline] + #[must_use] + pub(crate) const fn is_variable_access(&self, non_qualified: bool) -> bool { + match self { + Self::Variable(_, _, x) => !non_qualified || x.1.is_none(), + _ => false, + } + } + /// Return the variable name if the expression a simple variable access. + #[inline] + #[must_use] + pub(crate) fn get_variable_name(&self, non_qualified: bool) -> Option<&str> { + match self { + Self::Variable(_, _, x) if !non_qualified || x.1.is_none() => Some(x.2.as_str()), + _ => None, + } + } + /// Get the [position][Position] of the expression. + #[inline] + #[must_use] + pub const fn position(&self) -> Position { + match self { + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_, pos) => *pos, + + Self::DynamicConstant(_, pos) + | Self::BoolConstant(_, pos) + | Self::IntegerConstant(_, pos) + | Self::CharConstant(_, pos) + | Self::Unit(pos) + | Self::StringConstant(_, pos) + | Self::Array(_, pos) + | Self::Map(_, pos) + | Self::Variable(_, pos, _) + | Self::Stack(_, pos) + | Self::FnCall(_, pos) + | Self::Custom(_, pos) + | Self::InterpolatedString(_, pos) => *pos, + + Self::Property(x) => (x.2).1, + Self::Stmt(x) => x.position(), + + Self::And(x, _) | Self::Or(x, _) | Self::Dot(x, _, _) | Self::Index(x, _, _) => { + x.lhs.position() + } + } + } + /// Override the [position][Position] of the expression. + #[inline] + pub fn set_position(&mut self, new_pos: Position) -> &mut Self { + match self { + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_, pos) => *pos = new_pos, + + Self::DynamicConstant(_, pos) + | Self::BoolConstant(_, pos) + | Self::IntegerConstant(_, pos) + | Self::CharConstant(_, pos) + | Self::Unit(pos) + | Self::StringConstant(_, pos) + | Self::Array(_, pos) + | Self::Map(_, pos) + | Self::And(_, pos) + | Self::Or(_, pos) + | Self::Dot(_, _, pos) + | Self::Index(_, _, pos) + | Self::Variable(_, pos, _) + | Self::Stack(_, pos) + | Self::FnCall(_, pos) + | Self::Custom(_, pos) + | Self::InterpolatedString(_, pos) => *pos = new_pos, + + Self::Property(x) => (x.2).1 = new_pos, + Self::Stmt(x) => x.set_position(new_pos), + } + + self + } + /// Is the expression pure? + /// + /// A pure expression has no side effects. + #[inline] + #[must_use] + pub fn is_pure(&self) -> bool { + match self { + Self::InterpolatedString(x, _) | Self::Array(x, _) => x.iter().all(Self::is_pure), + + Self::Map(x, _) => x.0.iter().map(|(_, v)| v).all(Self::is_pure), + + Self::And(x, _) | Self::Or(x, _) => x.lhs.is_pure() && x.rhs.is_pure(), + + Self::Stmt(x) => x.iter().all(Stmt::is_pure), + + Self::Variable(_, _, _) | Self::Stack(_, _) => true, + + _ => self.is_constant(), + } + } + /// Is the expression the unit `()` literal? + #[inline(always)] + #[must_use] + pub const fn is_unit(&self) -> bool { + matches!(self, Self::Unit(_)) + } + /// Is the expression a constant? + #[inline] + #[must_use] + pub fn is_constant(&self) -> bool { + match self { + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_, _) => true, + + Self::DynamicConstant(_, _) + | Self::BoolConstant(_, _) + | Self::IntegerConstant(_, _) + | Self::CharConstant(_, _) + | Self::StringConstant(_, _) + | Self::Unit(_) + | Self::Stack(_, _) => true, + + Self::InterpolatedString(x, _) | Self::Array(x, _) => x.iter().all(Self::is_constant), + + Self::Map(x, _) => x.0.iter().map(|(_, expr)| expr).all(Self::is_constant), + + _ => false, + } + } + /// Is a particular [token][Token] allowed as a postfix operator to this expression? + #[inline] + #[must_use] + pub const fn is_valid_postfix(&self, token: &Token) -> bool { + match token { + #[cfg(not(feature = "no_object"))] + Token::Period => return true, + _ => (), + } + + match self { + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_, _) => false, + + Self::DynamicConstant(_, _) + | Self::BoolConstant(_, _) + | Self::CharConstant(_, _) + | Self::And(_, _) + | Self::Or(_, _) + | Self::Unit(_) => false, + + Self::IntegerConstant(_, _) + | Self::StringConstant(_, _) + | Self::InterpolatedString(_, _) + | Self::FnCall(_, _) + | Self::Stmt(_) + | Self::Dot(_, _, _) + | Self::Index(_, _, _) + | Self::Array(_, _) + | Self::Map(_, _) + | Self::Custom(_, _) => match token { + #[cfg(not(feature = "no_index"))] + Token::LeftBracket => true, + _ => false, + }, + + Self::Variable(_, _, _) => match token { + #[cfg(not(feature = "no_index"))] + Token::LeftBracket => true, + Token::LeftParen => true, + Token::Bang => true, + Token::DoubleColon => true, + _ => false, + }, + + Self::Property(_) => match token { + #[cfg(not(feature = "no_index"))] + Token::LeftBracket => true, + Token::LeftParen => true, + _ => false, + }, + + Self::Stack(_, _) => false, + } + } + /// Recursively walk this expression. + /// Return `false` from the callback to terminate the walk. + pub fn walk<'a>( + &'a self, + path: &mut Vec>, + on_node: &mut impl FnMut(&[ASTNode]) -> bool, + ) -> bool { + // Push the current node onto the path + path.push(self.into()); + + if !on_node(path) { + return false; + } + + match self { + Self::Stmt(x) => { + for s in x.iter() { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::InterpolatedString(x, _) | Self::Array(x, _) => { + for e in x.as_ref() { + if !e.walk(path, on_node) { + return false; + } + } + } + Self::Map(x, _) => { + for (_, e) in &x.0 { + if !e.walk(path, on_node) { + return false; + } + } + } + Self::Index(x, _, _) | Self::Dot(x, _, _) | Expr::And(x, _) | Expr::Or(x, _) => { + if !x.lhs.walk(path, on_node) { + return false; + } + if !x.rhs.walk(path, on_node) { + return false; + } + } + Self::FnCall(x, _) => { + for e in &x.args { + if !e.walk(path, on_node) { + return false; + } + } + } + Self::Custom(x, _) => { + for e in &x.inputs { + if !e.walk(path, on_node) { + return false; + } + } + } + _ => (), + } + + path.pop().expect("contains current node"); + + true + } +} diff --git a/src/ast/flags.rs b/src/ast/flags.rs new file mode 100644 index 00000000..4b5bc19d --- /dev/null +++ b/src/ast/flags.rs @@ -0,0 +1,169 @@ +//! Module defining script options. + +/// A type representing the access mode of a function. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +pub enum FnAccess { + /// Public function. + Public, + /// Private function. + Private, +} + +use std::ops::{Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, Not, Sub, SubAssign}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +/// A type that holds a configuration option with bit-flags. +/// Exported under the `internals` feature only. +#[derive(PartialEq, Eq, Copy, Clone, Hash, Default)] +pub struct OptionFlags(u8); + +impl OptionFlags { + /// Does this [`OptionFlags`] contain a particular option flag? + #[inline(always)] + #[must_use] + pub const fn contains(self, flag: Self) -> bool { + self.0 & flag.0 != 0 + } +} + +impl Not for OptionFlags { + type Output = Self; + + /// Return the negation of the [`OptionFlags`]. + #[inline(always)] + fn not(self) -> Self::Output { + Self(!self.0) & AST_OPTION_FLAGS::AST_OPTION_ALL + } +} + +impl Add for OptionFlags { + type Output = Self; + + /// Return the union of two [`OptionFlags`]. + #[inline(always)] + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} + +impl AddAssign for OptionFlags { + /// Add the option flags in one [`OptionFlags`] to another. + #[inline(always)] + fn add_assign(&mut self, rhs: Self) { + self.0 |= rhs.0 + } +} + +impl BitOr for OptionFlags { + type Output = Self; + + /// Return the union of two [`OptionFlags`]. + #[inline(always)] + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} + +impl BitOrAssign for OptionFlags { + /// Add the option flags in one [`OptionFlags`] to another. + #[inline(always)] + fn bitor_assign(&mut self, rhs: Self) { + self.0 |= rhs.0 + } +} + +impl Sub for OptionFlags { + type Output = Self; + + /// Return the difference of two [`OptionFlags`]. + #[inline(always)] + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 & !rhs.0) + } +} + +impl SubAssign for OptionFlags { + /// Remove the option flags in one [`OptionFlags`] from another. + #[inline(always)] + fn sub_assign(&mut self, rhs: Self) { + self.0 &= !rhs.0 + } +} + +impl BitAnd for OptionFlags { + type Output = Self; + + /// Return the intersection of two [`OptionFlags`]. + #[inline(always)] + fn bitand(self, rhs: Self) -> Self::Output { + Self(self.0 & !rhs.0) + } +} + +impl BitAndAssign for OptionFlags { + /// Keep only the intersection of one [`OptionFlags`] with another. + #[inline(always)] + fn bitand_assign(&mut self, rhs: Self) { + self.0 &= !rhs.0 + } +} + +/// Option bit-flags for [`AST`][super::AST] nodes. +#[allow(non_snake_case)] +pub mod AST_OPTION_FLAGS { + use super::OptionFlags; + + /// _(internals)_ No options for the [`AST`][crate::AST] node. + /// Exported under the `internals` feature only. + pub const AST_OPTION_NONE: OptionFlags = OptionFlags(0b0000_0000); + /// _(internals)_ The [`AST`][crate::AST] node is constant. + /// Exported under the `internals` feature only. + pub const AST_OPTION_CONSTANT: OptionFlags = OptionFlags(0b0000_0001); + /// _(internals)_ The [`AST`][crate::AST] node is public. + /// Exported under the `internals` feature only. + pub const AST_OPTION_PUBLIC: OptionFlags = OptionFlags(0b0000_0010); + /// _(internals)_ The [`AST`][crate::AST] node is in negated mode. + /// Exported under the `internals` feature only. + pub const AST_OPTION_NEGATED: OptionFlags = OptionFlags(0b0000_0100); + /// _(internals)_ The [`AST`][crate::AST] node breaks out of normal control flow. + /// Exported under the `internals` feature only. + pub const AST_OPTION_BREAK_OUT: OptionFlags = OptionFlags(0b0000_1000); + /// _(internals)_ Mask of all options. + /// Exported under the `internals` feature only. + pub(crate) const AST_OPTION_ALL: OptionFlags = OptionFlags( + AST_OPTION_CONSTANT.0 | AST_OPTION_PUBLIC.0 | AST_OPTION_NEGATED.0 | AST_OPTION_BREAK_OUT.0, + ); + + impl std::fmt::Debug for OptionFlags { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn write_option( + options: &OptionFlags, + f: &mut std::fmt::Formatter<'_>, + num_flags: &mut usize, + flag: OptionFlags, + name: &str, + ) -> std::fmt::Result { + if options.contains(flag) { + if *num_flags > 0 { + f.write_str("+")?; + } + f.write_str(name)?; + *num_flags += 1; + } + Ok(()) + } + + let num_flags = &mut 0; + + f.write_str("(")?; + write_option(self, f, num_flags, AST_OPTION_CONSTANT, "Constant")?; + write_option(self, f, num_flags, AST_OPTION_PUBLIC, "Public")?; + write_option(self, f, num_flags, AST_OPTION_NEGATED, "Negated")?; + write_option(self, f, num_flags, AST_OPTION_BREAK_OUT, "Break")?; + f.write_str(")")?; + + Ok(()) + } + } +} diff --git a/src/ast/ident.rs b/src/ast/ident.rs new file mode 100644 index 00000000..4a19878d --- /dev/null +++ b/src/ast/ident.rs @@ -0,0 +1,37 @@ +//! Module defining script identifiers. + +use crate::{Identifier, Position}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; +use std::{fmt, hash::Hash}; + +/// _(internals)_ An identifier containing a name and a [position][Position]. +/// Exported under the `internals` feature only. +#[derive(Clone, Eq, PartialEq, Hash)] +pub struct Ident { + /// Identifier name. + pub name: Identifier, + /// Position. + pub pos: Position, +} + +impl fmt::Debug for Ident { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.name)?; + self.pos.debug_print(f) + } +} + +impl AsRef for Ident { + #[inline(always)] + fn as_ref(&self) -> &str { + self.name.as_ref() + } +} + +impl Ident { + #[inline(always)] + pub fn as_str(&self) -> &str { + self.name.as_str() + } +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs new file mode 100644 index 00000000..0bb45b1e --- /dev/null +++ b/src/ast/mod.rs @@ -0,0 +1,19 @@ +//! Module defining the AST (abstract syntax tree). + +pub mod ast; +pub mod expr; +pub mod flags; +pub mod ident; +pub mod script_fn; +pub mod stmt; + +pub use ast::{ASTNode, AST}; +pub use expr::{BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnCallHashes}; +pub use flags::{FnAccess, OptionFlags, AST_OPTION_FLAGS}; +pub use ident::Ident; +#[cfg(not(feature = "no_function"))] +pub use script_fn::{ScriptFnDef, ScriptFnMetadata}; +pub use stmt::{OpAssignment, Stmt, StmtBlock}; + +#[cfg(feature = "no_function")] +pub struct ScriptFnDef; diff --git a/src/ast/script_fn.rs b/src/ast/script_fn.rs new file mode 100644 index 00000000..c0bd55ca --- /dev/null +++ b/src/ast/script_fn.rs @@ -0,0 +1,128 @@ +//! Module defining script-defined functions. +#![cfg(not(feature = "no_function"))] + +use super::{FnAccess, StmtBlock}; +use crate::{Identifier, Module, Shared, StaticVec}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; +use std::{fmt, hash::Hash}; + +/// _(internals)_ A type containing information on a script-defined function. +/// Exported under the `internals` feature only. +#[derive(Debug, Clone)] +pub struct ScriptFnDef { + /// Function body. + pub body: StmtBlock, + /// Encapsulated running environment, if any. + pub lib: Option>, + /// Encapsulated imported modules. + /// + /// Not available under `no_module`. + #[cfg(not(feature = "no_module"))] + pub mods: crate::engine::Imports, + /// Function name. + pub name: Identifier, + /// Function access mode. + pub access: FnAccess, + /// Names of function parameters. + pub params: StaticVec, + /// _(metadata)_ Function doc-comments (if any). + /// Exported under the `metadata` feature only. + #[cfg(feature = "metadata")] + pub comments: Option]>>, +} + +impl fmt::Display for ScriptFnDef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}{}({})", + match self.access { + FnAccess::Public => "", + FnAccess::Private => "private ", + }, + self.name, + self.params + .iter() + .map(|s| s.as_str()) + .collect::>() + .join(", ") + ) + } +} + +/// A type containing the metadata of a script-defined function. +/// +/// Not available under `no_function`. +/// +/// Created by [`AST::iter_functions`][super::AST::iter_functions]. +#[derive(Debug, Eq, PartialEq, Clone, Hash)] +pub struct ScriptFnMetadata<'a> { + /// _(metadata)_ Function doc-comments (if any). + /// Exported under the `metadata` feature only. + /// + /// Block doc-comments are kept in a single string slice with line-breaks within. + /// + /// Line doc-comments are kept in one string slice per line without the termination line-break. + /// + /// Leading white-spaces are stripped, and each string slice always starts with the corresponding + /// doc-comment leader: `///` or `/**`. + #[cfg(feature = "metadata")] + pub comments: Vec<&'a str>, + /// Function access mode. + pub access: FnAccess, + /// Function name. + pub name: &'a str, + /// Function parameters (if any). + pub params: Vec<&'a str>, +} + +impl fmt::Display for ScriptFnMetadata<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}{}({})", + match self.access { + FnAccess::Public => "", + FnAccess::Private => "private ", + }, + self.name, + self.params + .iter() + .cloned() + .collect::>() + .join(", ") + ) + } +} + +impl<'a> From<&'a ScriptFnDef> for ScriptFnMetadata<'a> { + #[inline] + fn from(value: &'a ScriptFnDef) -> Self { + Self { + #[cfg(feature = "metadata")] + comments: value + .comments + .as_ref() + .map_or_else(|| Vec::new(), |v| v.iter().map(Box::as_ref).collect()), + access: value.access, + name: &value.name, + params: value.params.iter().map(|s| s.as_str()).collect(), + } + } +} + +impl std::cmp::PartialOrd for ScriptFnMetadata<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl std::cmp::Ord for ScriptFnMetadata<'_> { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match self.name.cmp(other.name) { + std::cmp::Ordering::Equal => self.params.len().cmp(&other.params.len()), + cmp => cmp, + } + } +} diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs new file mode 100644 index 00000000..6cb2d6a5 --- /dev/null +++ b/src/ast/stmt.rs @@ -0,0 +1,625 @@ +//! Module defining script statements. + +use super::{ASTNode, Expr, FnCallExpr, Ident, OptionFlags, AST_OPTION_FLAGS}; +use crate::tokenizer::Token; +use crate::{calc_fn_hash, Position, StaticVec, INT}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; +use std::{ + collections::BTreeMap, + fmt, + hash::Hash, + mem, + ops::{Deref, DerefMut}, +}; + +/// _(internals)_ An op-assignment operator. +/// Exported under the `internals` feature only. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +pub struct OpAssignment<'a> { + /// Hash of the op-assignment call. + pub hash_op_assign: u64, + /// Hash of the underlying operator call (for fallback). + pub hash_op: u64, + /// Op-assignment operator. + pub op: &'a str, +} + +impl OpAssignment<'_> { + /// Create a new [`OpAssignment`]. + /// + /// # Panics + /// + /// Panics if the operator name is not an op-assignment operator. + #[must_use] + pub fn new(op: Token) -> Self { + let op_raw = op + .map_op_assignment() + .expect("op-assignment") + .literal_syntax(); + let op_assignment = op.literal_syntax(); + + Self { + hash_op_assign: calc_fn_hash(op_assignment, 2), + hash_op: calc_fn_hash(op_raw, 2), + op: op_assignment, + } + } +} + +/// _(internals)_ A scoped block of statements. +/// Exported under the `internals` feature only. +#[derive(Clone, Hash, Default)] +pub struct StmtBlock(StaticVec, Position); + +impl StmtBlock { + /// A [`StmtBlock`] that does not exist. + pub const NONE: Self = Self::empty(Position::NONE); + + /// Create a new [`StmtBlock`]. + #[must_use] + pub fn new(statements: impl IntoIterator, pos: Position) -> Self { + let mut statements: StaticVec<_> = statements.into_iter().collect(); + statements.shrink_to_fit(); + Self(statements, pos) + } + /// Create an empty [`StmtBlock`]. + #[inline(always)] + #[must_use] + pub const fn empty(pos: Position) -> Self { + Self(StaticVec::new_const(), pos) + } + /// Is this statements block empty? + #[inline(always)] + #[must_use] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + /// Number of statements in this statements block. + #[inline(always)] + #[must_use] + pub fn len(&self) -> usize { + self.0.len() + } + /// Get the statements of this statements block. + #[inline(always)] + #[must_use] + pub fn statements(&self) -> &[Stmt] { + &self.0 + } + /// Extract the statements. + #[inline(always)] + #[must_use] + pub(crate) fn take_statements(&mut self) -> StaticVec { + mem::take(&mut self.0) + } + /// Get an iterator over the statements of this statements block. + #[inline(always)] + #[must_use] + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + /// Get the position (location of the beginning `{`) of this statements block. + #[inline(always)] + #[must_use] + pub const fn position(&self) -> Position { + self.1 + } + /// Set the position (location of the beginning `{`) of this statements block. + #[inline(always)] + pub fn set_position(&mut self, pos: Position) { + self.1 = pos; + } +} + +impl Deref for StmtBlock { + type Target = StaticVec; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for StmtBlock { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl fmt::Debug for StmtBlock { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Block")?; + fmt::Debug::fmt(&self.0, f)?; + self.1.debug_print(f) + } +} + +impl From for Stmt { + #[inline(always)] + fn from(block: StmtBlock) -> Self { + Self::Block(block.0.into_boxed_slice(), block.1) + } +} + +impl IntoIterator for StmtBlock { + type Item = Stmt; + type IntoIter = smallvec::IntoIter<[Stmt; 3]>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Extend for StmtBlock { + #[inline(always)] + fn extend>(&mut self, iter: T) { + self.0.extend(iter) + } +} + +/// _(internals)_ A statement. +/// Exported under the `internals` feature only. +#[derive(Debug, Clone, Hash)] +pub enum Stmt { + /// No-op. + Noop(Position), + /// `if` expr `{` stmt `}` `else` `{` stmt `}` + If(Expr, Box<(StmtBlock, StmtBlock)>, Position), + /// `switch` expr `if` condition `{` literal or _ `=>` stmt `,` ... `}` + Switch( + Expr, + Box<( + BTreeMap, StmtBlock)>>, + StmtBlock, + StaticVec<(INT, INT, bool, Option, StmtBlock)>, + )>, + Position, + ), + /// `while` expr `{` stmt `}` | `loop` `{` stmt `}` + /// + /// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement. + While(Expr, Box, Position), + /// `do` `{` stmt `}` `while`|`until` expr + /// + /// ### Option Flags + /// + /// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `while` + /// * [`AST_OPTION_NEGATED`][AST_OPTION_FLAGS::AST_OPTION_NEGATED] = `until` + Do(Box, Expr, OptionFlags, Position), + /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` + For(Expr, Box<(Ident, Option, StmtBlock)>, Position), + /// \[`export`\] `let`|`const` id `=` expr + /// + /// ### Option Flags + /// + /// * [`AST_OPTION_PUBLIC`][AST_OPTION_FLAGS::AST_OPTION_PUBLIC] = `export` + /// * [`AST_OPTION_CONSTANT`][AST_OPTION_FLAGS::AST_OPTION_CONSTANT] = `const` + Var(Expr, Box, OptionFlags, Position), + /// expr op`=` expr + Assignment(Box<(Expr, Option>, Expr)>, Position), + /// func `(` expr `,` ... `)` + /// + /// Note - this is a duplicate of [`Expr::FnCall`] to cover the very common pattern of a single + /// function call forming one statement. + FnCall(Box, Position), + /// `{` stmt`;` ... `}` + Block(Box<[Stmt]>, Position), + /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` + TryCatch(Box<(StmtBlock, Option, StmtBlock)>, Position), + /// [expression][Expr] + Expr(Expr), + /// `continue`/`break` + /// + /// ### Option Flags + /// + /// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `continue` + /// * [`AST_OPTION_BREAK_OUT`][AST_OPTION_FLAGS::AST_OPTION_BREAK_OUT] = `break` + BreakLoop(OptionFlags, Position), + /// `return`/`throw` + /// + /// ### Option Flags + /// + /// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `return` + /// * [`AST_OPTION_BREAK_OUT`][AST_OPTION_FLAGS::AST_OPTION_BREAK_OUT] = `throw` + Return(OptionFlags, Option, Position), + /// `import` expr `as` var + /// + /// Not available under `no_module`. + #[cfg(not(feature = "no_module"))] + Import(Expr, Option>, Position), + /// `export` var `as` var `,` ... + /// + /// Not available under `no_module`. + #[cfg(not(feature = "no_module"))] + Export(Box<[(Ident, Ident)]>, Position), + /// Convert a variable to shared. + /// + /// Not available under `no_closure`. + /// + /// # Notes + /// + /// This variant does not map to any language structure. It is currently only used only to + /// convert a normal variable into a shared variable when the variable is _captured_ by a closure. + #[cfg(not(feature = "no_closure"))] + Share(crate::Identifier), +} + +impl Default for Stmt { + #[inline(always)] + fn default() -> Self { + Self::Noop(Position::NONE) + } +} + +impl From for StmtBlock { + #[inline] + 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(StaticVec::new_const(), pos), + _ => { + let pos = stmt.position(); + Self(vec![stmt].into(), pos) + } + } + } +} + +impl Stmt { + /// Is this statement [`Noop`][Stmt::Noop]? + #[inline(always)] + #[must_use] + pub const fn is_noop(&self) -> bool { + matches!(self, Self::Noop(_)) + } + /// Get the [position][Position] of this statement. + #[must_use] + pub const fn position(&self) -> Position { + match self { + Self::Noop(pos) + | Self::BreakLoop(_, pos) + | Self::Block(_, pos) + | Self::Assignment(_, pos) + | Self::FnCall(_, pos) + | Self::If(_, _, pos) + | Self::Switch(_, _, pos) + | Self::While(_, _, pos) + | Self::Do(_, _, _, pos) + | Self::For(_, _, pos) + | Self::Return(_, _, pos) + | Self::Var(_, _, _, pos) + | Self::TryCatch(_, pos) => *pos, + + Self::Expr(x) => x.position(), + + #[cfg(not(feature = "no_module"))] + Self::Import(_, _, pos) => *pos, + #[cfg(not(feature = "no_module"))] + Self::Export(_, pos) => *pos, + + #[cfg(not(feature = "no_closure"))] + Self::Share(_) => Position::NONE, + } + } + /// Override the [position][Position] of this statement. + pub fn set_position(&mut self, new_pos: Position) -> &mut Self { + match self { + Self::Noop(pos) + | Self::BreakLoop(_, pos) + | Self::Block(_, pos) + | Self::Assignment(_, pos) + | Self::FnCall(_, pos) + | Self::If(_, _, pos) + | Self::Switch(_, _, pos) + | Self::While(_, _, pos) + | Self::Do(_, _, _, pos) + | Self::For(_, _, pos) + | Self::Return(_, _, pos) + | Self::Var(_, _, _, pos) + | Self::TryCatch(_, pos) => *pos = new_pos, + + Self::Expr(x) => { + x.set_position(new_pos); + } + + #[cfg(not(feature = "no_module"))] + Self::Import(_, _, pos) => *pos = new_pos, + #[cfg(not(feature = "no_module"))] + Self::Export(_, pos) => *pos = new_pos, + + #[cfg(not(feature = "no_closure"))] + Self::Share(_) => (), + } + + self + } + /// Does this statement return a value? + #[must_use] + pub const fn returns_value(&self) -> bool { + match self { + Self::If(_, _, _) + | Self::Switch(_, _, _) + | Self::Block(_, _) + | Self::Expr(_) + | Self::FnCall(_, _) => true, + + Self::Noop(_) + | Self::While(_, _, _) + | Self::Do(_, _, _, _) + | Self::For(_, _, _) + | Self::TryCatch(_, _) => false, + + Self::Var(_, _, _, _) + | Self::Assignment(_, _) + | Self::BreakLoop(_, _) + | Self::Return(_, _, _) => false, + + #[cfg(not(feature = "no_module"))] + Self::Import(_, _, _) | Self::Export(_, _) => false, + + #[cfg(not(feature = "no_closure"))] + Self::Share(_) => false, + } + } + /// Is this statement self-terminated (i.e. no need for a semicolon terminator)? + #[must_use] + pub const fn is_self_terminated(&self) -> bool { + match self { + Self::If(_, _, _) + | Self::Switch(_, _, _) + | Self::While(_, _, _) + | Self::For(_, _, _) + | Self::Block(_, _) + | Self::TryCatch(_, _) => true, + + // A No-op requires a semicolon in order to know it is an empty statement! + Self::Noop(_) => false, + + Self::Expr(Expr::Custom(x, _)) if x.is_self_terminated() => true, + + Self::Var(_, _, _, _) + | Self::Assignment(_, _) + | Self::Expr(_) + | Self::FnCall(_, _) + | Self::Do(_, _, _, _) + | Self::BreakLoop(_, _) + | Self::Return(_, _, _) => false, + + #[cfg(not(feature = "no_module"))] + Self::Import(_, _, _) | Self::Export(_, _) => false, + + #[cfg(not(feature = "no_closure"))] + Self::Share(_) => false, + } + } + /// Is this statement _pure_? + /// + /// A pure statement has no side effects. + #[must_use] + pub fn is_pure(&self) -> bool { + match self { + Self::Noop(_) => true, + Self::Expr(expr) => expr.is_pure(), + Self::If(condition, x, _) => { + condition.is_pure() + && (x.0).0.iter().all(Stmt::is_pure) + && (x.1).0.iter().all(Stmt::is_pure) + } + Self::Switch(expr, x, _) => { + expr.is_pure() + && x.0.values().all(|block| { + block.0.as_ref().map(Expr::is_pure).unwrap_or(true) + && (block.1).0.iter().all(Stmt::is_pure) + }) + && (x.2).iter().all(|(_, _, _, condition, stmt)| { + condition.as_ref().map(Expr::is_pure).unwrap_or(true) + && stmt.0.iter().all(Stmt::is_pure) + }) + && (x.1).0.iter().all(Stmt::is_pure) + } + + // Loops that exit can be pure because it can never be infinite. + Self::While(Expr::BoolConstant(false, _), _, _) => true, + Self::Do(body, Expr::BoolConstant(x, _), options, _) + if *x == options.contains(AST_OPTION_FLAGS::AST_OPTION_NEGATED) => + { + body.iter().all(Stmt::is_pure) + } + + // Loops are never pure since they can be infinite - and that's a side effect. + Self::While(_, _, _) | Self::Do(_, _, _, _) => false, + + // For loops can be pure because if the iterable is pure, it is finite, + // so infinite loops can never occur. + Self::For(iterable, x, _) => iterable.is_pure() && (x.2).0.iter().all(Stmt::is_pure), + + Self::Var(_, _, _, _) | Self::Assignment(_, _) | Self::FnCall(_, _) => false, + Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), + Self::BreakLoop(_, _) | Self::Return(_, _, _) => false, + Self::TryCatch(x, _) => { + (x.0).0.iter().all(Stmt::is_pure) && (x.2).0.iter().all(Stmt::is_pure) + } + + #[cfg(not(feature = "no_module"))] + Self::Import(_, _, _) => false, + #[cfg(not(feature = "no_module"))] + Self::Export(_, _) => false, + + #[cfg(not(feature = "no_closure"))] + Self::Share(_) => false, + } + } + /// Is this statement _pure_ within the containing block? + /// + /// An internally pure statement only has side effects that disappear outside the block. + /// + /// Currently only variable definitions (i.e. `let` and `const`) and `import`/`export` + /// statements are internally pure. + #[inline] + #[must_use] + pub fn is_internally_pure(&self) -> bool { + match self { + Self::Var(expr, _, _, _) => expr.is_pure(), + + #[cfg(not(feature = "no_module"))] + Self::Import(expr, _, _) => expr.is_pure(), + #[cfg(not(feature = "no_module"))] + Self::Export(_, _) => true, + + _ => self.is_pure(), + } + } + /// Does this statement break the current control flow through the containing block? + /// + /// Currently this is only true for `return`, `throw`, `break` and `continue`. + /// + /// All statements following this statement will essentially be dead code. + #[inline] + #[must_use] + pub const fn is_control_flow_break(&self) -> bool { + match self { + Self::Return(_, _, _) | Self::BreakLoop(_, _) => true, + _ => false, + } + } + /// Recursively walk this statement. + /// Return `false` from the callback to terminate the walk. + pub fn walk<'a>( + &'a self, + path: &mut Vec>, + on_node: &mut impl FnMut(&[ASTNode]) -> bool, + ) -> bool { + // Push the current node onto the path + path.push(self.into()); + + if !on_node(path) { + return false; + } + + match self { + Self::Var(e, _, _, _) => { + if !e.walk(path, on_node) { + return false; + } + } + Self::If(e, x, _) => { + if !e.walk(path, on_node) { + return false; + } + for s in &(x.0).0 { + if !s.walk(path, on_node) { + return false; + } + } + for s in &(x.1).0 { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::Switch(e, x, _) => { + if !e.walk(path, on_node) { + return false; + } + for b in x.0.values() { + if !b.0.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) { + return false; + } + for s in &(b.1).0 { + if !s.walk(path, on_node) { + return false; + } + } + } + for (_, _, _, c, stmt) in &x.2 { + if !c.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) { + return false; + } + for s in &stmt.0 { + if !s.walk(path, on_node) { + return false; + } + } + } + for s in &(x.1).0 { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::While(e, s, _) | Self::Do(s, e, _, _) => { + if !e.walk(path, on_node) { + return false; + } + for s in &s.0 { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::For(e, x, _) => { + if !e.walk(path, on_node) { + return false; + } + for s in &(x.2).0 { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::Assignment(x, _) => { + if !x.0.walk(path, on_node) { + return false; + } + if !x.2.walk(path, on_node) { + return false; + } + } + Self::FnCall(x, _) => { + for s in &x.args { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::Block(x, _) => { + for s in x.iter() { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::TryCatch(x, _) => { + for s in &(x.0).0 { + if !s.walk(path, on_node) { + return false; + } + } + for s in &(x.2).0 { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::Expr(e) | Self::Return(_, Some(e), _) => { + if !e.walk(path, on_node) { + return false; + } + } + #[cfg(not(feature = "no_module"))] + Self::Import(e, _, _) => { + if !e.walk(path, on_node) { + return false; + } + } + _ => (), + } + + path.pop().expect("contains current node"); + + true + } +} diff --git a/src/func/call.rs b/src/func/call.rs index 7db44596..f1cf279b 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -561,7 +561,7 @@ impl Engine { _ => (), } - // Scripted function call? + // Script-defined function call? #[cfg(not(feature = "no_function"))] if let Some(FnResolutionCacheEntry { func, source }) = self .resolve_fn(mods, state, lib, fn_name, hashes.script, None, false, false) @@ -570,7 +570,7 @@ impl Engine { // Script function call assert!(func.is_script()); - let func = func.get_script_fn_def().expect("scripted function"); + let func = func.get_script_fn_def().expect("script-defined function"); if func.body.is_empty() { return Ok((Dynamic::UNIT, false)); @@ -1271,7 +1271,7 @@ impl Engine { match func { #[cfg(not(feature = "no_function"))] Some(f) if f.is_script() => { - let fn_def = f.get_script_fn_def().expect("scripted function"); + let fn_def = f.get_script_fn_def().expect("script-defined function"); if fn_def.body.is_empty() { Ok(Dynamic::UNIT) diff --git a/src/func/native.rs b/src/func/native.rs index e5cf5b8e..b31c1327 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -476,7 +476,7 @@ impl CallableFunction { Self::Script(_) => false, } } - /// Is this a Rhai-scripted function? + /// Is this a script-defined function? #[inline] #[must_use] pub const fn is_script(&self) -> bool { diff --git a/src/func/script.rs b/src/func/script.rs index 5e32bfcd..383dd400 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -163,7 +163,7 @@ impl Engine { result } - // Does a scripted function exist? + // Does a script-defined function exist? #[must_use] pub(crate) fn has_script_fn( &self, diff --git a/src/module/mod.rs b/src/module/mod.rs index 8152f0a9..4e7bc7a8 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1328,7 +1328,7 @@ impl Module { f.access, f.name.as_str(), f.params, - f.func.get_script_fn_def().expect("scripted function"), + f.func.get_script_fn_def().expect("script-defined function"), ) }) } @@ -1459,7 +1459,7 @@ impl Module { let mut func = f .func .get_script_fn_def() - .expect("scripted function") + .expect("script-defined function") .as_ref() .clone(); func.lib = Some(ast.shared_lib().clone()); diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index 6470e528..df6364fe 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -163,18 +163,15 @@ impl From<&crate::module::FuncInfo> for FnMetadata { signature: info.gen_signature(), doc_comments: if info.func.is_script() { #[cfg(feature = "no_function")] - { - unreachable!("scripted functions should not exist under no_function") - } + unreachable!("script-defined functions should not exist under no_function"); + #[cfg(not(feature = "no_function"))] - { - info.func - .get_script_fn_def() - .expect("scripted function") - .comments - .as_ref() - .map_or_else(|| Vec::new(), |v| v.to_vec()) - } + info.func + .get_script_fn_def() + .expect("script-defined function") + .comments + .as_ref() + .map_or_else(|| Vec::new(), |v| v.to_vec()) } else { Vec::new() }, diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 127bf18f..69d0788a 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -2042,7 +2042,7 @@ pub fn is_valid_identifier(name: impl Iterator) -> bool { first_alphabetic } -/// Is a text string a valid scripted function name? +/// Is a text string a valid script-defined function name? #[inline(always)] #[must_use] pub fn is_valid_function_name(name: impl AsRef) -> bool { diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 2542a0e4..aa098fb2 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -296,7 +296,7 @@ fn test_call_fn_events() -> Result<(), Box> { "end" => engine.call_fn(scope, ast, "end", (event_data,)).unwrap(), // The 'update' event maps to function 'update'. - // This event provides a default implementation when the scripted function is not found. + // This event provides a default implementation when the script-defined function is not found. "update" => engine .call_fn(scope, ast, "update", (event_data,)) .or_else(|err| match *err {