Add AST::iter_literal_variables.

This commit is contained in:
Stephen Chung 2021-11-09 13:22:45 +08:00
parent e63cba9caf
commit f6dc440601
3 changed files with 134 additions and 4 deletions

View File

@ -14,6 +14,7 @@ New features
* `#[cfg(...)]` attributes can now be put directly on plugin functions or function defined in a plugin module.
* A custom syntax parser can now return a symbol starting with `$$` to inform the implementation function which syntax variant was actually parsed.
* `AST::iter_literal_variables` extracts all top-level literal constant/variable definitions from a script without running it.
Enhancements
------------

View File

@ -749,6 +749,86 @@ impl AST {
self.body = StmtBlock::empty();
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<rhai::EvalAltResult>> {
/// 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()));
///
/// 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()));
///
/// 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();
///
/// assert_eq!(scope.len(), 2);
///
/// Ok(())
/// # }
/// ```
pub fn iter_literal_variables(
&self,
include_constants: bool,
include_variables: bool,
) -> impl Iterator<Item = (&str, bool, Dynamic)> {
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"))]
@ -835,7 +915,7 @@ impl AsRef<Module> for AST {
pub struct Ident {
/// Identifier name.
pub name: Identifier,
/// Declaration position.
/// Position.
pub pos: Position,
}
@ -846,6 +926,20 @@ impl fmt::Debug for Ident {
}
}
impl AsRef<str> 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.
///
@ -1404,7 +1498,7 @@ impl Stmt {
///
/// An internally pure statement only has side effects that disappear outside the block.
///
/// Currently only variable declarations (i.e. `let` and `const`) and `import`/`export`
/// Currently only variable definitions (i.e. `let` and `const`) and `import`/`export`
/// statements are internally pure.
#[inline]
#[must_use]

View File

@ -2,6 +2,7 @@
use crate::dynamic::{AccessMode, Variant};
use crate::{Dynamic, Identifier, StaticVec};
use std::iter::FromIterator;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{borrow::Cow, iter::Extend};
@ -592,8 +593,42 @@ impl<'a, K: Into<Cow<'a, str>>> Extend<(K, Dynamic)> for Scope<'a> {
#[inline]
fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(name, value)| {
self.names.push((name.into(), None));
self.values.push(value);
self.push_dynamic_value(name, AccessMode::ReadWrite, value);
});
}
}
impl<'a, K: Into<Cow<'a, str>>> FromIterator<(K, Dynamic)> for Scope<'a> {
#[inline]
fn from_iter<T: IntoIterator<Item = (K, Dynamic)>>(iter: T) -> Self {
let mut scope = Self::new();
scope.extend(iter);
scope
}
}
impl<'a, K: Into<Cow<'a, str>>> Extend<(K, bool, Dynamic)> for Scope<'a> {
#[inline]
fn extend<T: IntoIterator<Item = (K, bool, Dynamic)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(name, is_constant, value)| {
self.push_dynamic_value(
name,
if is_constant {
AccessMode::ReadOnly
} else {
AccessMode::ReadWrite
},
value,
);
});
}
}
impl<'a, K: Into<Cow<'a, str>>> FromIterator<(K, bool, Dynamic)> for Scope<'a> {
#[inline]
fn from_iter<T: IntoIterator<Item = (K, bool, Dynamic)>>(iter: T) -> Self {
let mut scope = Self::new();
scope.extend(iter);
scope
}
}