ecma-syntax-cat 0.1.0

ECMAScript abstract syntax tree as comp-cat-rs-idiomatic Rust types. ESTree-shaped, ES2024-complete, no panics, no Rc, no interior mutability. Foundation crate for boa-cat and related downstream tooling.
Documentation
//! ECMAScript statements.

use crate::class::Class;
use crate::declaration::VariableDeclaration;
use crate::expression::Expression;
use crate::function::Function;
use crate::identifier::Identifier;
use crate::pattern::Pattern;
use crate::span::Spanned;

/// A statement paired with its source span.
pub type Statement = Spanned<StatementKind>;

/// The shape of an ECMAScript statement.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StatementKind {
    /// A block: `{ ... }`.
    Block {
        /// Statements inside the block.
        body: Vec<Statement>,
    },
    /// An empty statement (`;`).
    Empty,
    /// `debugger;`
    Debugger,
    /// An expression evaluated as a statement.
    Expression {
        /// The expression.
        expression: Expression,
    },
    /// `if (test) consequent else? alternate`.
    If {
        /// The test expression.
        test: Expression,
        /// The body run when `test` is truthy.
        consequent: Box<Statement>,
        /// The optional `else` branch.
        alternate: Option<Box<Statement>>,
    },
    /// `switch (discriminant) { cases }`.
    Switch {
        /// The discriminant expression.
        discriminant: Expression,
        /// The cases.
        cases: Vec<SwitchCase>,
    },
    /// `for (init; test; update) body`.
    For {
        /// Optional initialiser (declaration or expression).
        init: Option<ForInit>,
        /// Optional loop-continuation test.
        test: Option<Expression>,
        /// Optional update expression evaluated after each iteration.
        update: Option<Expression>,
        /// The loop body.
        body: Box<Statement>,
    },
    /// `for (left in right) body`.
    ForIn {
        /// The iteration target (declaration or assignment-target pattern).
        left: ForLeft,
        /// The object whose enumerable properties are iterated.
        right: Expression,
        /// The loop body.
        body: Box<Statement>,
    },
    /// `for (left of right) body`, possibly `for await`.
    ForOf {
        /// The iteration target.
        left: ForLeft,
        /// The iterable expression.
        right: Expression,
        /// The loop body.
        body: Box<Statement>,
        /// True for `for await (...) of`.
        is_await: bool,
    },
    /// `while (test) body`.
    While {
        /// The test expression.
        test: Expression,
        /// The loop body.
        body: Box<Statement>,
    },
    /// `do body while (test);`.
    DoWhile {
        /// The loop body.
        body: Box<Statement>,
        /// The test expression checked after each iteration.
        test: Expression,
    },
    /// `return argument?;`
    Return {
        /// Optional return value.
        argument: Option<Expression>,
    },
    /// `throw argument;`
    Throw {
        /// The value to throw.
        argument: Expression,
    },
    /// `try { ... } catch (e) { ... } finally { ... }`.
    Try {
        /// The protected block.
        block: Vec<Statement>,
        /// Optional catch clause.
        handler: Option<CatchClause>,
        /// Optional finally block.
        finalizer: Option<Vec<Statement>>,
    },
    /// `break label?;`
    Break {
        /// Optional label to break out of.
        label: Option<Identifier>,
    },
    /// `continue label?;`
    Continue {
        /// Optional label to continue.
        label: Option<Identifier>,
    },
    /// `label: body` -- attach a label to a loop or block.
    Labeled {
        /// The label name.
        label: Identifier,
        /// The labelled statement.
        body: Box<Statement>,
    },
    /// A variable declaration treated as a statement.
    VariableDeclaration(VariableDeclaration),
    /// A function declaration.
    FunctionDeclaration(Function),
    /// A class declaration.
    ClassDeclaration(Class),
}

/// One arm of a `switch` statement.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SwitchCase {
    test: Option<Expression>,
    consequent: Vec<Statement>,
}

impl SwitchCase {
    /// Build a case.  `test = None` is the `default:` arm.
    #[must_use]
    pub fn new(test: Option<Expression>, consequent: Vec<Statement>) -> Self {
        Self { test, consequent }
    }

    /// The case label expression, or `None` for `default:`.
    #[must_use]
    pub fn test(&self) -> Option<&Expression> {
        self.test.as_ref()
    }

    /// The statements that run when this case is selected.
    #[must_use]
    pub fn consequent(&self) -> &[Statement] {
        &self.consequent
    }
}

/// The catch clause of a `try` statement.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CatchClause {
    param: Option<Pattern>,
    body: Vec<Statement>,
}

impl CatchClause {
    /// Build a catch clause.  `param = None` represents bare `catch { ... }`
    /// without a binding.
    #[must_use]
    pub fn new(param: Option<Pattern>, body: Vec<Statement>) -> Self {
        Self { param, body }
    }

    /// The binding receiving the thrown value, if any.
    #[must_use]
    pub fn param(&self) -> Option<&Pattern> {
        self.param.as_ref()
    }

    /// The statements in the catch body.
    #[must_use]
    pub fn body(&self) -> &[Statement] {
        &self.body
    }
}

/// The initialiser slot of a `for(;;)` loop.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ForInit {
    /// `for (let i = 0; ...)`
    Declaration(VariableDeclaration),
    /// `for (i = 0; ...)`
    Expression(Expression),
}

/// The left-hand side of `for-in` / `for-of`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ForLeft {
    /// `for (let x in obj)`
    Declaration(VariableDeclaration),
    /// `for (x in obj)`
    Pattern(Pattern),
}

impl std::fmt::Display for StatementKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Block { body } => write_block(f, body),
            Self::Empty => f.write_str(";"),
            Self::Debugger => f.write_str("debugger;"),
            Self::Expression { expression } => write!(f, "{expression};"),
            Self::If {
                test,
                consequent,
                alternate,
            } => write_if(f, test, consequent, alternate.as_deref()),
            Self::Switch {
                discriminant,
                cases,
            } => write_switch(f, discriminant, cases),
            Self::For {
                init,
                test,
                update,
                body,
            } => write_for(f, init.as_ref(), test.as_ref(), update.as_ref(), body),
            Self::ForIn { left, right, body } => write!(f, "for ({left} in {right}) {body}"),
            Self::ForOf {
                left,
                right,
                body,
                is_await,
            } => write_for_of(f, left, right, body, *is_await),
            Self::While { test, body } => write!(f, "while ({test}) {body}"),
            Self::DoWhile { body, test } => write!(f, "do {body} while ({test});"),
            Self::Return { argument } => write_return(f, argument.as_ref()),
            Self::Throw { argument } => write!(f, "throw {argument};"),
            Self::Try {
                block,
                handler,
                finalizer,
            } => write_try(f, block, handler.as_ref(), finalizer.as_ref()),
            Self::Break { label } => write_break_continue(f, "break", label.as_ref()),
            Self::Continue { label } => write_break_continue(f, "continue", label.as_ref()),
            Self::Labeled { label, body } => write!(f, "{label}: {body}"),
            Self::VariableDeclaration(decl) => write!(f, "{decl};"),
            Self::FunctionDeclaration(func) => write!(f, "{func}"),
            Self::ClassDeclaration(class) => write!(f, "{class}"),
        }
    }
}

fn write_block(f: &mut std::fmt::Formatter<'_>, body: &[Statement]) -> std::fmt::Result {
    let lines = body
        .iter()
        .map(|s| format!("  {s}"))
        .collect::<Vec<_>>()
        .join("\n");
    if body.is_empty() {
        f.write_str("{}")
    } else {
        write!(f, "{{\n{lines}\n}}")
    }
}

fn write_if(
    f: &mut std::fmt::Formatter<'_>,
    test: &Expression,
    consequent: &Statement,
    alternate: Option<&Statement>,
) -> std::fmt::Result {
    write!(f, "if ({test}) {consequent}")?;
    match alternate {
        Some(alt) => write!(f, " else {alt}"),
        None => Ok(()),
    }
}

fn write_switch(
    f: &mut std::fmt::Formatter<'_>,
    discriminant: &Expression,
    cases: &[SwitchCase],
) -> std::fmt::Result {
    write!(f, "switch ({discriminant}) {{ ")?;
    let body = cases
        .iter()
        .map(|c| match c.test() {
            Some(t) => format!("case {t}: ..."),
            None => "default: ...".to_owned(),
        })
        .collect::<Vec<_>>()
        .join(" ");
    write!(f, "{body} }}")
}

fn write_for(
    f: &mut std::fmt::Formatter<'_>,
    init: Option<&ForInit>,
    test: Option<&Expression>,
    update: Option<&Expression>,
    body: &Statement,
) -> std::fmt::Result {
    let init_str = init.map_or(String::new(), |i| format!("{i}"));
    let test_str = test.map_or(String::new(), |t| format!("{t}"));
    let update_str = update.map_or(String::new(), |u| format!("{u}"));
    write!(f, "for ({init_str}; {test_str}; {update_str}) {body}")
}

fn write_for_of(
    f: &mut std::fmt::Formatter<'_>,
    left: &ForLeft,
    right: &Expression,
    body: &Statement,
    is_await: bool,
) -> std::fmt::Result {
    let keyword = if is_await { "for await" } else { "for" };
    write!(f, "{keyword} ({left} of {right}) {body}")
}

fn write_return(
    f: &mut std::fmt::Formatter<'_>,
    argument: Option<&Expression>,
) -> std::fmt::Result {
    match argument {
        Some(arg) => write!(f, "return {arg};"),
        None => f.write_str("return;"),
    }
}

fn write_try(
    f: &mut std::fmt::Formatter<'_>,
    block: &[Statement],
    handler: Option<&CatchClause>,
    finalizer: Option<&Vec<Statement>>,
) -> std::fmt::Result {
    write!(f, "try {{ {} stmts }}", block.len())?;
    if let Some(h) = handler {
        match h.param() {
            Some(p) => write!(f, " catch ({p}) {{ ... }}")?,
            None => write!(f, " catch {{ ... }}")?,
        }
    }
    if let Some(fin) = finalizer {
        write!(f, " finally {{ {} stmts }}", fin.len())?;
    }
    Ok(())
}

fn write_break_continue(
    f: &mut std::fmt::Formatter<'_>,
    keyword: &str,
    label: Option<&Identifier>,
) -> std::fmt::Result {
    match label {
        Some(l) => write!(f, "{keyword} {l};"),
        None => write!(f, "{keyword};"),
    }
}

impl std::fmt::Display for ForInit {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Declaration(decl) => write!(f, "{decl}"),
            Self::Expression(expr) => write!(f, "{expr}"),
        }
    }
}

impl std::fmt::Display for ForLeft {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Declaration(decl) => write!(f, "{decl}"),
            Self::Pattern(pat) => write!(f, "{pat}"),
        }
    }
}