Skip to main content

rhai/api/
compile.rs

1//! Module that defines the public compilation API of [`Engine`].
2
3use crate::parser::{ParseResult, ParseState};
4use crate::tokenizer::TokenStream;
5use crate::{Engine, Scope, AST};
6#[cfg(feature = "no_std")]
7use std::prelude::v1::*;
8
9impl Engine {
10    /// Compile a string into an [`AST`], which can be used later for evaluation.
11    ///
12    /// # Example
13    ///
14    /// ```
15    /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
16    /// use rhai::Engine;
17    ///
18    /// let engine = Engine::new();
19    ///
20    /// // Compile a script to an AST and store it for later evaluation
21    /// let ast = engine.compile("40 + 2")?;
22    ///
23    /// for _ in 0..42 {
24    ///     assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
25    /// }
26    /// # Ok(())
27    /// # }
28    /// ```
29    #[inline(always)]
30    pub fn compile(&self, script: impl AsRef<str>) -> ParseResult<AST> {
31        self.compile_with_scope(&Scope::new(), script)
32    }
33    /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation.
34    ///
35    /// ## Constants Propagation
36    ///
37    /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within
38    /// the scope are propagated throughout the script _including_ functions. This allows functions
39    /// to be optimized based on dynamic global constants.
40    ///
41    /// # Example
42    ///
43    /// ```
44    /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
45    /// # #[cfg(not(feature = "no_optimize"))]
46    /// # {
47    /// use rhai::{Engine, Scope, OptimizationLevel};
48    ///
49    /// let mut engine = Engine::new();
50    ///
51    /// // Create initialized scope
52    /// let mut scope = Scope::new();
53    /// scope.push_constant("x", 42_i64);   // 'x' is a constant
54    ///
55    /// // Compile a script to an AST and store it for later evaluation.
56    /// // Notice that `Full` optimization is on, so constants are folded
57    /// // into function calls and operators.
58    /// let ast = engine.compile_with_scope(&mut scope,
59    ///             "if x > 40 { x } else { 0 }"    // all 'x' are replaced with 42
60    /// )?;
61    ///
62    /// // Normally this would have failed because no scope is passed into the 'eval_ast'
63    /// // call and so the variable 'x' does not exist.  Here, it passes because the script
64    /// // has been optimized and all references to 'x' are already gone.
65    /// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
66    /// # }
67    /// # Ok(())
68    /// # }
69    /// ```
70    #[inline(always)]
71    pub fn compile_with_scope(&self, scope: &Scope, script: impl AsRef<str>) -> ParseResult<AST> {
72        self.compile_scripts_with_scope(scope, &[script])
73    }
74    /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation,
75    /// embedding all imported modules.
76    ///
77    /// Not available under `no_module`.
78    ///
79    /// Modules referred by `import` statements containing literal string paths are eagerly resolved
80    /// via the current [module resolver][crate::ModuleResolver] and embedded into the resultant
81    /// [`AST`]. When it is evaluated later, `import` statement directly recall pre-resolved
82    /// [modules][crate::Module] and the resolution process is not performed again.
83    #[cfg(not(feature = "no_module"))]
84    pub fn compile_into_self_contained(
85        &self,
86        scope: &Scope,
87        script: impl AsRef<str>,
88    ) -> crate::RhaiResultOf<AST> {
89        use crate::{
90            ast::{ASTNode, Expr, Stmt},
91            func::native::shared_take_or_clone,
92            module::resolvers::StaticModuleResolver,
93        };
94        use std::collections::BTreeSet;
95
96        fn collect_imports(
97            ast: &AST,
98            resolver: &StaticModuleResolver,
99            imports: &mut BTreeSet<crate::Identifier>,
100        ) {
101            ast._walk(&mut |path| match path.last().unwrap() {
102                // Collect all `import` statements with a string constant path
103                ASTNode::Stmt(Stmt::Import(x, ..)) => match x.0 {
104                    Expr::StringConstant(ref s, ..)
105                        if !resolver.contains_path(s)
106                            && (imports.is_empty() || !imports.contains(s.as_str())) =>
107                    {
108                        imports.insert(s.clone().into());
109                        true
110                    }
111                    _ => true,
112                },
113                _ => true,
114            });
115        }
116
117        let mut ast = self.compile_with_scope(scope, script)?;
118
119        let mut resolver = StaticModuleResolver::new();
120        let mut imports = BTreeSet::new();
121
122        collect_imports(&ast, &resolver, &mut imports);
123
124        if !imports.is_empty() {
125            while let Some(path) = imports.pop_first() {
126                let path = path.clone();
127
128                match self
129                    .module_resolver()
130                    .resolve_ast(self, None, &path, crate::Position::NONE)
131                {
132                    Some(Ok(module_ast)) => collect_imports(&module_ast, &resolver, &mut imports),
133                    Some(err) => return err,
134                    None => (),
135                }
136
137                let module =
138                    self.module_resolver()
139                        .resolve(self, None, &path, crate::Position::NONE)?;
140
141                let module = shared_take_or_clone(module);
142
143                resolver.insert(path, module);
144            }
145            ast.resolver = Some(resolver.into());
146        }
147
148        Ok(ast)
149    }
150    /// When passed a list of strings, first join the strings into one large script, and then
151    /// compile them into an [`AST`] using own scope, which can be used later for evaluation.
152    ///
153    /// The scope is useful for passing constants into the script for optimization when using
154    /// [`OptimizationLevel::Full`][crate::OptimizationLevel::Full].
155    ///
156    /// ## Note
157    ///
158    /// All strings are simply parsed one after another with nothing inserted in between, not even a
159    /// newline or space.
160    ///
161    /// ## Constants Propagation
162    ///
163    /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within
164    /// the scope are propagated throughout the script _including_ functions. This allows functions
165    /// to be optimized based on dynamic global constants.
166    ///
167    /// # Example
168    ///
169    /// ```
170    /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
171    /// # #[cfg(not(feature = "no_optimize"))]
172    /// # {
173    /// use rhai::{Engine, Scope, OptimizationLevel};
174    ///
175    /// let mut engine = Engine::new();
176    ///
177    /// // Create initialized scope
178    /// let mut scope = Scope::new();
179    /// scope.push_constant("x", 42_i64);   // 'x' is a constant
180    ///
181    /// // Compile a script made up of script segments to an AST and store it for later evaluation.
182    /// // Notice that `Full` optimization is on, so constants are folded
183    /// // into function calls and operators.
184    /// let ast = engine.compile_scripts_with_scope(&mut scope, &[
185    ///             "if x > 40",            // all 'x' are replaced with 42
186    ///             "{ x } el",
187    ///             "se { 0 }"              // segments do not need to be valid scripts!
188    /// ])?;
189    ///
190    /// // Normally this would have failed because no scope is passed into the 'eval_ast'
191    /// // call and so the variable 'x' does not exist.  Here, it passes because the script
192    /// // has been optimized and all references to 'x' are already gone.
193    /// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
194    /// # }
195    /// # Ok(())
196    /// # }
197    /// ```
198    #[inline(always)]
199    pub fn compile_scripts_with_scope<S: AsRef<str>>(
200        &self,
201        scope: &Scope,
202        scripts: impl AsRef<[S]>,
203    ) -> ParseResult<AST> {
204        self.compile_scripts_with_scope_raw(
205            Some(scope),
206            scripts,
207            #[cfg(not(feature = "no_optimize"))]
208            self.optimization_level,
209        )
210    }
211    /// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level.
212    ///
213    /// ## Constants Propagation
214    ///
215    /// If not [`OptimizationLevel::None`][`crate::OptimizationLevel::None`], constants defined within the scope are propagated
216    /// throughout the script _including_ functions. This allows functions to be optimized based on
217    /// dynamic global constants.
218    #[inline]
219    pub(crate) fn compile_scripts_with_scope_raw<S: AsRef<str>>(
220        &self,
221        scope: Option<&Scope>,
222        scripts: impl AsRef<[S]>,
223        #[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel,
224    ) -> ParseResult<AST> {
225        let (stream, tc) = self.lex(scripts.as_ref());
226
227        let input = &mut TokenStream::new(stream);
228        let lib = &mut <_>::default();
229        let state = ParseState::new(scope, input, tc.clone(), lib);
230        let mut _ast = self.parse(
231            state,
232            #[cfg(not(feature = "no_optimize"))]
233            optimization_level,
234        )?;
235        #[cfg(feature = "metadata")]
236        {
237            let global_comments = &tc.borrow().global_comments;
238            _ast.doc = global_comments.into();
239        }
240        Ok(_ast)
241    }
242    /// Compile a string containing an expression into an [`AST`],
243    /// which can be used later for evaluation.
244    ///
245    /// # Example
246    ///
247    /// ```
248    /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
249    /// use rhai::Engine;
250    ///
251    /// let engine = Engine::new();
252    ///
253    /// // Compile a script to an AST and store it for later evaluation
254    /// let ast = engine.compile_expression("40 + 2")?;
255    ///
256    /// for _ in 0..42 {
257    ///     assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
258    /// }
259    /// # Ok(())
260    /// # }
261    /// ```
262    #[inline(always)]
263    pub fn compile_expression(&self, script: impl AsRef<str>) -> ParseResult<AST> {
264        self.compile_expression_with_scope(&Scope::new(), script)
265    }
266    /// Compile a string containing an expression into an [`AST`] using own scope,
267    /// which can be used later for evaluation.
268    ///
269    /// # Example
270    ///
271    /// ```
272    /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
273    /// # #[cfg(not(feature = "no_optimize"))]
274    /// # {
275    /// use rhai::{Engine, Scope, OptimizationLevel};
276    ///
277    /// let mut engine = Engine::new();
278    ///
279    /// // Create initialized scope
280    /// let mut scope = Scope::new();
281    /// scope.push_constant("x", 10_i64);   // 'x' is a constant
282    ///
283    /// // Compile a script to an AST and store it for later evaluation.
284    /// let ast = engine.compile_expression_with_scope(&mut scope,
285    ///             "2 + (x + x) * 2"    // all 'x' are replaced with 10
286    /// )?;
287    ///
288    /// // Normally this would have failed because no scope is passed into the 'eval_ast'
289    /// // call and so the variable 'x' does not exist.  Here, it passes because the script
290    /// // has been optimized and all references to 'x' are already gone.
291    /// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
292    /// # }
293    /// # Ok(())
294    /// # }
295    /// ```
296    #[inline]
297    pub fn compile_expression_with_scope(
298        &self,
299        scope: &Scope,
300        script: impl AsRef<str>,
301    ) -> ParseResult<AST> {
302        let scripts = [script];
303        let (stream, t) = self.lex(&scripts);
304
305        let input = &mut TokenStream::new(stream);
306        let lib = &mut <_>::default();
307        let state = ParseState::new(Some(scope), input, t, lib);
308
309        self.parse_global_expr(
310            state,
311            |_| {},
312            #[cfg(not(feature = "no_optimize"))]
313            self.optimization_level,
314        )
315    }
316}