rhai/ast/
expr.rs

1//! Module defining script expressions.
2
3use super::{ASTFlags, ASTNode, Ident, Stmt, StmtBlock};
4use crate::engine::KEYWORD_FN_PTR;
5use crate::eval::GlobalRuntimeState;
6use crate::tokenizer::Token;
7use crate::types::dynamic::Union;
8use crate::{
9    calc_fn_hash, Dynamic, FnArgsVec, FnPtr, Identifier, ImmutableString, Position, SmartString,
10    StaticVec, ThinVec, INT,
11};
12#[cfg(feature = "no_std")]
13use std::prelude::v1::*;
14use std::{
15    collections::BTreeMap,
16    fmt,
17    fmt::Write,
18    hash::Hash,
19    iter::once,
20    mem,
21    num::{NonZeroU8, NonZeroUsize},
22};
23
24/// _(internals)_ A binary expression.
25/// Exported under the `internals` feature only.
26#[derive(Debug, Clone, Hash, Default)]
27pub struct BinaryExpr {
28    /// LHS expression.
29    pub lhs: Expr,
30    /// RHS expression.
31    pub rhs: Expr,
32}
33
34/// _(internals)_ A custom syntax expression.
35/// Exported under the `internals` feature only.
36///
37/// Not available under `no_custom_syntax`.
38#[cfg(not(feature = "no_custom_syntax"))]
39#[derive(Debug, Clone, Hash)]
40pub struct CustomExpr {
41    /// List of keywords.
42    pub inputs: FnArgsVec<Expr>,
43    /// List of tokens actually parsed.
44    pub tokens: FnArgsVec<ImmutableString>,
45    /// State value.
46    pub state: Dynamic,
47    /// Is the current [`Scope`][crate::Scope] possibly modified by this custom statement
48    /// (e.g. introducing a new variable)?
49    pub scope_may_be_changed: bool,
50    /// Is this custom syntax self-terminated?
51    pub self_terminated: bool,
52}
53
54#[cfg(not(feature = "no_custom_syntax"))]
55impl CustomExpr {
56    /// Is this custom syntax self-terminated (i.e. no need for a semicolon terminator)?
57    ///
58    /// A self-terminated custom syntax always ends in `$block$`, `}` or `;`
59    #[inline(always)]
60    #[must_use]
61    pub const fn is_self_terminated(&self) -> bool {
62        self.self_terminated
63    }
64}
65
66/// _(internals)_ A set of function call hashes. Exported under the `internals` feature only.
67///
68/// Two separate hashes are pre-calculated because of the following patterns:
69///
70/// ```rhai
71/// func(a, b, c);      // Native: func(a, b, c)        - 3 parameters
72///                     // Script: func(a, b, c)        - 3 parameters
73///
74/// a.func(b, c);       // Native: func(&mut a, b, c)   - 3 parameters
75///                     // Script: func(b, c)           - 2 parameters
76/// ```
77///
78/// For normal function calls, the native hash equals the script hash.
79///
80/// For method-style calls, the script hash contains one fewer parameter.
81///
82/// Function call hashes are used in the following manner:
83///
84/// * First, the script hash (if any) is tried, which contains only the called function's name plus
85///   the number of parameters.
86///
87/// * Next, the actual types of arguments are hashed and _combined_ with the native hash, which is
88///   then used to search for a native function.
89///
90///   In other words, a complete native function call hash always contains the called function's
91///   name plus the types of the arguments.  This is due to possible function overloading for
92///   different parameter types.
93#[derive(Clone, Copy, Eq, PartialEq, Hash)]
94pub struct FnCallHashes {
95    /// Pre-calculated hash for a script-defined function ([`None`] if native functions only).
96    #[cfg(not(feature = "no_function"))]
97    script: Option<u64>,
98    /// Pre-calculated hash for a native Rust function with no parameter types.
99    native: u64,
100}
101
102impl fmt::Debug for FnCallHashes {
103    #[cold]
104    #[inline(never)]
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        #[cfg(not(feature = "no_function"))]
107        return match self.script {
108            Some(script) if script == self.native => fmt::Debug::fmt(&self.native, f),
109            Some(script) => write!(f, "({script}, {})", self.native),
110            None => write!(f, "{} (native only)", self.native),
111        };
112
113        #[cfg(feature = "no_function")]
114        return write!(f, "{}", self.native);
115    }
116}
117
118impl FnCallHashes {
119    /// Create a [`FnCallHashes`] from a single hash.
120    #[inline]
121    #[must_use]
122    pub const fn from_hash(hash: u64) -> Self {
123        Self {
124            #[cfg(not(feature = "no_function"))]
125            script: Some(hash),
126            native: hash,
127        }
128    }
129    /// Create a [`FnCallHashes`] with only the native Rust hash.
130    #[inline]
131    #[must_use]
132    pub const fn from_native_only(hash: u64) -> Self {
133        Self {
134            #[cfg(not(feature = "no_function"))]
135            script: None,
136            native: hash,
137        }
138    }
139    /// Create a [`FnCallHashes`] with both script function and native Rust hashes.
140    ///
141    /// Not available under `no_function`.
142    #[cfg(not(feature = "no_function"))]
143    #[inline]
144    #[must_use]
145    pub const fn from_script_and_native(script: u64, native: u64) -> Self {
146        Self {
147            script: Some(script),
148            native,
149        }
150    }
151    /// Is this [`FnCallHashes`] native-only?
152    #[inline(always)]
153    #[must_use]
154    pub const fn is_native_only(&self) -> bool {
155        #[cfg(not(feature = "no_function"))]
156        return self.script.is_none();
157        #[cfg(feature = "no_function")]
158        return true;
159    }
160    /// Get the native hash.
161    ///
162    /// The hash returned is never zero.
163    #[inline(always)]
164    #[must_use]
165    pub const fn native(&self) -> u64 {
166        self.native
167    }
168    /// Get the script hash.
169    ///
170    /// The hash returned is never zero.
171    ///
172    /// # Panics
173    ///
174    /// Panics if this [`FnCallHashes`] is native-only.
175    #[cfg(not(feature = "no_function"))]
176    #[inline(always)]
177    #[must_use]
178    pub fn script(&self) -> u64 {
179        self.script.expect("native-only hash")
180    }
181}
182
183/// _(internals)_ A function call.
184/// Exported under the `internals` feature only.
185#[derive(Clone, Hash)]
186pub struct FnCallExpr {
187    /// Namespace of the function, if any.
188    #[cfg(not(feature = "no_module"))]
189    pub namespace: super::Namespace,
190    /// Function name.
191    pub name: ImmutableString,
192    /// Pre-calculated hashes.
193    pub hashes: FnCallHashes,
194    /// List of function call argument expressions.
195    pub args: FnArgsVec<Expr>,
196    /// Does this function call capture the parent scope?
197    pub capture_parent_scope: bool,
198    /// Is this function call a native operator?
199    pub op_token: Option<Token>,
200}
201
202impl fmt::Debug for FnCallExpr {
203    #[cold]
204    #[inline(never)]
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        let mut ff = f.debug_struct("FnCallExpr");
207        #[cfg(not(feature = "no_module"))]
208        if !self.namespace.is_empty() {
209            ff.field("namespace", &self.namespace);
210        }
211        ff.field("hash", &self.hashes)
212            .field("name", &self.name)
213            .field("args", &self.args);
214        if self.is_operator_call() {
215            ff.field("op_token", &self.op_token);
216        }
217        if self.capture_parent_scope {
218            ff.field("capture_parent_scope", &self.capture_parent_scope);
219        }
220        ff.finish()
221    }
222}
223
224impl FnCallExpr {
225    /// Does this function call contain a qualified namespace?
226    ///
227    /// Not available under `no_module`
228    #[cfg(not(feature = "no_module"))]
229    #[inline(always)]
230    #[must_use]
231    pub fn is_qualified(&self) -> bool {
232        !self.namespace.is_empty()
233    }
234    /// Is this function call an operator expression?
235    #[inline(always)]
236    #[must_use]
237    pub const fn is_operator_call(&self) -> bool {
238        self.op_token.is_some()
239    }
240    /// Convert this into an [`Expr::FnCall`].
241    #[inline(always)]
242    #[must_use]
243    pub fn into_fn_call_expr(self, pos: Position) -> Expr {
244        Expr::FnCall(self.into(), pos)
245    }
246    /// Are all arguments constant?
247    #[inline]
248    #[must_use]
249    pub fn constant_args(&self) -> bool {
250        self.args.is_empty() || self.args.iter().all(Expr::is_constant)
251    }
252}
253
254/// _(internals)_ An expression sub-tree.
255/// Exported under the `internals` feature only.
256#[derive(Clone, Hash)]
257#[non_exhaustive]
258#[allow(clippy::type_complexity)]
259pub enum Expr {
260    /// Dynamic constant.
261    ///
262    /// Used to hold complex constants such as [`Array`][crate::Array] or [`Map`][crate::Map] for quick cloning.
263    /// Primitive data types should use the appropriate variants to avoid an allocation.
264    ///
265    /// The [`Dynamic`] value is boxed in order to avoid bloating the size of [`Expr`].
266    DynamicConstant(Box<Dynamic>, Position),
267    /// Boolean constant.
268    BoolConstant(bool, Position),
269    /// Integer constant.
270    IntegerConstant(INT, Position),
271    /// Floating-point constant.
272    #[cfg(not(feature = "no_float"))]
273    FloatConstant(crate::types::FloatWrapper<crate::FLOAT>, Position),
274    /// Character constant.
275    CharConstant(char, Position),
276    /// [String][ImmutableString] constant.
277    StringConstant(ImmutableString, Position),
278    /// An interpolated [string][ImmutableString].
279    InterpolatedString(ThinVec<Self>, Position),
280    /// [ expr, ... ]
281    Array(ThinVec<Self>, Position),
282    /// #{ name:expr, ... }
283    Map(
284        Box<(StaticVec<(Ident, Self)>, BTreeMap<Identifier, Dynamic>)>,
285        Position,
286    ),
287    /// ()
288    Unit(Position),
289    /// Variable access - (optional long index, variable name, namespace, namespace hash), optional short index, position
290    ///
291    /// The short index is [`u8`] which is used when the index is <= 255, which should be
292    /// the vast majority of cases (unless there are more than 255 variables defined!).
293    /// This is to avoid reading a pointer redirection during each variable access.
294    Variable(
295        #[cfg(not(feature = "no_module"))]
296        Box<(Option<NonZeroUsize>, ImmutableString, super::Namespace, u64)>,
297        #[cfg(feature = "no_module")] Box<(Option<NonZeroUsize>, ImmutableString)>,
298        Option<NonZeroU8>,
299        Position,
300    ),
301    /// `this`.
302    ThisPtr(Position),
303    /// Property access - ((getter, hash), (setter, hash), prop)
304    Property(
305        Box<(
306            (ImmutableString, u64),
307            (ImmutableString, u64),
308            ImmutableString,
309        )>,
310        Position,
311    ),
312    /// xxx `.` method `(` expr `,` ... `)`
313    MethodCall(Box<FnCallExpr>, Position),
314    /// { [statement][Stmt] ... }
315    Stmt(Box<StmtBlock>),
316    /// func `(` expr `,` ... `)`
317    FnCall(Box<FnCallExpr>, Position),
318    /// lhs `.` rhs | lhs `?.` rhs
319    ///
320    /// ### Flags
321    ///
322    /// * [`NEGATED`][ASTFlags::NEGATED] = `?.` (`.` if unset)
323    /// * [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset)
324    Dot(Box<BinaryExpr>, ASTFlags, Position),
325    /// lhs `[` rhs `]`
326    ///
327    /// ### Flags
328    ///
329    /// * [`NEGATED`][ASTFlags::NEGATED] = `?[` ... `]` (`[` ... `]` if unset)
330    /// * [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset)
331    Index(Box<BinaryExpr>, ASTFlags, Position),
332    /// lhs `&&` rhs
333    And(Box<StaticVec<Self>>, Position),
334    /// lhs `||` rhs
335    Or(Box<StaticVec<Self>>, Position),
336    /// lhs `??` rhs
337    Coalesce(Box<StaticVec<Self>>, Position),
338    /// Custom syntax
339    #[cfg(not(feature = "no_custom_syntax"))]
340    Custom(Box<CustomExpr>, Position),
341}
342
343impl Default for Expr {
344    #[inline(always)]
345    fn default() -> Self {
346        Self::Unit(Position::NONE)
347    }
348}
349
350impl fmt::Debug for Expr {
351    #[cold]
352    #[inline(never)]
353    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
354        let mut display_pos = self.start_position();
355
356        match self {
357            Self::DynamicConstant(value, ..) => write!(f, "{value:?}"),
358            Self::BoolConstant(value, ..) => write!(f, "{value:?}"),
359            Self::IntegerConstant(value, ..) => write!(f, "{value:?}"),
360            #[cfg(not(feature = "no_float"))]
361            Self::FloatConstant(value, ..) => write!(f, "{value:?}"),
362            Self::CharConstant(value, ..) => write!(f, "{value:?}"),
363            Self::StringConstant(value, ..) => write!(f, "{value:?}"),
364            Self::Unit(..) => f.write_str("()"),
365
366            Self::InterpolatedString(x, ..) => {
367                f.write_str("InterpolatedString")?;
368                return f.debug_list().entries(x.iter()).finish();
369            }
370            Self::Array(x, ..) => {
371                f.write_str("Array")?;
372                f.debug_list().entries(x.iter()).finish()
373            }
374            Self::Map(x, ..) => {
375                f.write_str("Map")?;
376                f.debug_map()
377                    .entries(x.0.iter().map(|(k, v)| (k, v)))
378                    .finish()
379            }
380            Self::ThisPtr(..) => f.debug_struct("ThisPtr").finish(),
381            Self::Variable(x, i, ..) => {
382                f.write_str("Variable(")?;
383
384                #[cfg(not(feature = "no_module"))]
385                if !x.2.is_empty() {
386                    write!(f, "{}{}", x.1, crate::engine::NAMESPACE_SEPARATOR)?;
387                    let pos = x.2.position();
388                    if !pos.is_none() {
389                        display_pos = pos;
390                    }
391                }
392                f.write_str(&x.1)?;
393                #[cfg(not(feature = "no_module"))]
394                if let Some(n) = x.2.index {
395                    write!(f, " #{n}")?;
396                }
397                if let Some(n) = i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) {
398                    write!(f, " #{n}")?;
399                }
400                f.write_str(")")
401            }
402            Self::Property(x, ..) => write!(f, "Property({})", x.2),
403            Self::MethodCall(x, ..) => f.debug_tuple("MethodCall").field(x).finish(),
404            Self::Stmt(x) => {
405                let pos = x.span();
406                if !pos.is_none() {
407                    display_pos = pos.start();
408                }
409                f.write_str("ExprStmtBlock")?;
410                f.debug_list().entries(x.iter()).finish()
411            }
412            Self::FnCall(x, ..) => fmt::Debug::fmt(x, f),
413            Self::Index(x, options, pos) => {
414                if !pos.is_none() {
415                    display_pos = *pos;
416                }
417
418                let mut f = f.debug_struct("Index");
419
420                f.field("lhs", &x.lhs).field("rhs", &x.rhs);
421                if !options.is_empty() {
422                    f.field("options", options);
423                }
424                f.finish()
425            }
426            Self::Dot(x, options, pos) => {
427                if !pos.is_none() {
428                    display_pos = *pos;
429                }
430
431                let mut f = f.debug_struct("Dot");
432
433                f.field("lhs", &x.lhs).field("rhs", &x.rhs);
434                if !options.is_empty() {
435                    f.field("options", options);
436                }
437                f.finish()
438            }
439            Self::And(x, pos) | Self::Or(x, pos) | Self::Coalesce(x, pos) => {
440                let op_name = match self {
441                    Self::And(..) => "And",
442                    Self::Or(..) => "Or",
443                    Self::Coalesce(..) => "Coalesce",
444                    expr => unreachable!("`And`, `Or` or `Coalesce` expected but gets {:?}", expr),
445                };
446
447                if !pos.is_none() {
448                    display_pos = *pos;
449                }
450
451                let mut f = f.debug_tuple(op_name);
452                x.iter().for_each(|expr| {
453                    f.field(expr);
454                });
455                f.finish()
456            }
457            #[cfg(not(feature = "no_custom_syntax"))]
458            Self::Custom(x, ..) => f.debug_tuple("Custom").field(x).finish(),
459        }?;
460
461        write!(f, " @ {display_pos:?}")
462    }
463}
464
465impl Expr {
466    /// Get the [`Dynamic`] value of a literal constant expression.
467    ///
468    /// Returns [`None`] if the expression is not a literal constant.
469    #[inline]
470    #[must_use]
471    pub fn get_literal_value(&self, global: Option<&GlobalRuntimeState>) -> Option<Dynamic> {
472        Some(match self {
473            Self::DynamicConstant(x, ..) => {
474                let mut _value = x.as_ref().clone();
475
476                #[cfg(not(feature = "no_function"))]
477                if let Some(global) = global {
478                    if let Some(mut fn_ptr) = _value.write_lock::<FnPtr>() {
479                        // Create a new environment with the current module
480                        fn_ptr.env = Some(crate::Shared::new(global.into()));
481                    }
482                }
483
484                _value
485            }
486
487            Self::IntegerConstant(x, ..) => (*x).into(),
488            #[cfg(not(feature = "no_float"))]
489            Self::FloatConstant(x, ..) => (*x).into(),
490            Self::CharConstant(x, ..) => (*x).into(),
491            Self::StringConstant(x, ..) => x.clone().into(),
492            Self::BoolConstant(x, ..) => (*x).into(),
493            Self::Unit(..) => Dynamic::UNIT,
494
495            #[cfg(not(feature = "no_index"))]
496            Self::Array(x, ..) if self.is_constant() => {
497                let mut arr = crate::Array::with_capacity(x.len());
498                arr.extend(x.iter().map(|v| v.get_literal_value(global).unwrap()));
499                Dynamic::from_array(arr)
500            }
501
502            #[cfg(not(feature = "no_object"))]
503            Self::Map(x, ..) if self.is_constant() => {
504                let mut map = x.1.clone();
505
506                for (k, v) in &x.0 {
507                    *map.get_mut(k.as_str()).unwrap() = v.get_literal_value(global).unwrap();
508                }
509
510                Dynamic::from_map(map)
511            }
512
513            // Interpolated string
514            Self::InterpolatedString(x, ..) if self.is_constant() => {
515                let mut s = SmartString::new_const();
516                for segment in x {
517                    let v = segment.get_literal_value(global).unwrap();
518                    write!(&mut s, "{v}").unwrap();
519                }
520                s.into()
521            }
522
523            // Qualified function call
524            #[cfg(not(feature = "no_module"))]
525            Self::FnCall(x, ..) if x.is_qualified() => return None,
526
527            // Function call
528            Self::FnCall(x, ..) if x.args.len() == 1 && x.name == KEYWORD_FN_PTR => {
529                match x.args[0] {
530                    Self::StringConstant(ref s, ..) => FnPtr::new(s.clone()).ok()?.into(),
531                    _ => return None,
532                }
533            }
534
535            // Binary operator call
536            Self::FnCall(x, ..) if x.args.len() == 2 => {
537                pub const OP_EXCLUSIVE_RANGE: &str = Token::ExclusiveRange.literal_syntax();
538                pub const OP_INCLUSIVE_RANGE: &str = Token::InclusiveRange.literal_syntax();
539
540                match x.name.as_str() {
541                    // x..y
542                    OP_EXCLUSIVE_RANGE => match (&x.args[0], &x.args[1]) {
543                        (
544                            Self::IntegerConstant(ref start, ..),
545                            Self::IntegerConstant(ref end, ..),
546                        ) => (*start..*end).into(),
547                        (Self::IntegerConstant(ref start, ..), Self::Unit(..)) => {
548                            (*start..INT::MAX).into()
549                        }
550                        (Self::Unit(..), Self::IntegerConstant(ref start, ..)) => {
551                            (0..*start).into()
552                        }
553                        _ => return None,
554                    },
555                    // x..=y
556                    OP_INCLUSIVE_RANGE => match (&x.args[0], &x.args[1]) {
557                        (
558                            Self::IntegerConstant(ref start, ..),
559                            Self::IntegerConstant(ref end, ..),
560                        ) => (*start..=*end).into(),
561                        (Self::IntegerConstant(ref start, ..), Self::Unit(..)) => {
562                            (*start..=INT::MAX).into()
563                        }
564                        (Self::Unit(..), Self::IntegerConstant(ref start, ..)) => {
565                            (0..=*start).into()
566                        }
567                        _ => return None,
568                    },
569                    _ => return None,
570                }
571            }
572
573            _ => return None,
574        })
575    }
576    /// Create an [`Expr`] from a [`Dynamic`] value.
577    #[inline]
578    #[must_use]
579    pub fn from_dynamic(value: Dynamic, pos: Position) -> Self {
580        match value.0 {
581            Union::Unit(..) => Self::Unit(pos),
582            Union::Bool(b, ..) => Self::BoolConstant(b, pos),
583            Union::Str(s, ..) => Self::StringConstant(s, pos),
584            Union::Char(c, ..) => Self::CharConstant(c, pos),
585            Union::Int(i, ..) => Self::IntegerConstant(i, pos),
586
587            #[cfg(feature = "decimal")]
588            Union::Decimal(value, ..) => Self::DynamicConstant(Box::new((*value).into()), pos),
589
590            #[cfg(not(feature = "no_float"))]
591            Union::Float(f, ..) => Self::FloatConstant(f, pos),
592
593            #[cfg(not(feature = "no_index"))]
594            Union::Array(a, ..) => Self::DynamicConstant(Box::new((*a).into()), pos),
595
596            #[cfg(not(feature = "no_object"))]
597            Union::Map(m, ..) => Self::DynamicConstant(Box::new((*m).into()), pos),
598
599            Union::FnPtr(f, ..) if !f.is_curried() => Self::FnCall(
600                FnCallExpr {
601                    #[cfg(not(feature = "no_module"))]
602                    namespace: super::Namespace::NONE,
603                    name: KEYWORD_FN_PTR.into(),
604                    hashes: FnCallHashes::from_hash(calc_fn_hash(None, f.fn_name(), 1)),
605                    args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(),
606                    capture_parent_scope: false,
607                    op_token: None,
608                }
609                .into(),
610                pos,
611            ),
612
613            _ => Self::DynamicConstant(value.into(), pos),
614        }
615    }
616    /// Return the variable name if the expression a simple variable access.
617    ///
618    /// `non_qualified` is ignored under `no_module`.
619    #[inline]
620    #[must_use]
621    pub(crate) fn get_variable_name(&self, _non_qualified: bool) -> Option<&str> {
622        match self {
623            #[cfg(not(feature = "no_module"))]
624            Self::Variable(x, ..) if _non_qualified && !x.2.is_empty() => None,
625            Self::Variable(x, ..) => Some(&x.1),
626            _ => None,
627        }
628    }
629    /// Get the [options][ASTFlags] of the expression.
630    #[inline]
631    #[must_use]
632    pub const fn options(&self) -> ASTFlags {
633        match self {
634            Self::Index(_, options, _) | Self::Dot(_, options, _) => *options,
635
636            #[cfg(not(feature = "no_float"))]
637            Self::FloatConstant(..) => ASTFlags::empty(),
638
639            Self::DynamicConstant(..)
640            | Self::BoolConstant(..)
641            | Self::IntegerConstant(..)
642            | Self::CharConstant(..)
643            | Self::Unit(..)
644            | Self::StringConstant(..)
645            | Self::Array(..)
646            | Self::Map(..)
647            | Self::Variable(..)
648            | Self::ThisPtr(..)
649            | Self::And(..)
650            | Self::Or(..)
651            | Self::Coalesce(..)
652            | Self::FnCall(..)
653            | Self::MethodCall(..)
654            | Self::InterpolatedString(..)
655            | Self::Property(..)
656            | Self::Stmt(..) => ASTFlags::empty(),
657
658            #[cfg(not(feature = "no_custom_syntax"))]
659            Self::Custom(..) => ASTFlags::empty(),
660        }
661    }
662    /// Get the [position][Position] of the expression.
663    #[inline]
664    #[must_use]
665    pub const fn position(&self) -> Position {
666        match self {
667            #[cfg(not(feature = "no_float"))]
668            Self::FloatConstant(.., pos) => *pos,
669
670            Self::DynamicConstant(.., pos)
671            | Self::BoolConstant(.., pos)
672            | Self::IntegerConstant(.., pos)
673            | Self::CharConstant(.., pos)
674            | Self::Unit(pos)
675            | Self::StringConstant(.., pos)
676            | Self::Array(.., pos)
677            | Self::Map(.., pos)
678            | Self::Variable(.., pos)
679            | Self::ThisPtr(pos)
680            | Self::And(.., pos)
681            | Self::Or(.., pos)
682            | Self::Coalesce(.., pos)
683            | Self::FnCall(.., pos)
684            | Self::MethodCall(.., pos)
685            | Self::Index(.., pos)
686            | Self::Dot(.., pos)
687            | Self::InterpolatedString(.., pos)
688            | Self::Property(.., pos) => *pos,
689
690            #[cfg(not(feature = "no_custom_syntax"))]
691            Self::Custom(.., pos) => *pos,
692
693            Self::Stmt(x) => x.position(),
694        }
695    }
696    /// Get the starting [position][Position] of the expression.
697    /// For a binary expression, this will be the left-most LHS instead of the operator.
698    #[inline]
699    #[must_use]
700    pub fn start_position(&self) -> Position {
701        match self {
702            #[cfg(not(feature = "no_module"))]
703            Self::Variable(x, ..) => {
704                if x.2.is_empty() {
705                    self.position()
706                } else {
707                    x.2.position()
708                }
709            }
710
711            Self::And(x, ..) | Self::Or(x, ..) | Self::Coalesce(x, ..) => x[0].start_position(),
712
713            Self::Index(x, ..) | Self::Dot(x, ..) => x.lhs.start_position(),
714
715            Self::FnCall(.., pos) => *pos,
716
717            _ => self.position(),
718        }
719    }
720    /// Override the [position][Position] of the expression.
721    #[inline]
722    pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
723        match self {
724            #[cfg(not(feature = "no_float"))]
725            Self::FloatConstant(.., pos) => *pos = new_pos,
726
727            Self::DynamicConstant(.., pos)
728            | Self::BoolConstant(.., pos)
729            | Self::IntegerConstant(.., pos)
730            | Self::CharConstant(.., pos)
731            | Self::Unit(pos)
732            | Self::StringConstant(.., pos)
733            | Self::Array(.., pos)
734            | Self::Map(.., pos)
735            | Self::And(.., pos)
736            | Self::Or(.., pos)
737            | Self::Coalesce(.., pos)
738            | Self::Dot(.., pos)
739            | Self::Index(.., pos)
740            | Self::Variable(.., pos)
741            | Self::ThisPtr(pos)
742            | Self::FnCall(.., pos)
743            | Self::MethodCall(.., pos)
744            | Self::InterpolatedString(.., pos)
745            | Self::Property(.., pos) => *pos = new_pos,
746
747            #[cfg(not(feature = "no_custom_syntax"))]
748            Self::Custom(.., pos) => *pos = new_pos,
749
750            Self::Stmt(x) => x.set_position(new_pos, Position::NONE),
751        }
752
753        self
754    }
755    /// Is the expression pure?
756    ///
757    /// A pure expression has no side effects.
758    #[inline]
759    #[must_use]
760    pub fn is_pure(&self) -> bool {
761        match self {
762            Self::InterpolatedString(x, ..) | Self::Array(x, ..) => x.iter().all(Self::is_pure),
763
764            Self::Map(x, ..) => x.0.iter().map(|(.., v)| v).all(Self::is_pure),
765
766            Self::And(x, ..) | Self::Or(x, ..) | Self::Coalesce(x, ..) => {
767                x.iter().all(Self::is_pure)
768            }
769
770            Self::Stmt(x) => x.iter().all(Stmt::is_pure),
771
772            Self::Variable(..) => true,
773
774            _ => self.is_constant(),
775        }
776    }
777    /// Is the expression the unit `()` literal?
778    #[inline(always)]
779    #[must_use]
780    pub const fn is_unit(&self) -> bool {
781        matches!(self, Self::Unit(..))
782    }
783    /// Is the expression a constant?
784    #[inline]
785    #[must_use]
786    pub fn is_constant(&self) -> bool {
787        match self {
788            #[cfg(not(feature = "no_float"))]
789            Self::FloatConstant(..) => true,
790
791            Self::DynamicConstant(..)
792            | Self::BoolConstant(..)
793            | Self::IntegerConstant(..)
794            | Self::CharConstant(..)
795            | Self::StringConstant(..)
796            | Self::Unit(..) => true,
797
798            Self::InterpolatedString(x, ..) | Self::Array(x, ..) => x.iter().all(Self::is_constant),
799
800            Self::Map(x, ..) => x.0.iter().map(|(.., expr)| expr).all(Self::is_constant),
801
802            _ => false,
803        }
804    }
805    /// Is a particular [token][Token] allowed as a postfix operator to this expression?
806    #[inline]
807    #[must_use]
808    pub const fn is_valid_postfix(&self, token: &Token) -> bool {
809        match token {
810            #[cfg(not(feature = "no_object"))]
811            Token::Period | Token::Elvis => return true,
812            #[cfg(not(feature = "no_index"))]
813            Token::LeftBracket | Token::QuestionBracket => return true,
814            _ => (),
815        }
816
817        match self {
818            #[cfg(not(feature = "no_float"))]
819            Self::FloatConstant(..) => false,
820
821            Self::DynamicConstant(..)
822            | Self::BoolConstant(..)
823            | Self::CharConstant(..)
824            | Self::And(..)
825            | Self::Or(..)
826            | Self::Coalesce(..)
827            | Self::Unit(..) => false,
828
829            Self::IntegerConstant(..)
830            | Self::StringConstant(..)
831            | Self::InterpolatedString(..)
832            | Self::FnCall(..)
833            | Self::ThisPtr(..)
834            | Self::MethodCall(..)
835            | Self::Stmt(..)
836            | Self::Dot(..)
837            | Self::Index(..)
838            | Self::Array(..)
839            | Self::Map(..) => false,
840
841            #[cfg(not(feature = "no_custom_syntax"))]
842            Self::Custom(..) => false,
843
844            Self::Variable(..) => matches!(
845                token,
846                Token::LeftParen | Token::Unit | Token::Bang | Token::DoubleColon
847            ),
848
849            Self::Property(..) => matches!(token, Token::LeftParen),
850        }
851    }
852    /// Return this [`Expr`], replacing it with [`Expr::Unit`].
853    #[inline(always)]
854    #[must_use]
855    pub fn take(&mut self) -> Self {
856        mem::take(self)
857    }
858    /// Recursively walk this expression.
859    /// Return `false` from the callback to terminate the walk.
860    pub fn walk<'a>(
861        &'a self,
862        path: &mut Vec<ASTNode<'a>>,
863        on_node: &mut (impl FnMut(&[ASTNode]) -> bool + ?Sized),
864    ) -> bool {
865        // Push the current node onto the path
866        path.push(self.into());
867
868        if !on_node(path) {
869            return false;
870        }
871
872        match self {
873            Self::Stmt(x) => {
874                for s in &**x {
875                    if !s.walk(path, on_node) {
876                        return false;
877                    }
878                }
879            }
880            Self::InterpolatedString(x, ..) | Self::Array(x, ..) => {
881                for e in &**x {
882                    if !e.walk(path, on_node) {
883                        return false;
884                    }
885                }
886            }
887            Self::Map(x, ..) => {
888                for (.., e) in &x.0 {
889                    if !e.walk(path, on_node) {
890                        return false;
891                    }
892                }
893            }
894            Self::Index(x, ..) | Self::Dot(x, ..) => {
895                if !x.lhs.walk(path, on_node) {
896                    return false;
897                }
898                if !x.rhs.walk(path, on_node) {
899                    return false;
900                }
901            }
902            Self::And(x, ..) | Self::Or(x, ..) | Self::Coalesce(x, ..) => {
903                for expr in &***x {
904                    if !expr.walk(path, on_node) {
905                        return false;
906                    }
907                }
908            }
909            Self::FnCall(x, ..) => {
910                for e in &*x.args {
911                    if !e.walk(path, on_node) {
912                        return false;
913                    }
914                }
915            }
916            #[cfg(not(feature = "no_custom_syntax"))]
917            Self::Custom(x, ..) => {
918                for e in &*x.inputs {
919                    if !e.walk(path, on_node) {
920                        return false;
921                    }
922                }
923            }
924            _ => (),
925        }
926
927        path.pop().unwrap();
928
929        true
930    }
931}