spo_rhai/ast/
expr.rs

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