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
//! The top-level [`Program`] node.

use crate::module::ModuleItem;
use crate::span::Spanned;
use crate::statement::Statement;

/// A complete parsed ECMAScript source paired with its source span.
pub type Program = Spanned<ProgramKind>;

/// Whether the program is a Script or a Module.  ECMA-262 treats the two
/// goals as distinct, with differences such as top-level `await` and the
/// availability of `import`/`export`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProgramKind {
    /// A Script: top-level body is a sequence of statements.
    Script {
        /// Top-level statements.
        body: Vec<Statement>,
    },
    /// A Module: top-level body may include import/export declarations.
    Module {
        /// Top-level module items.
        body: Vec<ModuleItem>,
    },
}

impl ProgramKind {
    /// Build a Script program.
    #[must_use]
    pub fn script(body: Vec<Statement>) -> Self {
        Self::Script { body }
    }

    /// Build a Module program.
    #[must_use]
    pub fn module(body: Vec<ModuleItem>) -> Self {
        Self::Module { body }
    }
}

impl std::fmt::Display for ProgramKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Script { body } => write_items(f, "Script", body.iter().map(|s| format!("{s}"))),
            Self::Module { body } => write_items(f, "Module", body.iter().map(|m| format!("{m}"))),
        }
    }
}

fn write_items<I: Iterator<Item = String>>(
    f: &mut std::fmt::Formatter<'_>,
    kind: &str,
    items: I,
) -> std::fmt::Result {
    write!(f, "{kind} {{ ")?;
    let body = items.collect::<Vec<_>>().join("; ");
    write!(f, "{body} }}")
}