rhai/src/api/compile.rs

304 lines
11 KiB
Rust
Raw Normal View History

2021-11-20 14:57:21 +08:00
//! Module that defines the public compilation API of [`Engine`].
2021-12-25 23:49:14 +08:00
use crate::parser::{ParseResult, ParseState};
2022-02-07 21:03:39 +08:00
use crate::{Engine, OptimizationLevel, Scope, AST};
2022-07-25 13:40:23 +08:00
use std::mem;
2021-11-20 14:57:21 +08:00
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
impl Engine {
/// Compile a string into an [`AST`], which can be used later for evaluation.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::Engine;
///
/// let engine = Engine::new();
///
/// // Compile a script to an AST and store it for later evaluation
/// let ast = engine.compile("40 + 2")?;
///
/// for _ in 0..42 {
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
/// }
/// # Ok(())
/// # }
/// ```
#[inline(always)]
2021-12-25 23:49:14 +08:00
pub fn compile(&self, script: impl AsRef<str>) -> ParseResult<AST> {
2021-11-20 14:57:21 +08:00
self.compile_with_scope(&Scope::new(), script)
}
/// Compile a string into an [`AST`] using own scope, which can be used later for evaluation.
///
/// ## Constants Propagation
///
2021-11-20 21:29:36 +08:00
/// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within
/// the scope are propagated throughout the script _including_ functions. This allows functions
/// to be optimized based on dynamic global constants.
2021-11-20 14:57:21 +08:00
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_optimize"))]
/// # {
/// use rhai::{Engine, Scope, OptimizationLevel};
///
/// let mut engine = Engine::new();
///
/// // Create initialized scope
/// let mut scope = Scope::new();
/// scope.push_constant("x", 42_i64); // 'x' is a constant
///
/// // Compile a script to an AST and store it for later evaluation.
/// // Notice that `Full` optimization is on, so constants are folded
/// // into function calls and operators.
/// let ast = engine.compile_with_scope(&mut scope,
/// "if x > 40 { x } else { 0 }" // all 'x' are replaced with 42
/// )?;
///
/// // Normally this would have failed because no scope is passed into the 'eval_ast'
/// // call and so the variable 'x' does not exist. Here, it passes because the script
/// // has been optimized and all references to 'x' are already gone.
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
/// # }
/// # Ok(())
/// # }
/// ```
#[inline(always)]
2021-12-25 23:49:14 +08:00
pub fn compile_with_scope(&self, scope: &Scope, script: impl AsRef<str>) -> ParseResult<AST> {
2021-11-20 14:57:21 +08:00
self.compile_scripts_with_scope(scope, &[script])
}
/// Compile a string into an [`AST`] using own scope, which can be used later for evaluation,
/// embedding all imported modules.
///
/// Not available under `no_module`.
///
/// Modules referred by `import` statements containing literal string paths are eagerly resolved
/// via the current [module resolver][crate::ModuleResolver] and embedded into the resultant
/// [`AST`]. When it is evaluated later, `import` statement directly recall pre-resolved
2021-11-20 21:29:36 +08:00
/// [modules][crate::Module] and the resolution process is not performed again.
2021-11-20 14:57:21 +08:00
#[cfg(not(feature = "no_module"))]
pub fn compile_into_self_contained(
&self,
scope: &Scope,
2021-11-27 23:04:45 +08:00
script: impl AsRef<str>,
2021-12-27 22:28:11 +08:00
) -> crate::RhaiResultOf<AST> {
2021-11-20 14:57:21 +08:00
use crate::{
ast::{ASTNode, Expr, Stmt},
func::native::shared_take_or_clone,
module::resolvers::StaticModuleResolver,
};
use std::collections::BTreeSet;
fn collect_imports(
ast: &AST,
resolver: &StaticModuleResolver,
2021-11-27 23:29:32 +08:00
imports: &mut BTreeSet<crate::Identifier>,
2021-11-20 14:57:21 +08:00
) {
2022-01-06 11:07:52 +08:00
ast.walk(&mut |path| match path.last().unwrap() {
// Collect all `import` statements with a string constant path
2022-02-16 17:51:14 +08:00
ASTNode::Stmt(Stmt::Import(x, ..)) => match x.0 {
Expr::StringConstant(ref s, ..)
2022-03-03 13:02:57 +08:00
if !resolver.contains_path(s)
&& (imports.is_empty() || !imports.contains(s.as_str())) =>
2022-02-16 17:51:14 +08:00
{
imports.insert(s.clone().into());
true
}
_ => true,
},
2022-01-06 11:07:52 +08:00
_ => true,
});
2021-11-20 14:57:21 +08:00
}
let mut ast = self.compile_scripts_with_scope(scope, &[script])?;
2022-03-20 21:58:43 +08:00
let mut resolver = StaticModuleResolver::new();
let mut imports = BTreeSet::new();
2021-11-20 14:57:21 +08:00
2022-03-20 21:58:43 +08:00
collect_imports(&ast, &resolver, &mut imports);
2021-11-20 14:57:21 +08:00
2022-03-20 21:58:43 +08:00
if !imports.is_empty() {
while let Some(path) = imports.iter().next() {
let path = path.clone();
2021-11-20 14:57:21 +08:00
2022-03-20 21:58:43 +08:00
match self
.module_resolver
.resolve_ast(self, None, &path, crate::Position::NONE)
{
Some(Ok(module_ast)) => collect_imports(&module_ast, &resolver, &mut imports),
Some(err) => return err,
None => (),
}
2021-11-20 14:57:21 +08:00
2022-03-20 21:58:43 +08:00
let module =
self.module_resolver
.resolve(self, None, &path, crate::Position::NONE)?;
2021-11-20 14:57:21 +08:00
2022-03-20 21:58:43 +08:00
let module = shared_take_or_clone(module);
imports.remove(&path);
resolver.insert(path, module);
2021-11-20 14:57:21 +08:00
}
2022-03-20 21:58:43 +08:00
ast.set_resolver(resolver);
2021-11-20 14:57:21 +08:00
}
Ok(ast)
}
2021-11-20 21:29:36 +08:00
/// When passed a list of strings, first join the strings into one large script, and then
/// compile them into an [`AST`] using own scope, which can be used later for evaluation.
2021-11-20 14:57:21 +08:00
///
2021-11-20 21:29:36 +08:00
/// The scope is useful for passing constants into the script for optimization when using
/// [`OptimizationLevel::Full`][crate::OptimizationLevel::Full].
2021-11-20 14:57:21 +08:00
///
/// ## Note
///
2021-11-20 21:29:36 +08:00
/// All strings are simply parsed one after another with nothing inserted in between, not even a
/// newline or space.
2021-11-20 14:57:21 +08:00
///
/// ## Constants Propagation
///
2021-11-20 21:29:36 +08:00
/// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within
/// the scope are propagated throughout the script _including_ functions. This allows functions
/// to be optimized based on dynamic global constants.
2021-11-20 14:57:21 +08:00
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_optimize"))]
/// # {
/// use rhai::{Engine, Scope, OptimizationLevel};
///
/// let mut engine = Engine::new();
///
/// // Create initialized scope
/// let mut scope = Scope::new();
/// scope.push_constant("x", 42_i64); // 'x' is a constant
///
/// // Compile a script made up of script segments to an AST and store it for later evaluation.
/// // Notice that `Full` optimization is on, so constants are folded
/// // into function calls and operators.
/// let ast = engine.compile_scripts_with_scope(&mut scope, &[
/// "if x > 40", // all 'x' are replaced with 42
/// "{ x } el",
/// "se { 0 }" // segments do not need to be valid scripts!
/// ])?;
///
/// // Normally this would have failed because no scope is passed into the 'eval_ast'
/// // call and so the variable 'x' does not exist. Here, it passes because the script
/// // has been optimized and all references to 'x' are already gone.
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
/// # }
/// # Ok(())
/// # }
/// ```
#[inline(always)]
2022-01-04 15:22:48 +08:00
pub fn compile_scripts_with_scope<S: AsRef<str>>(
2021-11-20 14:57:21 +08:00
&self,
scope: &Scope,
2022-01-04 15:22:48 +08:00
scripts: impl AsRef<[S]>,
2021-12-25 23:49:14 +08:00
) -> ParseResult<AST> {
2022-05-21 22:13:02 +08:00
self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level)
2021-11-20 14:57:21 +08:00
}
/// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level.
///
/// ## Constants Propagation
///
/// If not [`OptimizationLevel::None`], constants defined within the scope are propagated
/// throughout the script _including_ functions. This allows functions to be optimized based on
/// dynamic global constants.
#[inline]
2022-01-04 15:22:48 +08:00
pub(crate) fn compile_with_scope_and_optimization_level<S: AsRef<str>>(
2021-11-20 14:57:21 +08:00
&self,
scope: &Scope,
2022-01-04 15:22:48 +08:00
scripts: impl AsRef<[S]>,
2022-02-07 21:03:39 +08:00
optimization_level: OptimizationLevel,
2021-12-25 23:49:14 +08:00
) -> ParseResult<AST> {
2022-01-04 15:22:48 +08:00
let (stream, tokenizer_control) = self.lex_raw(
scripts.as_ref(),
2022-07-05 16:26:38 +08:00
self.token_mapper.as_ref().map(<_>::as_ref),
2022-01-04 15:22:48 +08:00
);
let mut state = ParseState::new(self, scope, tokenizer_control);
2022-07-25 13:40:23 +08:00
let mut ast = self.parse(&mut stream.peekable(), &mut state, optimization_level)?;
#[cfg(feature = "metadata")]
ast.set_doc(mem::take(
&mut state.tokenizer_control.borrow_mut().global_comments,
));
Ok(ast)
2021-11-20 14:57:21 +08:00
}
/// Compile a string containing an expression into an [`AST`],
/// which can be used later for evaluation.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::Engine;
///
/// let engine = Engine::new();
///
/// // Compile a script to an AST and store it for later evaluation
/// let ast = engine.compile_expression("40 + 2")?;
///
/// for _ in 0..42 {
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
/// }
/// # Ok(())
/// # }
/// ```
#[inline(always)]
2021-12-25 23:49:14 +08:00
pub fn compile_expression(&self, script: impl AsRef<str>) -> ParseResult<AST> {
2021-11-20 14:57:21 +08:00
self.compile_expression_with_scope(&Scope::new(), script)
}
/// Compile a string containing an expression into an [`AST`] using own scope,
/// which can be used later for evaluation.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_optimize"))]
/// # {
/// use rhai::{Engine, Scope, OptimizationLevel};
///
/// let mut engine = Engine::new();
///
/// // Create initialized scope
/// let mut scope = Scope::new();
/// scope.push_constant("x", 10_i64); // 'x' is a constant
///
/// // Compile a script to an AST and store it for later evaluation.
/// // Notice that `Full` optimization is on, so constants are folded
/// // into function calls and operators.
/// let ast = engine.compile_expression_with_scope(&mut scope,
/// "2 + (x + x) * 2" // all 'x' are replaced with 10
/// )?;
///
/// // Normally this would have failed because no scope is passed into the 'eval_ast'
/// // call and so the variable 'x' does not exist. Here, it passes because the script
/// // has been optimized and all references to 'x' are already gone.
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
/// # }
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn compile_expression_with_scope(
&self,
scope: &Scope,
2021-11-27 23:04:45 +08:00
script: impl AsRef<str>,
2021-12-25 23:49:14 +08:00
) -> ParseResult<AST> {
2021-11-20 14:57:21 +08:00
let scripts = [script];
let (stream, tokenizer_control) =
2022-07-05 16:26:38 +08:00
self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref));
2021-11-20 14:57:21 +08:00
let mut peekable = stream.peekable();
let mut state = ParseState::new(self, scope, tokenizer_control);
2022-05-21 22:13:02 +08:00
self.parse_global_expr(&mut peekable, &mut state, self.optimization_level)
2021-11-20 14:57:21 +08:00
}
}