vize_l1 0.458.0

L1 - the lossless Vue-template surface tree (codename Sinopia, Davinci P2-7)
Documentation
//! The L1 surface-tree node types.
//!
//! Every node is a composition of [`Token`]s; a token is two `&'a str`
//! slices of the input (`leading` + `text`), so rendering the tree in
//! order reproduces the source bytes exactly. The typed holes — `Missing`
//! tokens, [`ElementClose::Missing`], [`SurfaceChild::Unexpected`] — are
//! the crate-level hole policy's clauses 1 and 2 (see the crate docs).

use vize_l0::{Box, Vec};

mod lint_tag;
pub(crate) use lint_tag::{LintHeaderFact, LintTagFact};

/// Whether a token's syntax is present in the source or a typed hole.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenStatus {
    /// The token's `text` is real source bytes.
    Present,
    /// Required-but-absent syntax (hole policy clause 1). `text` is a
    /// zero-width slice at the offset where the syntax belongs; `leading`
    /// may still carry real gap bytes.
    Missing,
}

/// One terminal of the surface tree: the verbatim gap since the previous
/// terminal (`leading`) plus the terminal's own text.
///
/// `leading` is pure inter-token whitespace in well-formed source; in
/// recovered source it may carry junk bytes under a reported diagnostic
/// (hole policy clause 3). Both slices point into the parsed source, so
/// `render == source` holds by construction and Drop-freedom is trivial.
#[derive(Clone, Copy)]
pub struct Token<'a> {
    /// Verbatim source between the previous terminal and `text`.
    pub leading: &'a str,
    /// The terminal's own bytes; empty when [`TokenStatus::Missing`].
    pub text: &'a str,
    /// Present source syntax, or a typed `Missing` hole.
    pub status: TokenStatus,
    verbatim_opening: bool,
    raw_interpolation: bool,
    lint_tag: Option<LintTagFact>,
    lint_header_literal: bool,
    lint_table_context: bool,
    lint_recovery_context: bool,
}

// Keep public diagnostics and dumps independent of private construction facts.
impl core::fmt::Debug for Token<'_> {
    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        formatter
            .debug_struct("Token")
            .field("leading", &self.leading)
            .field("text", &self.text)
            .field("status", &self.status)
            .finish()
    }
}

impl<'a> Token<'a> {
    /// Construct raw source syntax without a resolved lexical policy fact.
    /// Only native construction can mark a resolved opening-tag mode.
    pub fn present(leading: &'a str, text: &'a str) -> Self {
        Self {
            leading,
            text,
            status: TokenStatus::Present,
            verbatim_opening: false,
            raw_interpolation: false,
            lint_tag: None,
            lint_header_literal: false,
            lint_table_context: false,
            lint_recovery_context: false,
        }
    }

    pub(crate) fn missing(leading: &'a str, at: &'a str) -> Self {
        debug_assert!(at.is_empty());
        Self {
            leading,
            text: at,
            status: TokenStatus::Missing,
            verbatim_opening: false,
            raw_interpolation: false,
            lint_tag: None,
            lint_header_literal: false,
            lint_table_context: false,
            lint_recovery_context: false,
        }
    }

    pub(crate) fn mark_verbatim_opening(&mut self, verbatim: bool) {
        self.verbatim_opening = verbatim;
    }

    pub(crate) fn mark_raw_interpolation(&mut self) {
        self.raw_interpolation = true;
    }

    pub(crate) fn mark_lint_tag(
        &mut self,
        fact: Option<LintTagFact>,
        header_literal: bool,
        table_context: bool,
        recovery_context: bool,
    ) {
        self.lint_tag = fact;
        self.lint_header_literal = header_literal;
        self.lint_table_context = table_context;
        self.lint_recovery_context = recovery_context;
    }

    pub fn is_missing(&self) -> bool {
        self.status == TokenStatus::Missing
    }
}

/// A child at any children level (the root or an element's contents).
#[derive(Debug)]
pub enum SurfaceChild<'a> {
    Element(Box<'a, Element<'a>>),
    /// A raw text run — entities undecoded, whitespace uncondensed.
    Text(Token<'a>),
    /// `{{ expr }}` with its delimiters as tokens.
    Interpolation(Box<'a, Interpolation<'a>>),
    /// A whole comment, framing included: `<!-- … -->` (and the abrupt
    /// empty forms `<!-->` / `<!--->`). Declarations (`<!DOCTYPE …>`)
    /// are consumed silently by the tokenizer in template mode and
    /// surface as [`SurfaceChild::Unexpected`] instead.
    Comment(Token<'a>),
    /// A whole `<![CDATA[ … ]]>` section, framing included.
    Cdata(Token<'a>),
    /// A whole `<? … >` processing instruction, framing included.
    ProcessingInstruction(Token<'a>),
    /// Hole policy clause 2: verbatim bytes with no structural home
    /// (stray end tag, `</>`, trailing junk at EOF).
    Unexpected(Token<'a>),
}

/// `{{ expr }}` or complete native Vue 1 `{{{ expr }}}` framing.
/// The tokenizer only reports an interpolation once its
/// closing delimiter matched, so all three tokens are always present
/// (an unterminated `{{ …` at EOF is a [`SurfaceChild::Text`] run, the
/// tokenizer's own recovery).
#[derive(Debug)]
pub struct Interpolation<'a> {
    /// The opening delimiter (`{{`, or native Vue 1 `{{{`).
    pub open: Token<'a>,
    /// The expression bytes between the delimiters, verbatim.
    pub content: Token<'a>,
    /// The corresponding closing delimiter (`}}`, or native Vue 1 `}}}`).
    pub close: Token<'a>,
}

impl Interpolation<'_> {
    /// Native construction recognized unescaped interpolation framing.
    /// Raw constructors default false; this fact grants no runtime admission.
    pub fn is_raw_html(&self) -> bool {
        self.open.raw_interpolation
    }
}

/// An element: open tag, contents, and how the element ended.
#[derive(Debug)]
pub struct Element<'a> {
    pub open: OpenTag<'a>,
    pub children: Vec<'a, SurfaceChild<'a>>,
    pub close: ElementClose<'a>,
}

impl<'a> Element<'a> {
    /// The tag name as authored (the `lt_name` text without its `<`).
    pub fn tag(&self) -> &'a str {
        let text = self.open.lt_name.text;
        text.get(1..).unwrap_or(text)
    }
}

/// `<name attrs* /? >`.
#[derive(Debug)]
pub struct OpenTag<'a> {
    /// `<` plus the tag name, one token (`"<div"`).
    pub lt_name: Token<'a>,
    pub attrs: Vec<'a, Attribute<'a>>,
    /// The self-closing `/`, when authored.
    pub slash: Option<Token<'a>>,
    /// The closing `>`; a `Missing` hole at EOF-in-tag.
    pub gt: Token<'a>,
}

impl OpenTag<'_> {
    pub(crate) fn lint_tag(&self) -> Option<LintTagFact> {
        self.lt_name.lint_tag
    }

    pub(crate) fn lint_header_is_literal(&self) -> bool {
        self.lt_name.lint_header_literal
    }

    pub(crate) fn lint_in_table_context(&self) -> bool {
        self.lt_name.lint_table_context
    }

    pub(crate) fn lint_in_recovery_context(&self) -> bool {
        self.lt_name.lint_recovery_context
    }

    /// The native dialect's resolved lexical mode for this opening tag.
    /// Includes inherited mode after recovery; raw/compatibility construction
    /// defaults to false. This fact does not admit L2 control semantics.
    pub fn is_verbatim(&self) -> bool {
        self.lt_name.verbatim_opening
    }
}

/// How an element's extent ended (the node-level half of hole clause 1).
#[derive(Debug)]
pub enum ElementClose<'a> {
    /// A real end tag.
    Present(CloseTag<'a>),
    /// Closed by HTML tree construction before its authored end tag.
    /// The later redundant end tag is retained as an [`Unexpected`] child so
    /// rendering remains byte-for-byte faithful.
    ///
    /// [`Unexpected`]: SurfaceChild::Unexpected
    Implicit,
    /// Required but absent: the element was still open when its parent
    /// closed or the input ended. A typed hole, zero-width.
    Missing,
    /// Never required: the open tag was self-closing, or the tag is an
    /// HTML void element.
    NotExpected,
}

/// `</name >`.
#[derive(Debug)]
pub struct CloseTag<'a> {
    /// `</` plus the name, one token (`"</div"`, whitespace kept
    /// verbatim when authored as `</ div`).
    pub lt_slash_name: Token<'a>,
    /// The closing `>`; a `Missing` hole at EOF.
    pub gt: Token<'a>,
}

/// An attribute or directive. Directives stay *raw* at L1: `name` spans
/// the authored spelling (`v-on:click.stop`, `:[key]`, `#slot`) and the
/// semantic split into head/argument/modifiers is L1→L2's job (P2-8).
#[derive(Debug)]
pub struct Attribute<'a> {
    pub name: Token<'a>,
    /// The `=`, when authored (absent in the recovered `a"v"` form too).
    pub eq: Option<Token<'a>>,
    pub value: Option<AttrValue<'a>>,
}

/// An attribute value with its quotes.
#[derive(Debug)]
pub struct AttrValue<'a> {
    /// `None` for unquoted values.
    pub open_quote: Option<Token<'a>>,
    /// The value bytes, verbatim (entities undecoded). A `Missing` hole
    /// when a value was announced but absent (`a= >`).
    pub content: Token<'a>,
    /// `None` for unquoted values; a `Missing` hole when the quote is
    /// unterminated at EOF.
    pub close_quote: Option<Token<'a>>,
}

impl<'a> AttrValue<'a> {
    pub(crate) fn unquoted(content: Token<'a>) -> Self {
        Self {
            open_quote: None,
            content,
            close_quote: None,
        }
    }
}

/// The parse result: the source and the root children level.
#[derive(Debug)]
pub struct SurfaceTree<'a> {
    pub source: &'a str,
    pub children: Vec<'a, SurfaceChild<'a>>,
}

/// Node footprints are pinned (P1-10 discipline: slices, not owned
/// strings; oxc containers). All of these hold pointers, so the figures
/// are 64-bit footprints and the asserts are guarded the way
/// `vize_relief`'s are ("the wasm32 build is 32-bit",
/// `crates/vize_relief/src/relief/elements.rs:31-36`).
#[cfg(target_pointer_width = "64")]
const _: () = {
    assert!(size_of::<Token<'_>>() == 40);
    assert!(size_of::<Option<Token<'_>>>() == 40);
    assert!(size_of::<Interpolation<'_>>() == 120);
    assert!(size_of::<Attribute<'_>>() == 200);
    assert!(size_of::<AttrValue<'_>>() == 120);
    assert!(size_of::<OpenTag<'_>>() == 144);
    assert!(size_of::<CloseTag<'_>>() == 80);
    assert!(size_of::<ElementClose<'_>>() == 80);
    assert!(size_of::<Element<'_>>() == 248);
    assert!(size_of::<SurfaceChild<'_>>() == 48);
    assert!(size_of::<SurfaceTree<'_>>() == 40);
};