spo_rhai/eval/
expr.rs

1//! Module defining functions for evaluating an expression.
2
3use super::{Caches, EvalContext, GlobalRuntimeState, Target};
4use crate::ast::Expr;
5use crate::packages::string_basic::{print_with_func, FUNC_TO_STRING};
6use crate::types::dynamic::AccessMode;
7use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, SmartString, ERR};
8#[cfg(feature = "no_std")]
9use std::prelude::v1::*;
10use std::{fmt::Write, num::NonZeroUsize};
11
12/// Search for a module within an imports stack.
13#[cfg(not(feature = "no_module"))]
14#[inline]
15#[must_use]
16pub fn search_imports(
17    engine: &Engine,
18    global: &GlobalRuntimeState,
19    namespace: &crate::ast::Namespace,
20) -> Option<crate::SharedModule> {
21    debug_assert!(!namespace.is_empty());
22
23    let root = namespace.root();
24
25    // Qualified - check if the root module is directly indexed
26    if !global.always_search_scope {
27        if let Some(index) = namespace.index {
28            let offset = global.num_imports() - index.get();
29
30            if let m @ Some(_) = global.get_shared_import(offset) {
31                return m;
32            }
33        }
34    }
35
36    // Do a text-match search if the index doesn't work
37    global.find_import(root).map_or_else(
38        || engine.global_sub_modules.get(root).cloned(),
39        |offset| global.get_shared_import(offset),
40    )
41}
42
43/// Search for a variable within the scope
44///
45/// # Panics
46///
47/// Panics if `expr` is not [`Expr::Variable`].
48pub fn search_scope_only<'s>(
49    engine: &Engine,
50    global: &mut GlobalRuntimeState,
51    caches: &mut Caches,
52    scope: &'s mut Scope,
53    this_ptr: Option<&'s mut Dynamic>,
54    expr: &Expr,
55) -> RhaiResultOf<Target<'s>> {
56    // Make sure that the pointer indirection is taken only when absolutely necessary.
57
58    let index = match expr {
59        // Check if the variable is `this`
60        Expr::ThisPtr(..) => unreachable!("Expr::ThisPtr should have been handled outside"),
61
62        _ if global.always_search_scope => 0,
63
64        Expr::Variable(_, Some(i), ..) => i.get() as usize,
65        Expr::Variable(v, None, ..) => {
66            // Scripted function with the same name
67            #[cfg(not(feature = "no_function"))]
68            if let Some(fn_def) = global
69                .lib
70                .iter()
71                .flat_map(|m| m.iter_script_fn())
72                .find_map(|(_, _, f, _, func)| if f == v.3 { Some(func) } else { None })
73            {
74                let val: Dynamic = crate::FnPtr {
75                    name: v.3.clone(),
76                    curry: <_>::default(),
77                    environ: None,
78                    fn_def: Some(fn_def.clone()),
79                }
80                .into();
81                return Ok(val.into());
82            }
83
84            v.0.map_or(0, NonZeroUsize::get)
85        }
86
87        _ => unreachable!("Expr::Variable expected but gets {:?}", expr),
88    };
89
90    // Check the variable resolver, if any
91    if let Some(ref resolve_var) = engine.resolve_var {
92        let orig_scope_len = scope.len();
93
94        let context = EvalContext::new(engine, global, caches, scope, this_ptr);
95        let var_name = expr.get_variable_name(true).unwrap();
96        let resolved_var = resolve_var(var_name, index, context);
97
98        if orig_scope_len != scope.len() {
99            // The scope is changed, always search from now on
100            global.always_search_scope = true;
101        }
102
103        match resolved_var {
104            Ok(Some(mut result)) => {
105                result.set_access_mode(AccessMode::ReadOnly);
106                return Ok(result.into());
107            }
108            Ok(None) => (),
109            Err(err) => return Err(err.fill_position(expr.position())),
110        }
111    }
112
113    let index = if index > 0 {
114        scope.len() - index
115    } else {
116        // Find the variable in the scope
117        let var_name = expr.get_variable_name(true).unwrap();
118
119        match scope.search(var_name) {
120            Some(index) => index,
121            None => {
122                return engine
123                    .global_modules
124                    .iter()
125                    .find_map(|m| m.get_var(var_name))
126                    .map_or_else(
127                        || {
128                            Err(
129                                ERR::ErrorVariableNotFound(var_name.to_string(), expr.position())
130                                    .into(),
131                            )
132                        },
133                        |val| Ok(val.into()),
134                    )
135            }
136        }
137    };
138
139    let val = scope.get_mut_by_index(index);
140
141    Ok(val.into())
142}
143
144/// Search for a variable within the scope or within imports,
145/// depending on whether the variable name is namespace-qualified.
146pub fn search_namespace<'s>(
147    engine: &Engine,
148    global: &mut GlobalRuntimeState,
149    caches: &mut Caches,
150    scope: &'s mut Scope,
151    this_ptr: Option<&'s mut Dynamic>,
152    expr: &Expr,
153) -> RhaiResultOf<Target<'s>> {
154    match expr {
155        Expr::Variable(_, Some(_), _) => {
156            search_scope_only(engine, global, caches, scope, this_ptr, expr)
157        }
158        Expr::Variable(v, None, ..) => match &**v {
159            // Normal variable access
160            (_, ns, ..) if ns.is_empty() => {
161                search_scope_only(engine, global, caches, scope, this_ptr, expr)
162            }
163
164            // Qualified variable access
165            #[cfg(not(feature = "no_module"))]
166            (_, ns, hash_var, var_name) => {
167                // foo:bar::baz::VARIABLE
168                if let Some(module) = search_imports(engine, global, ns) {
169                    return module.get_qualified_var(*hash_var).map_or_else(
170                        || {
171                            let sep = crate::engine::NAMESPACE_SEPARATOR;
172
173                            Err(ERR::ErrorVariableNotFound(
174                                format!("{ns}{sep}{var_name}"),
175                                ns.position(),
176                            )
177                            .into())
178                        },
179                        |mut target| {
180                            // Module variables are constant
181                            target.set_access_mode(AccessMode::ReadOnly);
182                            Ok(target.into())
183                        },
184                    );
185                }
186
187                // global::VARIABLE
188                #[cfg(not(feature = "no_function"))]
189                if ns.path.len() == 1 && ns.root() == crate::engine::KEYWORD_GLOBAL {
190                    if let Some(ref constants) = global.constants {
191                        if let Some(value) =
192                            crate::func::locked_write(constants).get_mut(var_name.as_str())
193                        {
194                            let mut target: Target = value.clone().into();
195                            // Module variables are constant
196                            target.as_mut().set_access_mode(AccessMode::ReadOnly);
197                            return Ok(target);
198                        }
199                    }
200
201                    let sep = crate::engine::NAMESPACE_SEPARATOR;
202
203                    return Err(ERR::ErrorVariableNotFound(
204                        format!("{ns}{sep}{var_name}"),
205                        ns.position(),
206                    )
207                    .into());
208                }
209
210                Err(ERR::ErrorModuleNotFound(ns.to_string(), ns.position()).into())
211            }
212
213            #[cfg(feature = "no_module")]
214            _ => unreachable!("Invalid expression {:?}", expr),
215        },
216        _ => unreachable!("Expr::Variable expected but gets {:?}", expr),
217    }
218}
219
220impl Engine {
221    /// Evaluate an expression.
222    pub(crate) fn eval_expr(
223        &self,
224        global: &mut GlobalRuntimeState,
225        caches: &mut Caches,
226        scope: &mut Scope,
227        mut this_ptr: Option<&mut Dynamic>,
228        expr: &Expr,
229    ) -> RhaiResult {
230        self.track_operation(global, expr.position())?;
231
232        #[cfg(feature = "debugging")]
233        let reset =
234            self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
235        #[cfg(feature = "debugging")]
236        defer! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }
237
238        match expr {
239            // Constants
240            Expr::IntegerConstant(x, ..) => Ok((*x).into()),
241            Expr::StringConstant(x, ..) => Ok(x.clone().into()),
242            Expr::BoolConstant(x, ..) => Ok((*x).into()),
243            #[cfg(not(feature = "no_float"))]
244            Expr::FloatConstant(x, ..) => Ok((*x).into()),
245            Expr::CharConstant(x, ..) => Ok((*x).into()),
246            Expr::Unit(..) => Ok(Dynamic::UNIT),
247            Expr::DynamicConstant(x, ..) => Ok(x.as_ref().clone()),
248
249            Expr::FnCall(x, pos) => {
250                self.eval_fn_call_expr(global, caches, scope, this_ptr, x, *pos)
251            }
252
253            Expr::ThisPtr(var_pos) => this_ptr
254                .ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into())
255                .cloned(),
256
257            Expr::Variable(..) => search_namespace(self, global, caches, scope, this_ptr, expr)
258                .map(Target::take_or_clone),
259
260            Expr::InterpolatedString(x, _) => {
261                let mut concat = SmartString::new_const();
262
263                for expr in &**x {
264                    let item = &mut self
265                        .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)?
266                        .flatten();
267                    let pos = expr.position();
268
269                    if item.is_string() {
270                        write!(concat, "{item}").unwrap();
271                    } else {
272                        let source = global.source();
273                        let context = &(self, FUNC_TO_STRING, source, &*global, pos).into();
274                        let display = print_with_func(FUNC_TO_STRING, context, item);
275                        write!(concat, "{display}").unwrap();
276                    }
277
278                    #[cfg(not(feature = "unchecked"))]
279                    self.throw_on_size((0, 0, concat.len()))
280                        .map_err(|err| err.fill_position(pos))?;
281                }
282
283                Ok(self.get_interned_string(concat).into())
284            }
285
286            #[cfg(not(feature = "no_index"))]
287            Expr::Array(x, ..) => {
288                let mut array = crate::Array::with_capacity(x.len());
289
290                #[cfg(not(feature = "unchecked"))]
291                let mut total_data_sizes = (0, 0, 0);
292
293                for item_expr in &**x {
294                    let value = self
295                        .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), item_expr)?
296                        .flatten();
297
298                    #[cfg(not(feature = "unchecked"))]
299                    if self.has_data_size_limit() {
300                        let val_sizes = crate::eval::calc_data_sizes(&value, true);
301
302                        total_data_sizes = (
303                            total_data_sizes.0 + val_sizes.0 + 1,
304                            total_data_sizes.1 + val_sizes.1,
305                            total_data_sizes.2 + val_sizes.2,
306                        );
307                        self.throw_on_size(total_data_sizes)
308                            .map_err(|err| err.fill_position(item_expr.position()))?;
309                    }
310
311                    array.push(value);
312                }
313
314                Ok(Dynamic::from_array(array))
315            }
316
317            #[cfg(not(feature = "no_object"))]
318            #[cfg(not(feature = "indexmap"))]
319            Expr::Map(x, ..) => {
320                let mut map = x.1.clone();
321
322                #[cfg(not(feature = "unchecked"))]
323                let mut total_data_sizes = (0, 0, 0);
324
325                for (key, value_expr) in &x.0 {
326                    let value = self
327                        .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), value_expr)?
328                        .flatten();
329
330                    #[cfg(not(feature = "unchecked"))]
331                    if self.has_data_size_limit() {
332                        let delta = crate::eval::calc_data_sizes(&value, true);
333                        total_data_sizes = (
334                            total_data_sizes.0 + delta.0,
335                            total_data_sizes.1 + delta.1 + 1,
336                            total_data_sizes.2 + delta.2,
337                        );
338                        self.throw_on_size(total_data_sizes)
339                            .map_err(|err| err.fill_position(value_expr.position()))?;
340                    }
341
342                    *map.get_mut(key.as_str()).unwrap() = value;
343                }
344
345                Ok(Dynamic::from_map(map))
346            }
347
348            #[cfg(not(feature = "no_object"))]
349            #[cfg(feature = "indexmap")]
350            Expr::Map(x, ..) => {
351                let mut map = crate::Map::with_capacity(x.0.len());
352
353                #[cfg(not(feature = "unchecked"))]
354                let mut total_data_sizes = (0, 0, 0);
355
356                for (key, value_expr) in &x.0 {
357                    let value = self
358                        .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), value_expr)?
359                        .flatten();
360
361                    #[cfg(not(feature = "unchecked"))]
362                    if self.has_data_size_limit() {
363                        let delta = crate::eval::calc_data_sizes(&value, true);
364                        total_data_sizes = (
365                            total_data_sizes.0 + delta.0,
366                            total_data_sizes.1 + delta.1 + 1,
367                            total_data_sizes.2 + delta.2,
368                        );
369                        self.throw_on_size(total_data_sizes)
370                            .map_err(|err| err.fill_position(value_expr.position()))?;
371                    }
372
373                    map.insert(key.as_str().into(), value);
374                }
375
376                Ok(Dynamic::from_map(map))
377            }
378
379            Expr::And(x, ..) => Ok((self
380                .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), &x.lhs)?
381                .as_bool()
382                .map_err(|typ| self.make_type_mismatch_err::<bool>(typ, x.lhs.position()))?
383                && self
384                    .eval_expr(global, caches, scope, this_ptr, &x.rhs)?
385                    .as_bool()
386                    .map_err(|typ| self.make_type_mismatch_err::<bool>(typ, x.rhs.position()))?)
387            .into()),
388
389            Expr::Or(x, ..) => Ok((self
390                .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), &x.lhs)?
391                .as_bool()
392                .map_err(|typ| self.make_type_mismatch_err::<bool>(typ, x.lhs.position()))?
393                || self
394                    .eval_expr(global, caches, scope, this_ptr, &x.rhs)?
395                    .as_bool()
396                    .map_err(|typ| self.make_type_mismatch_err::<bool>(typ, x.rhs.position()))?)
397            .into()),
398
399            Expr::Coalesce(x, ..) => {
400                let value =
401                    self.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), &x.lhs)?;
402
403                if value.is_unit() {
404                    self.eval_expr(global, caches, scope, this_ptr, &x.rhs)
405                } else {
406                    Ok(value)
407                }
408            }
409
410            #[cfg(not(feature = "no_custom_syntax"))]
411            Expr::Custom(custom, pos) => {
412                let expressions: crate::StaticVec<_> =
413                    custom.inputs.iter().map(Into::into).collect();
414                // The first token acts as the custom syntax's key
415                let key_token = custom.tokens.first().unwrap();
416                // The key should exist, unless the AST is compiled in a different Engine
417                let custom_def = self.custom_syntax.get(key_token.as_str()).ok_or_else(|| {
418                    Box::new(ERR::ErrorCustomSyntax(
419                        format!("Invalid custom syntax prefix: {key_token}"),
420                        custom.tokens.iter().map(<_>::to_string).collect(),
421                        *pos,
422                    ))
423                })?;
424                let mut context = EvalContext::new(self, global, caches, scope, this_ptr);
425
426                (custom_def.func)(&mut context, &expressions, &custom.state)
427                    .and_then(|r| self.check_data_size(r, expr.start_position()))
428            }
429
430            Expr::Stmt(x) => {
431                self.eval_stmt_block(global, caches, scope, this_ptr, x.statements(), true)
432            }
433
434            #[cfg(not(feature = "no_index"))]
435            Expr::Index(..) => {
436                self.eval_dot_index_chain(global, caches, scope, this_ptr, expr, None)
437            }
438
439            #[cfg(not(feature = "no_object"))]
440            Expr::Dot(..) => self.eval_dot_index_chain(global, caches, scope, this_ptr, expr, None),
441
442            #[allow(unreachable_patterns)]
443            _ => unreachable!("expression cannot be evaluated: {:?}", expr),
444        }
445    }
446}