spo_rhai/ast/
stmt.rs

1//! Module defining script statements.
2
3use super::{ASTFlags, ASTNode, BinaryExpr, Expr, FnCallExpr, Ident};
4use crate::engine::{KEYWORD_EVAL, OP_EQUALS};
5use crate::func::StraightHashMap;
6use crate::tokenizer::Token;
7use crate::types::dynamic::Union;
8use crate::types::Span;
9use crate::{calc_fn_hash, Dynamic, FnArgsVec, Position, StaticVec, INT};
10#[cfg(feature = "no_std")]
11use std::prelude::v1::*;
12use std::{
13    borrow::Borrow,
14    fmt,
15    hash::{Hash, Hasher},
16    mem,
17    num::NonZeroUsize,
18    ops::{Range, RangeInclusive},
19};
20
21/// _(internals)_ An op-assignment operator.
22/// Exported under the `internals` feature only.
23///
24/// This type may hold a straight assignment (i.e. not an op-assignment).
25#[derive(Clone, PartialEq, Hash)]
26pub struct OpAssignment {
27    /// Hash of the op-assignment call.
28    hash_op_assign: u64,
29    /// Hash of the underlying operator call (for fallback).
30    hash_op: u64,
31    /// Op-assignment operator.
32    op_assign: Token,
33    /// Syntax of op-assignment operator.
34    op_assign_syntax: &'static str,
35    /// Underlying operator.
36    op: Token,
37    /// Syntax of underlying operator.
38    op_syntax: &'static str,
39    /// [Position] of the op-assignment operator.
40    pos: Position,
41}
42
43impl OpAssignment {
44    /// Create a new [`OpAssignment`] that is only a straight assignment.
45    #[must_use]
46    #[inline(always)]
47    pub const fn new_assignment(pos: Position) -> Self {
48        Self {
49            hash_op_assign: 0,
50            hash_op: 0,
51            op_assign: Token::Equals,
52            op_assign_syntax: OP_EQUALS,
53            op: Token::Equals,
54            op_syntax: OP_EQUALS,
55            pos,
56        }
57    }
58    /// Is this an op-assignment?
59    #[must_use]
60    #[inline(always)]
61    pub const fn is_op_assignment(&self) -> bool {
62        !matches!(self.op, Token::Equals)
63    }
64    /// Get information if this [`OpAssignment`] is an op-assignment.
65    ///
66    /// Returns `( hash_op_assign, hash_op, op_assign, op_assign_syntax, op, op_syntax )`:
67    ///
68    /// * `hash_op_assign`: Hash of the op-assignment call.
69    /// * `hash_op`: Hash of the underlying operator call (for fallback).
70    /// * `op_assign`: Op-assignment operator.
71    /// * `op_assign_syntax`: Syntax of op-assignment operator.
72    /// * `op`: Underlying operator.
73    /// * `op_syntax`: Syntax of underlying operator.
74    #[must_use]
75    #[inline]
76    pub const fn get_op_assignment_info(
77        &self,
78    ) -> Option<(u64, u64, &Token, &'static str, &Token, &'static str)> {
79        if self.is_op_assignment() {
80            Some((
81                self.hash_op_assign,
82                self.hash_op,
83                &self.op_assign,
84                self.op_assign_syntax,
85                &self.op,
86                self.op_syntax,
87            ))
88        } else {
89            None
90        }
91    }
92    /// Get the [position][Position] of this [`OpAssignment`].
93    #[must_use]
94    #[inline(always)]
95    pub const fn position(&self) -> Position {
96        self.pos
97    }
98    /// Create a new [`OpAssignment`].
99    ///
100    /// # Panics
101    ///
102    /// Panics if the name is not an op-assignment operator.
103    #[must_use]
104    #[inline(always)]
105    pub fn new_op_assignment(name: &str, pos: Position) -> Self {
106        let op = Token::lookup_symbol_from_syntax(name)
107            .unwrap_or_else(|| panic!("{} is not an op-assignment operator", name));
108        Self::new_op_assignment_from_token(op, pos)
109    }
110    /// Create a new [`OpAssignment`] from a [`Token`].
111    ///
112    /// # Panics
113    ///
114    /// Panics if the token is not an op-assignment operator.
115    #[must_use]
116    pub fn new_op_assignment_from_token(op_assign: Token, pos: Position) -> Self {
117        let op = op_assign
118            .get_base_op_from_assignment()
119            .unwrap_or_else(|| panic!("{:?} is not an op-assignment operator", op_assign));
120
121        let op_assign_syntax = op_assign.literal_syntax();
122        let op_syntax = op.literal_syntax();
123
124        Self {
125            hash_op_assign: calc_fn_hash(None, op_assign_syntax, 2),
126            hash_op: calc_fn_hash(None, op_syntax, 2),
127            op_assign,
128            op_assign_syntax,
129            op,
130            op_syntax,
131            pos,
132        }
133    }
134    /// Create a new [`OpAssignment`] from a base operator.
135    ///
136    /// # Panics
137    ///
138    /// Panics if the name is not an operator that can be converted into an op-operator.
139    #[must_use]
140    #[inline(always)]
141    pub fn new_op_assignment_from_base(name: &str, pos: Position) -> Self {
142        let op = Token::lookup_symbol_from_syntax(name)
143            .unwrap_or_else(|| panic!("{} cannot be converted into an op-operator", name));
144        Self::new_op_assignment_from_base_token(&op, pos)
145    }
146    /// Convert a [`Token`] into a new [`OpAssignment`].
147    ///
148    /// # Panics
149    ///
150    /// Panics if the token is cannot be converted into an op-assignment operator.
151    #[inline(always)]
152    #[must_use]
153    pub fn new_op_assignment_from_base_token(op: &Token, pos: Position) -> Self {
154        Self::new_op_assignment_from_token(
155            op.convert_to_op_assignment()
156                .unwrap_or_else(|| panic!("{:?} cannot be converted into an op-operator", op)),
157            pos,
158        )
159    }
160}
161
162impl fmt::Debug for OpAssignment {
163    #[cold]
164    #[inline(never)]
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        if self.is_op_assignment() {
167            f.debug_struct("OpAssignment")
168                .field("hash_op_assign", &self.hash_op_assign)
169                .field("hash_op", &self.hash_op)
170                .field("op_assign", &self.op_assign)
171                .field("op_assign_syntax", &self.op_assign_syntax)
172                .field("op", &self.op)
173                .field("op_syntax", &self.op_syntax)
174                .field("pos", &self.pos)
175                .finish()
176        } else {
177            write!(f, "{} @ {:?}", Token::Equals, self.pos)
178        }
179    }
180}
181
182/// _(internals)_ A type containing a range case for a `switch` statement.
183/// Exported under the `internals` feature only.
184#[derive(Clone, Hash)]
185pub enum RangeCase {
186    /// Exclusive range.
187    ExclusiveInt(Range<INT>, usize),
188    /// Inclusive range.
189    InclusiveInt(RangeInclusive<INT>, usize),
190}
191
192impl fmt::Debug for RangeCase {
193    #[cold]
194    #[inline(never)]
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        match self {
197            Self::ExclusiveInt(r, n) => write!(f, "{}..{} => {n}", r.start, r.end),
198            Self::InclusiveInt(r, n) => write!(f, "{}..={} => {n}", *r.start(), *r.end()),
199        }
200    }
201}
202
203impl From<Range<INT>> for RangeCase {
204    #[inline(always)]
205    fn from(value: Range<INT>) -> Self {
206        Self::ExclusiveInt(value, usize::MAX)
207    }
208}
209
210impl From<RangeInclusive<INT>> for RangeCase {
211    #[inline(always)]
212    fn from(value: RangeInclusive<INT>) -> Self {
213        Self::InclusiveInt(value, usize::MAX)
214    }
215}
216
217impl IntoIterator for RangeCase {
218    type Item = INT;
219    type IntoIter = Box<dyn Iterator<Item = Self::Item>>;
220
221    #[inline]
222    #[must_use]
223    fn into_iter(self) -> Self::IntoIter {
224        match self {
225            Self::ExclusiveInt(r, ..) => Box::new(r),
226            Self::InclusiveInt(r, ..) => Box::new(r),
227        }
228    }
229}
230
231impl RangeCase {
232    /// Returns `true` if the range contains no items.
233    #[inline]
234    #[must_use]
235    pub fn is_empty(&self) -> bool {
236        match self {
237            Self::ExclusiveInt(r, ..) => r.is_empty(),
238            Self::InclusiveInt(r, ..) => r.is_empty(),
239        }
240    }
241    /// Size of the range.
242    #[inline]
243    #[must_use]
244    pub fn len(&self) -> INT {
245        match self {
246            Self::ExclusiveInt(r, ..) if r.is_empty() => 0,
247            Self::ExclusiveInt(r, ..) => r.end - r.start,
248            Self::InclusiveInt(r, ..) if r.is_empty() => 0,
249            Self::InclusiveInt(r, ..) => *r.end() - *r.start() + 1,
250        }
251    }
252    /// Is the specified value within this range?
253    #[inline]
254    #[must_use]
255    pub fn contains(&self, value: &Dynamic) -> bool {
256        match value {
257            Dynamic(Union::Int(v, ..)) => self.contains_int(*v),
258            #[cfg(not(feature = "no_float"))]
259            Dynamic(Union::Float(v, ..)) => self.contains_float(**v),
260            #[cfg(feature = "decimal")]
261            Dynamic(Union::Decimal(v, ..)) => self.contains_decimal(**v),
262            _ => false,
263        }
264    }
265    /// Is the specified number within this range?
266    #[inline]
267    #[must_use]
268    pub fn contains_int(&self, n: INT) -> bool {
269        match self {
270            Self::ExclusiveInt(r, ..) => r.contains(&n),
271            Self::InclusiveInt(r, ..) => r.contains(&n),
272        }
273    }
274    /// Is the specified floating-point number within this range?
275    #[cfg(not(feature = "no_float"))]
276    #[inline]
277    #[must_use]
278    pub fn contains_float(&self, n: crate::FLOAT) -> bool {
279        use crate::FLOAT;
280
281        match self {
282            Self::ExclusiveInt(r, ..) => ((r.start as FLOAT)..(r.end as FLOAT)).contains(&n),
283            Self::InclusiveInt(r, ..) => ((*r.start() as FLOAT)..=(*r.end() as FLOAT)).contains(&n),
284        }
285    }
286    /// Is the specified decimal number within this range?
287    #[cfg(feature = "decimal")]
288    #[inline]
289    #[must_use]
290    pub fn contains_decimal(&self, n: rust_decimal::Decimal) -> bool {
291        use rust_decimal::Decimal;
292
293        match self {
294            Self::ExclusiveInt(r, ..) => {
295                (Into::<Decimal>::into(r.start)..Into::<Decimal>::into(r.end)).contains(&n)
296            }
297            Self::InclusiveInt(r, ..) => {
298                (Into::<Decimal>::into(*r.start())..=Into::<Decimal>::into(*r.end())).contains(&n)
299            }
300        }
301    }
302    /// Is the specified range inclusive?
303    #[inline(always)]
304    #[must_use]
305    pub const fn is_inclusive(&self) -> bool {
306        match self {
307            Self::ExclusiveInt(..) => false,
308            Self::InclusiveInt(..) => true,
309        }
310    }
311    /// Get the index to the list of expressions.
312    #[inline(always)]
313    #[must_use]
314    pub const fn index(&self) -> usize {
315        match self {
316            Self::ExclusiveInt(.., n) | Self::InclusiveInt(.., n) => *n,
317        }
318    }
319    /// Set the index to the list of expressions.
320    #[inline(always)]
321    pub fn set_index(&mut self, index: usize) {
322        match self {
323            Self::ExclusiveInt(.., n) | Self::InclusiveInt(.., n) => *n = index,
324        }
325    }
326}
327
328pub type CaseBlocksList = smallvec::SmallVec<[usize; 2]>;
329
330/// _(internals)_ A type containing all cases for a `switch` statement.
331/// Exported under the `internals` feature only.
332#[derive(Debug, Clone)]
333pub struct SwitchCasesCollection {
334    /// List of conditional expressions: LHS = condition, RHS = expression.
335    pub expressions: FnArgsVec<BinaryExpr>,
336    /// Dictionary mapping value hashes to [`CaseBlocksList`]'s.
337    pub cases: StraightHashMap<CaseBlocksList>,
338    /// List of range cases.
339    pub ranges: StaticVec<RangeCase>,
340    /// Statements block for the default case (there can be no condition for the default case).
341    pub def_case: Option<usize>,
342}
343
344impl Hash for SwitchCasesCollection {
345    #[inline(always)]
346    fn hash<H: Hasher>(&self, state: &mut H) {
347        self.expressions.hash(state);
348
349        self.cases.len().hash(state);
350        self.cases.iter().for_each(|kv| kv.hash(state));
351
352        self.ranges.hash(state);
353        self.def_case.hash(state);
354    }
355}
356
357/// Number of items to keep inline for [`StmtBlockContainer`].
358#[cfg(not(feature = "no_std"))]
359const STMT_BLOCK_INLINE_SIZE: usize = 8;
360
361/// _(internals)_ The underlying container type for [`StmtBlock`].
362/// Exported under the `internals` feature only.
363///
364/// A [`SmallVec`](https://crates.io/crates/smallvec) containing up to 8 items inline is used to
365/// hold a statements block, with the assumption that most program blocks would container fewer than
366/// 8 statements, and those that do have a lot more statements.
367#[cfg(not(feature = "no_std"))]
368pub type StmtBlockContainer = smallvec::SmallVec<[Stmt; STMT_BLOCK_INLINE_SIZE]>;
369
370/// _(internals)_ The underlying container type for [`StmtBlock`].
371/// Exported under the `internals` feature only.
372#[cfg(feature = "no_std")]
373pub type StmtBlockContainer = crate::StaticVec<Stmt>;
374
375/// _(internals)_ A scoped block of statements.
376/// Exported under the `internals` feature only.
377#[derive(Clone, Hash, Default)]
378pub struct StmtBlock {
379    /// List of [statements][Stmt].
380    block: StmtBlockContainer,
381    /// [Position] of the statements block.
382    span: Span,
383}
384
385impl StmtBlock {
386    /// A [`StmtBlock`] that does not exist.
387    pub const NONE: Self = Self::empty(Position::NONE);
388
389    /// Create a new [`StmtBlock`].
390    #[inline(always)]
391    #[must_use]
392    pub fn new(
393        statements: impl IntoIterator<Item = Stmt>,
394        start_pos: Position,
395        end_pos: Position,
396    ) -> Self {
397        Self::new_with_span(statements, Span::new(start_pos, end_pos))
398    }
399    /// Create a new [`StmtBlock`].
400    #[must_use]
401    pub fn new_with_span(statements: impl IntoIterator<Item = Stmt>, span: Span) -> Self {
402        let mut statements: smallvec::SmallVec<_> = statements.into_iter().collect();
403        statements.shrink_to_fit();
404        Self {
405            block: statements,
406            span,
407        }
408    }
409    /// Create an empty [`StmtBlock`].
410    #[inline(always)]
411    #[must_use]
412    pub const fn empty(pos: Position) -> Self {
413        Self {
414            block: StmtBlockContainer::new_const(),
415            span: Span::new(pos, pos),
416        }
417    }
418    /// Returns `true` if this statements block contains no statements.
419    #[inline(always)]
420    #[must_use]
421    pub fn is_empty(&self) -> bool {
422        self.block.is_empty()
423    }
424    /// Number of statements in this statements block.
425    #[inline(always)]
426    #[must_use]
427    pub fn len(&self) -> usize {
428        self.block.len()
429    }
430    /// Get the statements of this statements block.
431    #[inline(always)]
432    #[must_use]
433    pub fn statements(&self) -> &[Stmt] {
434        &self.block
435    }
436    /// Get the statements of this statements block.
437    #[inline(always)]
438    #[must_use]
439    pub fn statements_mut(&mut self) -> &mut StmtBlockContainer {
440        &mut self.block
441    }
442    /// Get an iterator over the statements of this statements block.
443    #[inline(always)]
444    pub fn iter(&self) -> impl Iterator<Item = &Stmt> {
445        self.block.iter()
446    }
447    /// Get the start position (location of the beginning `{`) of this statements block.
448    #[inline(always)]
449    #[must_use]
450    pub const fn position(&self) -> Position {
451        (self.span).start()
452    }
453    /// Get the end position (location of the ending `}`) of this statements block.
454    #[inline(always)]
455    #[must_use]
456    pub const fn end_position(&self) -> Position {
457        (self.span).end()
458    }
459    /// Get the positions (locations of the beginning `{` and ending `}`) of this statements block.
460    #[inline(always)]
461    #[must_use]
462    pub const fn span(&self) -> Span {
463        self.span
464    }
465    /// Get the positions (locations of the beginning `{` and ending `}`) of this statements block
466    /// or a default.
467    #[inline(always)]
468    #[must_use]
469    pub const fn span_or_else(&self, def_start_pos: Position, def_end_pos: Position) -> Span {
470        Span::new(
471            (self.span).start().or_else(def_start_pos),
472            (self.span).end().or_else(def_end_pos),
473        )
474    }
475    /// Set the positions of this statements block.
476    #[inline(always)]
477    pub fn set_position(&mut self, start_pos: Position, end_pos: Position) {
478        self.span = Span::new(start_pos, end_pos);
479    }
480}
481
482impl Borrow<[Stmt]> for StmtBlock {
483    #[inline(always)]
484    #[must_use]
485    fn borrow(&self) -> &[Stmt] {
486        &self.block
487    }
488}
489
490impl AsRef<[Stmt]> for StmtBlock {
491    #[inline(always)]
492    #[must_use]
493    fn as_ref(&self) -> &[Stmt] {
494        &self.block
495    }
496}
497
498impl AsMut<[Stmt]> for StmtBlock {
499    #[inline(always)]
500    #[must_use]
501    fn as_mut(&mut self) -> &mut [Stmt] {
502        &mut self.block
503    }
504}
505
506impl fmt::Debug for StmtBlock {
507    #[cold]
508    #[inline(never)]
509    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
510        f.write_str("Block")?;
511        fmt::Debug::fmt(&self.block, f)?;
512        if !self.span.is_none() {
513            write!(f, " @ {:?}", self.span())?;
514        }
515        Ok(())
516    }
517}
518
519impl From<Stmt> for StmtBlock {
520    #[inline]
521    fn from(stmt: Stmt) -> Self {
522        match stmt {
523            Stmt::Block(block) => *block,
524            Stmt::Noop(pos) => Self {
525                block: StmtBlockContainer::new_const(),
526                span: Span::new(pos, pos),
527            },
528            _ => {
529                let pos = stmt.position();
530                Self {
531                    block: vec![stmt].into(),
532                    span: Span::new(pos, Position::NONE),
533                }
534            }
535        }
536    }
537}
538
539impl IntoIterator for StmtBlock {
540    type Item = Stmt;
541    #[cfg(not(feature = "no_std"))]
542    type IntoIter = smallvec::IntoIter<[Stmt; STMT_BLOCK_INLINE_SIZE]>;
543    #[cfg(feature = "no_std")]
544    type IntoIter = smallvec::IntoIter<[Stmt; crate::STATIC_VEC_INLINE_SIZE]>;
545
546    #[inline(always)]
547    fn into_iter(self) -> Self::IntoIter {
548        self.block.into_iter()
549    }
550}
551
552impl<'a> IntoIterator for &'a StmtBlock {
553    type Item = &'a Stmt;
554    type IntoIter = std::slice::Iter<'a, Stmt>;
555
556    #[inline(always)]
557    fn into_iter(self) -> Self::IntoIter {
558        self.block.iter()
559    }
560}
561
562impl Extend<Stmt> for StmtBlock {
563    #[inline(always)]
564    fn extend<T: IntoIterator<Item = Stmt>>(&mut self, iter: T) {
565        self.block.extend(iter);
566    }
567}
568
569/// _(internals)_ A flow control block containing:
570/// * an expression,
571/// * a statements body
572/// * an alternate statements body
573///
574/// Exported under the `internals` feature only.
575#[derive(Debug, Clone, Hash)]
576pub struct FlowControl {
577    /// Flow control expression.
578    pub expr: Expr,
579    /// Main body.
580    pub body: StmtBlock,
581    /// Branch body.
582    pub branch: StmtBlock,
583}
584
585/// _(internals)_ A statement.
586/// Exported under the `internals` feature only.
587#[derive(Debug, Clone, Hash)]
588#[non_exhaustive]
589#[allow(clippy::type_complexity)]
590pub enum Stmt {
591    /// No-op.
592    Noop(Position),
593    /// `if` expr `{` stmt `}` `else` `{` stmt `}`
594    If(Box<FlowControl>, Position),
595    /// `switch` expr `{` literal or range or _ `if` condition `=>` stmt `,` ... `}`
596    ///
597    /// ### Data Structure
598    ///
599    /// 0) Hash table for (condition, block)
600    /// 1) Default block
601    /// 2) List of ranges: (start, end, inclusive, condition, statement)
602    Switch(Box<(Expr, SwitchCasesCollection)>, Position),
603    /// `while` expr `{` stmt `}` | `loop` `{` stmt `}`
604    ///
605    /// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement.
606    While(Box<FlowControl>, Position),
607    /// `do` `{` stmt `}` `while`|`until` expr
608    ///
609    /// ### Flags
610    ///
611    /// * [`NONE`][ASTFlags::NONE] = `while`  
612    /// * [`NEGATED`][ASTFlags::NEGATED] = `until`
613    Do(Box<FlowControl>, ASTFlags, Position),
614    /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}`
615    For(Box<(Ident, Option<Ident>, FlowControl)>, Position),
616    /// \[`export`\] `let`|`const` id `=` expr
617    ///
618    /// ### Flags
619    ///
620    /// * [`EXPORTED`][ASTFlags::EXPORTED] = `export`  
621    /// * [`CONSTANT`][ASTFlags::CONSTANT] = `const`
622    Var(Box<(Ident, Expr, Option<NonZeroUsize>)>, ASTFlags, Position),
623    /// expr op`=` expr
624    Assignment(Box<(OpAssignment, BinaryExpr)>),
625    /// func `(` expr `,` ... `)`
626    ///
627    /// This is a duplicate of [`Expr::FnCall`] to cover the very common pattern of a single
628    /// function call forming one statement.
629    FnCall(Box<FnCallExpr>, Position),
630    /// `{` stmt`;` ... `}`
631    Block(Box<StmtBlock>),
632    /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
633    TryCatch(Box<FlowControl>, Position),
634    /// [expression][Expr]
635    Expr(Box<Expr>),
636    /// `continue`/`break` expr
637    ///
638    /// ### Flags
639    ///
640    /// * [`NONE`][ASTFlags::NONE] = `continue`
641    /// * [`BREAK`][ASTFlags::BREAK] = `break`
642    BreakLoop(Option<Box<Expr>>, ASTFlags, Position),
643    /// `return`/`throw` expr
644    ///
645    /// ### Flags
646    ///
647    /// * [`NONE`][ASTFlags::NONE] = `return`
648    /// * [`BREAK`][ASTFlags::BREAK] = `throw`
649    Return(Option<Box<Expr>>, ASTFlags, Position),
650    /// `import` expr `as` alias
651    ///
652    /// Not available under `no_module`.
653    #[cfg(not(feature = "no_module"))]
654    Import(Box<(Expr, Ident)>, Position),
655    /// `export` var `as` alias
656    ///
657    /// Not available under `no_module`.
658    #[cfg(not(feature = "no_module"))]
659    Export(Box<(Ident, Ident)>, Position),
660    /// Convert a list of variables to shared.
661    ///
662    /// Not available under `no_closure`.
663    ///
664    /// # Notes
665    ///
666    /// This variant does not map to any language structure.  It is currently only used only to
667    /// convert normal variables into shared variables when they are _captured_ by a closure.
668    #[cfg(not(feature = "no_closure"))]
669    Share(Box<crate::FnArgsVec<(Ident, Option<NonZeroUsize>)>>),
670}
671
672impl Default for Stmt {
673    #[inline(always)]
674    #[must_use]
675    fn default() -> Self {
676        Self::Noop(Position::NONE)
677    }
678}
679
680impl Stmt {
681    /// Is this statement [`Noop`][Stmt::Noop]?
682    #[inline(always)]
683    #[must_use]
684    pub const fn is_noop(&self) -> bool {
685        matches!(self, Self::Noop(..))
686    }
687    /// Get the [options][ASTFlags] of this statement.
688    #[inline]
689    #[must_use]
690    pub const fn options(&self) -> ASTFlags {
691        match self {
692            Self::Do(_, options, _)
693            | Self::Var(_, options, _)
694            | Self::BreakLoop(_, options, _)
695            | Self::Return(_, options, _) => *options,
696
697            Self::Noop(..)
698            | Self::If(..)
699            | Self::Switch(..)
700            | Self::Block(..)
701            | Self::Expr(..)
702            | Self::FnCall(..)
703            | Self::While(..)
704            | Self::For(..)
705            | Self::TryCatch(..)
706            | Self::Assignment(..) => ASTFlags::empty(),
707
708            #[cfg(not(feature = "no_module"))]
709            Self::Import(..) | Self::Export(..) => ASTFlags::empty(),
710
711            #[cfg(not(feature = "no_closure"))]
712            Self::Share(..) => ASTFlags::empty(),
713        }
714    }
715    /// Get the [position][Position] of this statement.
716    #[must_use]
717    pub fn position(&self) -> Position {
718        match self {
719            Self::Noop(pos)
720            | Self::BreakLoop(.., pos)
721            | Self::FnCall(.., pos)
722            | Self::If(.., pos)
723            | Self::Switch(.., pos)
724            | Self::While(.., pos)
725            | Self::Do(.., pos)
726            | Self::For(.., pos)
727            | Self::Return(.., pos)
728            | Self::Var(.., pos)
729            | Self::TryCatch(.., pos) => *pos,
730
731            Self::Assignment(x) => x.0.pos,
732
733            Self::Block(x) => x.position(),
734
735            Self::Expr(x) => x.start_position(),
736
737            #[cfg(not(feature = "no_module"))]
738            Self::Import(.., pos) => *pos,
739            #[cfg(not(feature = "no_module"))]
740            Self::Export(.., pos) => *pos,
741
742            #[cfg(not(feature = "no_closure"))]
743            Self::Share(x) => x[0].0.pos,
744        }
745    }
746    /// Override the [position][Position] of this statement.
747    pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
748        match self {
749            Self::Noop(pos)
750            | Self::BreakLoop(.., pos)
751            | Self::FnCall(.., pos)
752            | Self::If(.., pos)
753            | Self::Switch(.., pos)
754            | Self::While(.., pos)
755            | Self::Do(.., pos)
756            | Self::For(.., pos)
757            | Self::Return(.., pos)
758            | Self::Var(.., pos)
759            | Self::TryCatch(.., pos) => *pos = new_pos,
760
761            Self::Assignment(x) => x.0.pos = new_pos,
762
763            Self::Block(x) => x.set_position(new_pos, x.end_position()),
764
765            Self::Expr(x) => {
766                x.set_position(new_pos);
767            }
768
769            #[cfg(not(feature = "no_module"))]
770            Self::Import(.., pos) => *pos = new_pos,
771            #[cfg(not(feature = "no_module"))]
772            Self::Export(.., pos) => *pos = new_pos,
773
774            #[cfg(not(feature = "no_closure"))]
775            Self::Share(x) => x.iter_mut().for_each(|(x, _)| x.pos = new_pos),
776        }
777
778        self
779    }
780    /// Does this statement return a value?
781    #[must_use]
782    pub const fn returns_value(&self) -> bool {
783        match self {
784            Self::If(..)
785            | Self::Switch(..)
786            | Self::Block(..)
787            | Self::Expr(..)
788            | Self::FnCall(..) => true,
789
790            Self::Noop(..)
791            | Self::While(..)
792            | Self::Do(..)
793            | Self::For(..)
794            | Self::TryCatch(..) => false,
795
796            Self::Var(..) | Self::Assignment(..) | Self::BreakLoop(..) | Self::Return(..) => false,
797
798            #[cfg(not(feature = "no_module"))]
799            Self::Import(..) | Self::Export(..) => false,
800
801            #[cfg(not(feature = "no_closure"))]
802            Self::Share(..) => false,
803        }
804    }
805    /// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
806    #[must_use]
807    pub const fn is_self_terminated(&self) -> bool {
808        match self {
809            Self::If(..)
810            | Self::Switch(..)
811            | Self::While(..)
812            | Self::For(..)
813            | Self::Block(..)
814            | Self::TryCatch(..) => true,
815
816            // A No-op requires a semicolon in order to know it is an empty statement!
817            Self::Noop(..) => false,
818
819            Self::Expr(e) => match &**e {
820                #[cfg(not(feature = "no_custom_syntax"))]
821                Expr::Custom(x, ..) if x.is_self_terminated() => true,
822                _ => false,
823            },
824
825            Self::Var(..)
826            | Self::Assignment(..)
827            | Self::FnCall(..)
828            | Self::Do(..)
829            | Self::BreakLoop(..)
830            | Self::Return(..) => false,
831
832            #[cfg(not(feature = "no_module"))]
833            Self::Import(..) | Self::Export(..) => false,
834
835            #[cfg(not(feature = "no_closure"))]
836            Self::Share(..) => false,
837        }
838    }
839    /// Is this statement _pure_?
840    ///
841    /// A pure statement has no side effects.
842    #[must_use]
843    pub fn is_pure(&self) -> bool {
844        match self {
845            Self::Noop(..) => true,
846            Self::Expr(expr) => expr.is_pure(),
847            Self::If(x, ..) => {
848                x.expr.is_pure()
849                    && x.body.iter().all(Self::is_pure)
850                    && x.branch.iter().all(Self::is_pure)
851            }
852            Self::Switch(x, ..) => {
853                let (expr, sw) = &**x;
854                expr.is_pure()
855                    && sw.cases.values().flat_map(|cases| cases.iter()).all(|&c| {
856                        let block = &sw.expressions[c];
857                        block.lhs.is_pure() && block.rhs.is_pure()
858                    })
859                    && sw.ranges.iter().all(|r| {
860                        let block = &sw.expressions[r.index()];
861                        block.lhs.is_pure() && block.rhs.is_pure()
862                    })
863                    && sw.def_case.is_some()
864                    && sw.expressions[sw.def_case.unwrap()].rhs.is_pure()
865            }
866
867            // Loops that exit can be pure because it can never be infinite.
868            Self::While(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => true,
869            Self::Do(x, options, ..) if matches!(x.expr, Expr::BoolConstant(..)) => match x.expr {
870                Expr::BoolConstant(cond, ..) if cond == options.intersects(ASTFlags::NEGATED) => {
871                    x.body.iter().all(Self::is_pure)
872                }
873                _ => false,
874            },
875
876            // Loops are never pure since they can be infinite - and that's a side effect.
877            Self::While(..) | Self::Do(..) => false,
878
879            // For loops can be pure because if the iterable is pure, it is finite,
880            // so infinite loops can never occur.
881            Self::For(x, ..) => x.2.expr.is_pure() && x.2.body.iter().all(Self::is_pure),
882
883            Self::Var(..) | Self::Assignment(..) | Self::FnCall(..) => false,
884            Self::Block(block, ..) => block.iter().all(Self::is_pure),
885            Self::BreakLoop(..) | Self::Return(..) => false,
886            Self::TryCatch(x, ..) => {
887                x.expr.is_pure()
888                    && x.body.iter().all(Self::is_pure)
889                    && x.branch.iter().all(Self::is_pure)
890            }
891
892            #[cfg(not(feature = "no_module"))]
893            Self::Import(..) => false,
894            #[cfg(not(feature = "no_module"))]
895            Self::Export(..) => false,
896
897            #[cfg(not(feature = "no_closure"))]
898            Self::Share(..) => false,
899        }
900    }
901    /// Does this statement's behavior depend on its containing block?
902    ///
903    /// A statement that depends on its containing block behaves differently when promoted to an
904    /// upper block.
905    ///
906    /// Currently only variable definitions (i.e. `let` and `const`), `import`/`export` statements,
907    /// and `eval` calls (which may in turn define variables) fall under this category.
908    #[inline]
909    #[must_use]
910    pub fn is_block_dependent(&self) -> bool {
911        match self {
912            Self::Var(..) => true,
913
914            Self::Expr(e) => match &**e {
915                Expr::Stmt(s) => s.iter().all(Self::is_block_dependent),
916                Expr::FnCall(x, ..) => !x.is_qualified() && x.name == KEYWORD_EVAL,
917                _ => false,
918            },
919
920            Self::FnCall(x, ..) => !x.is_qualified() && x.name == KEYWORD_EVAL,
921
922            #[cfg(not(feature = "no_module"))]
923            Self::Import(..) | Self::Export(..) => true,
924
925            _ => false,
926        }
927    }
928    /// Is this statement _pure_ within the containing block?
929    ///
930    /// An internally pure statement only has side effects that disappear outside the block.
931    ///
932    /// Currently only variable definitions (i.e. `let` and `const`) and `import`/`export`
933    /// statements are internally pure, other than pure expressions.
934    #[inline]
935    #[must_use]
936    pub fn is_internally_pure(&self) -> bool {
937        match self {
938            Self::Var(x, ..) => x.1.is_pure(),
939
940            Self::Expr(e) => match &**e {
941                Expr::Stmt(s) => s.iter().all(Self::is_internally_pure),
942                _ => self.is_pure(),
943            },
944
945            #[cfg(not(feature = "no_module"))]
946            Self::Import(x, ..) => x.0.is_pure(),
947            #[cfg(not(feature = "no_module"))]
948            Self::Export(..) => true,
949
950            _ => self.is_pure(),
951        }
952    }
953    /// Does this statement break the current control flow through the containing block?
954    ///
955    /// Currently this is only true for `return`, `throw`, `break` and `continue`.
956    ///
957    /// All statements following this statement will essentially be dead code.
958    #[inline]
959    #[must_use]
960    pub const fn is_control_flow_break(&self) -> bool {
961        matches!(self, Self::Return(..) | Self::BreakLoop(..))
962    }
963    /// Return this [`Stmt`], replacing it with [`Stmt::Noop`].
964    #[inline(always)]
965    #[must_use]
966    pub fn take(&mut self) -> Self {
967        mem::take(self)
968    }
969    /// Recursively walk this statement.
970    /// Return `false` from the callback to terminate the walk.
971    pub fn walk<'a>(
972        &'a self,
973        path: &mut Vec<ASTNode<'a>>,
974        on_node: &mut (impl FnMut(&[ASTNode]) -> bool + ?Sized),
975    ) -> bool {
976        // Push the current node onto the path
977        path.push(self.into());
978
979        if !on_node(path) {
980            return false;
981        }
982
983        match self {
984            Self::Var(x, ..) => {
985                if !x.1.walk(path, on_node) {
986                    return false;
987                }
988            }
989            Self::If(x, ..) => {
990                if !x.expr.walk(path, on_node) {
991                    return false;
992                }
993                for s in &x.body {
994                    if !s.walk(path, on_node) {
995                        return false;
996                    }
997                }
998                for s in &x.branch {
999                    if !s.walk(path, on_node) {
1000                        return false;
1001                    }
1002                }
1003            }
1004            Self::Switch(x, ..) => {
1005                let (expr, sw) = &**x;
1006
1007                if !expr.walk(path, on_node) {
1008                    return false;
1009                }
1010                for (.., blocks) in &sw.cases {
1011                    for &b in blocks {
1012                        let block = &sw.expressions[b];
1013
1014                        if !block.lhs.walk(path, on_node) {
1015                            return false;
1016                        }
1017                        if !block.rhs.walk(path, on_node) {
1018                            return false;
1019                        }
1020                    }
1021                }
1022                for r in &sw.ranges {
1023                    let block = &sw.expressions[r.index()];
1024
1025                    if !block.lhs.walk(path, on_node) {
1026                        return false;
1027                    }
1028                    if !block.rhs.walk(path, on_node) {
1029                        return false;
1030                    }
1031                }
1032                if let Some(index) = sw.def_case {
1033                    if !sw.expressions[index].lhs.walk(path, on_node) {
1034                        return false;
1035                    }
1036                }
1037            }
1038            Self::While(x, ..) | Self::Do(x, ..) => {
1039                if !x.expr.walk(path, on_node) {
1040                    return false;
1041                }
1042                for s in x.body.statements() {
1043                    if !s.walk(path, on_node) {
1044                        return false;
1045                    }
1046                }
1047            }
1048            Self::For(x, ..) => {
1049                if !x.2.expr.walk(path, on_node) {
1050                    return false;
1051                }
1052                for s in &x.2.body {
1053                    if !s.walk(path, on_node) {
1054                        return false;
1055                    }
1056                }
1057            }
1058            Self::Assignment(x, ..) => {
1059                if !x.1.lhs.walk(path, on_node) {
1060                    return false;
1061                }
1062                if !x.1.rhs.walk(path, on_node) {
1063                    return false;
1064                }
1065            }
1066            Self::FnCall(x, ..) => {
1067                for s in &*x.args {
1068                    if !s.walk(path, on_node) {
1069                        return false;
1070                    }
1071                }
1072            }
1073            Self::Block(x, ..) => {
1074                for s in x.statements() {
1075                    if !s.walk(path, on_node) {
1076                        return false;
1077                    }
1078                }
1079            }
1080            Self::TryCatch(x, ..) => {
1081                for s in &x.body {
1082                    if !s.walk(path, on_node) {
1083                        return false;
1084                    }
1085                }
1086                for s in &x.branch {
1087                    if !s.walk(path, on_node) {
1088                        return false;
1089                    }
1090                }
1091            }
1092            Self::Expr(e) => {
1093                if !e.walk(path, on_node) {
1094                    return false;
1095                }
1096            }
1097            Self::Return(Some(e), ..) => {
1098                if !e.walk(path, on_node) {
1099                    return false;
1100                }
1101            }
1102            #[cfg(not(feature = "no_module"))]
1103            Self::Import(x, ..) => {
1104                if !x.0.walk(path, on_node) {
1105                    return false;
1106                }
1107            }
1108            _ => (),
1109        }
1110
1111        path.pop().unwrap();
1112
1113        true
1114    }
1115}