vize_l4 0.480.0

L4 - the Davinci emission level (codename Stesura): writer, expression rewriting, module assembly and targets
Documentation
//! The L4 structured emission document (Davinci P3-9).
//!
//! Every template backend writes its generated code into an [`EmitDocument`]:
//! the generated text plus [`SpanLink`]s pairing generated byte ranges with
//! the authored byte ranges they come from. The document is target-neutral.
//! [`EmitDocument::source_map`] serializes its links as Source Map v3
//! segments, and the same links, read as ranges, are the rows a projection
//! consumer (Canon's `ProjectionMapping`) needs.
//!
//! Recording is additive. A document that does not record keeps no links, and
//! its text is byte-identical to one that does, so emitted code never depends
//! on whether maps are requested (TS-11). Fragments, pieces assembled before
//! their final position is known, always record; appending a fragment rebases
//! its links onto the offset where it lands.

use alloc::vec::Vec;

use vize_l0::{Span, String};

use super::source_map::SourceMapBuilder;

mod escape;
mod expression_links;
pub use expression_links::expression_links;

/// One generated↔authored link.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpanLink {
    /// Generated byte range inside the document text. Empty for an anchor
    /// placed before text that is written afterwards.
    pub generated: Span,
    /// Authored byte range. Empty for a unit anchor: generated structure that
    /// opens an authored construct (a block, a branch, a slot function) or
    /// comes from an authored position whose extent the emitter does not own.
    pub authored: Span,
    /// Authored symbol, for identifier and key links.
    pub name: Option<String>,
    /// Whether the link contributes a v3 segment. An expression whose first
    /// byte a nested identifier link already anchors keeps its range for
    /// projection consumers without a duplicate segment.
    pub segment: bool,
}

impl SpanLink {
    #[inline]
    fn rebased(&self, base: u32) -> Self {
        Self {
            generated: Span::new(self.generated.start + base, self.generated.end + base),
            ..self.clone()
        }
    }
}

/// Generated text plus the links from its bytes to the authored source.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EmitDocument {
    text: String,
    links: Vec<SpanLink>,
    recording: bool,
}

impl Default for EmitDocument {
    /// An empty recording fragment.
    #[inline]
    fn default() -> Self {
        Self::new(true)
    }
}

impl From<String> for EmitDocument {
    /// A recording fragment holding `text` with no links.
    #[inline]
    fn from(text: String) -> Self {
        Self {
            text,
            links: Vec::new(),
            recording: true,
        }
    }
}

impl EmitDocument {
    /// An empty document; it keeps links only when `recording`.
    #[inline]
    pub fn new(recording: bool) -> Self {
        Self::with_capacity(0, recording)
    }

    /// An empty document with room for `capacity` bytes of text.
    #[inline]
    pub fn with_capacity(capacity: usize, recording: bool) -> Self {
        Self {
            text: String::with_capacity(capacity),
            links: Vec::new(),
            recording,
        }
    }

    /// A non-recording document holding `text`.
    #[inline]
    pub fn unrecorded(text: String) -> Self {
        Self {
            text,
            links: Vec::new(),
            recording: false,
        }
    }

    /// A fragment holding `text` with no links.
    #[inline]
    pub fn plain(text: &str) -> Self {
        Self::from(String::new(text))
    }

    /// A fragment holding `text` generated from the authored byte `source`.
    #[inline]
    pub fn mapped(text: &str, source: u32) -> Self {
        let mut fragment = Self::default();
        fragment.push_mapped(text, source);
        fragment
    }

    #[inline]
    pub fn is_recording(&self) -> bool {
        self.recording
    }

    #[inline]
    pub fn len(&self) -> usize {
        self.text.len()
    }

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

    #[inline]
    pub fn as_str(&self) -> &str {
        &self.text
    }

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

    /// The generated text, dropping the links.
    #[inline]
    pub fn into_string(self) -> String {
        self.text
    }

    #[inline]
    pub fn into_parts(self) -> (String, Vec<SpanLink>) {
        (self.text, self.links)
    }

    /// A recording fragment from text and links over it, the inverse of
    /// [`Self::into_parts`].
    #[inline]
    pub fn from_parts(text: String, links: Vec<SpanLink>) -> Self {
        Self {
            text,
            links,
            recording: true,
        }
    }

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

    /// Insert unlinked `text` at generated byte `at` (a char boundary),
    /// shifting every link at or after it. A link ending exactly at `at` keeps
    /// its extent; an empty link there moves with the text that follows it.
    #[inline]
    pub fn insert_str(&mut self, at: usize, text: &str) {
        self.text.insert_str(at, text);
        let (at, grown) = (at as u32, text.len() as u32);
        for link in &mut self.links {
            let starts_after = link.generated.start >= at;
            if starts_after {
                link.generated.start += grown;
            }
            if link.generated.end > at || (starts_after && link.generated.end == at) {
                link.generated.end += grown;
            }
        }
    }

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

    #[inline]
    fn cursor(&self) -> u32 {
        self.text.len() as u32
    }

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

    #[inline(always)]
    fn push_recorded(&mut self, text: &str, authored: Span, name: Option<&str>) {
        if self.recording {
            let start = self.cursor();
            self.links.push(SpanLink {
                generated: Span::new(start, start + text.len() as u32),
                authored,
                name: name.map(String::new),
                segment: true,
            });
        }
        self.text.push_str(text);
    }

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

    /// Append `text` generated from the authored unit starting at `source`.
    #[inline]
    pub fn push_mapped(&mut self, text: &str, source: u32) {
        self.push_recorded(text, 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 the authored range `authored`, which
    /// spells the symbol `name`.
    #[inline]
    pub fn push_named(&mut self, text: &str, authored: Span, name: &str) {
        self.push_recorded(text, authored, Some(name));
    }

    /// Append an emitted expression authored at `span` of `source_text`, with
    /// its whole-expression and rewritten-identifier links.
    #[inline(always)]
    pub fn push_expression(&mut self, code: &str, span: Span, source_text: &str) {
        if self.recording {
            let base = self.cursor();
            self.links.extend(
                expression_links(code, span, source_text)
                    .iter()
                    .map(|link| link.rebased(base)),
            );
        }
        self.text.push_str(code);
    }

    /// Append another document, rebasing its links.
    #[inline(always)]
    pub fn push_spanned(&mut self, other: &EmitDocument) {
        if self.recording {
            let base = self.cursor();
            self.links
                .extend(other.links.iter().map(|link| link.rebased(base)));
        }
        self.text.push_str(&other.text);
    }

    /// Record every segment-bearing link into `builder`, in emission order.
    pub fn write_segments(&self, builder: &mut SourceMapBuilder) {
        for link in self.links.iter().filter(|link| link.segment) {
            let (generated, source) = (link.generated.start as usize, link.authored.start);
            match &link.name {
                Some(name) => builder.add_named(generated, source, name),
                None => builder.add_raw(generated, source),
            }
        }
    }

    /// Serialize the links as a Source Map v3 document for `source`.
    pub fn source_map(&self, filename: &str, source: &str) -> String {
        let mut builder = SourceMapBuilder::new();
        self.write_segments(&mut builder);
        builder.finish(&self.text, filename, source)
    }
}

#[cfg(test)]
mod tests;