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
//! Source-position newtypes and the [`Spanned`] wrapper.
//!
//! Every AST node in this crate is a [`Spanned`] value carrying both a kind
//! enum and a [`Span`] denoting the source range the node was parsed from.
//! Spans use 1-based line/column numbers and 0-based byte offsets, both
//! recorded so downstream tools can pick whichever fits their layer.

/// A source position: line, column, and byte offset.
///
/// Line and column are 1-based for human-readable diagnostics; offset is
/// 0-based for slicing the source buffer.  All three are tracked because
/// different layers want different forms: editors think in line/column,
/// source-map generators think in offset.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Position {
    line: u32,
    column: u32,
    offset: u32,
}

impl Position {
    /// Build a position.
    #[must_use]
    pub fn new(line: u32, column: u32, offset: u32) -> Self {
        Self {
            line,
            column,
            offset,
        }
    }

    /// A synthetic position with zero offsets, useful for constructed nodes
    /// that have no original source.
    #[must_use]
    pub fn synthetic() -> Self {
        Self {
            line: 0,
            column: 0,
            offset: 0,
        }
    }

    /// The 1-based line number.
    #[must_use]
    pub fn line(&self) -> u32 {
        self.line
    }

    /// The 1-based column number.
    #[must_use]
    pub fn column(&self) -> u32 {
        self.column
    }

    /// The 0-based byte offset.
    #[must_use]
    pub fn offset(&self) -> u32 {
        self.offset
    }
}

impl std::fmt::Display for Position {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}:{}", self.line, self.column)
    }
}

/// A half-open source range `[start, end)`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Span {
    start: Position,
    end: Position,
}

impl Span {
    /// Build a span from start and end positions.
    #[must_use]
    pub fn new(start: Position, end: Position) -> Self {
        Self { start, end }
    }

    /// A synthetic span (both endpoints at offset zero) for constructed
    /// nodes that have no original source.
    #[must_use]
    pub fn synthetic() -> Self {
        Self {
            start: Position::synthetic(),
            end: Position::synthetic(),
        }
    }

    /// The start position.
    #[must_use]
    pub fn start(&self) -> Position {
        self.start
    }

    /// The end position.
    #[must_use]
    pub fn end(&self) -> Position {
        self.end
    }

    /// Whether this span contains `other` (inclusive of the same range).
    #[must_use]
    pub fn contains(&self, other: Span) -> bool {
        self.start.offset() <= other.start.offset() && other.end.offset() <= self.end.offset()
    }
}

impl std::fmt::Display for Span {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}..{}", self.start, self.end)
    }
}

/// A value paired with its source span.
///
/// The wrapper centralises span tracking so each AST kind enum can focus on
/// its semantic variants without having to thread a span field through
/// every variant manually.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Spanned<T> {
    value: T,
    span: Span,
}

impl<T> Spanned<T> {
    /// Build a spanned value.
    pub fn new(value: T, span: Span) -> Self {
        Self { value, span }
    }

    /// Borrow the inner value.
    pub fn value(&self) -> &T {
        &self.value
    }

    /// The span of this node.
    #[must_use]
    pub fn span(&self) -> Span {
        self.span
    }

    /// Decompose into the inner value and its span.
    pub fn into_parts(self) -> (T, Span) {
        (self.value, self.span)
    }

    /// Map the inner value while preserving the span.
    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Spanned<U> {
        Spanned {
            value: f(self.value),
            span: self.span,
        }
    }
}

impl<T: std::fmt::Display> std::fmt::Display for Spanned<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.value.fmt(f)
    }
}