vize_l4 0.512.0

L4 - the Davinci emission level (codename Stesura): writer, expression rewriting, module assembly and targets
Documentation
//! The one append-only writer every L4 target uses.
//!
//! Targets write text straight into a [`Writer`]. The writer owns:
//!
//! - the output text, only ever appended to;
//! - indentation (two spaces per level, written by [`Writer::newline`]);
//! - the set of runtime helpers the body used, in first-use order;
//! - span links, through a [`LinkSink`] chosen at compile time. With
//!   [`NoLinks`] every link call compiles to nothing, so a writer that does
//!   not record costs exactly what a bare string buffer costs.
//!
//! Preambles (helper imports, hoists, cache declarations) depend on what the
//! body used, so they are written last into their own writer and joined by
//! [`Writer::finish_with_preamble`]: one exact-capacity allocation and one
//! copy of each part. Nothing is ever `insert_str`-ed mid-text.
//!
//! [`EmitDocument`] and [`SpanLink`] are the finished form of a recording
//! writer. The production compiler retains its independent document until
//! its fix-history gate closes (#6880).

use alloc::vec::Vec;

use vize_l0::{Span, String};

pub mod document;
pub(crate) mod line_endings;
pub mod rewrite_spans;
pub mod source_map;

pub use document::{EmitDocument, SpanLink};

use crate::runtime::{Helper, HelperSet};

/// Where span links go. Chosen per writer at compile time.
pub trait LinkSink: Default {
    /// Whether this sink keeps links. Constant, so callers can skip work that
    /// only feeds links (a symbol name, an expression's identifier spans).
    const RECORDING: bool;

    /// Record that generated bytes `generated` come from authored bytes
    /// `authored`, optionally spelling the symbol `name`.
    fn record(&mut self, generated: Span, authored: Span, name: Option<&str>);

    /// Append `other`'s links, shifted by `base` generated bytes.
    fn append(&mut self, other: Self, base: u32);

    /// The document holding `text` and these links.
    fn into_document(self, text: String) -> EmitDocument;
}

/// The sink of a writer that does not record: every call is a no-op.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct NoLinks;

impl LinkSink for NoLinks {
    const RECORDING: bool = false;

    #[inline(always)]
    fn record(&mut self, _generated: Span, _authored: Span, _name: Option<&str>) {}

    #[inline(always)]
    fn append(&mut self, _other: Self, _base: u32) {}

    fn into_document(self, text: String) -> EmitDocument {
        EmitDocument::unrecorded(text)
    }
}

/// The sink of a recording writer: the [`SpanLink`]s in emission order.
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Recorded {
    links: Vec<SpanLink>,
}

impl Recorded {
    #[must_use]
    pub fn links(&self) -> &[SpanLink] {
        &self.links
    }
}

impl LinkSink for Recorded {
    const RECORDING: bool = true;

    #[inline]
    fn record(&mut self, generated: Span, authored: Span, name: Option<&str>) {
        self.links.push(SpanLink {
            generated,
            authored,
            name: name.map(String::new),
            segment: true,
        });
    }

    fn append(&mut self, other: Self, base: u32) {
        if base == 0 && self.links.is_empty() {
            self.links = other.links;
            return;
        }
        self.links.extend(other.links.into_iter().map(|mut link| {
            link.generated = Span::new(link.generated.start + base, link.generated.end + base);
            link
        }));
    }

    fn into_document(self, text: String) -> EmitDocument {
        EmitDocument::from_parts(text, self.links)
    }
}

/// Finished output: the text and the links over it.
#[derive(Debug)]
pub struct Emitted<L: LinkSink> {
    pub text: String,
    pub links: L,
    pub helpers: HelperSet,
}

impl<L: LinkSink> Emitted<L> {
    /// The text and links as an [`EmitDocument`] (recording iff `L` records).
    #[must_use]
    pub fn into_document(self) -> EmitDocument {
        self.links.into_document(self.text)
    }
}

/// Append-only text writer with indentation, used helpers and span links.
#[derive(Debug)]
pub struct Writer<L: LinkSink = NoLinks> {
    text: String,
    links: L,
    indent: u32,
    helpers: HelperSet,
}

impl<L: LinkSink> Default for Writer<L> {
    fn default() -> Self {
        Self::with_capacity(0)
    }
}

/// Spaces written for indentation, sliced per level.
const SPACES: &str = "                                ";

impl<L: LinkSink> Writer<L> {
    /// An empty writer with room for `capacity` bytes of text.
    #[must_use]
    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            text: String::with_capacity(capacity),
            links: L::default(),
            indent: 0,
            helpers: HelperSet::default(),
        }
    }

    /// The text written so far.
    #[must_use]
    pub fn as_str(&self) -> &str {
        &self.text
    }

    /// Generated byte length so far.
    #[must_use]
    pub fn len(&self) -> usize {
        self.text.len()
    }

    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.text.is_empty()
    }

    #[inline]
    fn cursor(&self) -> u32 {
        u32::try_from(self.text.len()).unwrap_or(u32::MAX)
    }

    /// Append unlinked text.
    #[inline]
    pub fn push(&mut self, text: &str) {
        self.text.push_str(text);
    }

    /// Append one unlinked character.
    #[inline]
    pub fn push_char(&mut self, ch: char) {
        self.text.push(ch);
    }

    /// Start a new line at the current indentation (two spaces per level).
    pub fn newline(&mut self) {
        self.text.push('\n');
        let mut width = self.indent as usize * 2;
        while width > 0 {
            let chunk = width.min(SPACES.len());
            self.text.push_str(SPACES.get(..chunk).unwrap_or_default());
            width -= chunk;
        }
    }

    /// Increase the indentation of following lines.
    #[inline]
    pub fn indent(&mut self) {
        self.indent += 1;
    }

    /// Decrease the indentation of following lines (saturating).
    #[inline]
    pub fn deindent(&mut self) {
        self.indent = self.indent.saturating_sub(1);
    }

    /// Anchor the next written byte at authored byte `source`.
    #[inline]
    pub fn anchor(&mut self, source: u32) {
        if L::RECORDING {
            let at = self.cursor();
            self.links
                .record(Span::new(at, at), Span::new(source, source), None);
        }
    }

    /// Append `text` generated from the authored range `authored`.
    #[inline]
    pub fn push_linked(&mut self, text: &str, authored: Span) {
        self.push_recorded(text, authored, None);
    }

    /// Append `text` generated from `authored`, which spells symbol `name`.
    #[inline]
    pub fn push_named(&mut self, text: &str, authored: Span, name: &str) {
        self.push_recorded(text, authored, Some(name));
    }

    /// Append pieces of one rewritten symbol without allocating a temporary.
    #[inline]
    pub fn push_named_parts<const N: usize>(
        &mut self,
        parts: [&str; N],
        authored: Span,
        name: &str,
    ) {
        let start = self.cursor();
        for part in parts {
            self.text.push_str(part);
        }
        if L::RECORDING {
            self.links
                .record(Span::new(start, self.cursor()), authored, Some(name));
        }
    }

    #[inline]
    fn push_recorded(&mut self, text: &str, authored: Span, name: Option<&str>) {
        if L::RECORDING {
            let start = self.cursor();
            let end = start.saturating_add(u32::try_from(text.len()).unwrap_or(u32::MAX));
            self.links.record(Span::new(start, end), authored, name);
        }
        self.text.push_str(text);
    }

    /// Mark `helper` used by the body.
    #[inline]
    pub fn use_helper(&mut self, helper: Helper) {
        self.helpers.insert(helper);
    }

    /// The helpers used so far, in first-use order.
    #[must_use]
    pub fn helpers(&self) -> &HelperSet {
        &self.helpers
    }

    /// Append a finished fragment, rebasing its links and merging its helpers.
    /// The fragment's indentation is its own; it does not change this one.
    pub fn append(&mut self, fragment: Self) {
        let base = self.cursor();
        self.text.push_str(&fragment.text);
        self.links.append(fragment.links, base);
        self.helpers.extend(&fragment.helpers);
    }

    /// Finish the body without a preamble.
    #[must_use]
    pub fn finish(self) -> Emitted<L> {
        Emitted {
            text: self.text,
            links: self.links,
            helpers: self.helpers,
        }
    }

    /// Finish with `preamble` placed before the body: one exact-capacity
    /// allocation, body links shifted by the preamble length, helpers merged
    /// in body-then-preamble first-use order.
    #[must_use]
    pub fn finish_with_preamble(self, preamble: Self) -> Emitted<L> {
        let base = preamble.cursor();
        let mut text = String::with_capacity(preamble.text.len() + self.text.len());
        text.push_str(&preamble.text);
        text.push_str(&self.text);
        let mut links = preamble.links;
        links.append(self.links, base);
        let mut helpers = self.helpers;
        helpers.extend(&preamble.helpers);
        Emitted {
            text,
            links,
            helpers,
        }
    }
}

#[cfg(test)]
mod tests;