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}