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};
pub trait LinkSink: Default {
const RECORDING: bool;
fn record(&mut self, generated: Span, authored: Span, name: Option<&str>);
fn append(&mut self, other: Self, base: u32);
fn into_document(self, text: String) -> EmitDocument;
}
#[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)
}
}
#[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)
}
}
#[derive(Debug)]
pub struct Emitted<L: LinkSink> {
pub text: String,
pub links: L,
pub helpers: HelperSet,
}
impl<L: LinkSink> Emitted<L> {
#[must_use]
pub fn into_document(self) -> EmitDocument {
self.links.into_document(self.text)
}
}
#[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)
}
}
const SPACES: &str = " ";
impl<L: LinkSink> Writer<L> {
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
text: String::with_capacity(capacity),
links: L::default(),
indent: 0,
helpers: HelperSet::default(),
}
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.text
}
#[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)
}
#[inline]
pub fn push(&mut self, text: &str) {
self.text.push_str(text);
}
#[inline]
pub fn push_char(&mut self, ch: char) {
self.text.push(ch);
}
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;
}
}
#[inline]
pub fn indent(&mut self) {
self.indent += 1;
}
#[inline]
pub fn deindent(&mut self) {
self.indent = self.indent.saturating_sub(1);
}
#[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);
}
}
#[inline]
pub fn push_linked(&mut self, text: &str, authored: Span) {
self.push_recorded(text, authored, None);
}
#[inline]
pub fn push_named(&mut self, text: &str, authored: Span, name: &str) {
self.push_recorded(text, authored, Some(name));
}
#[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);
}
#[inline]
pub fn use_helper(&mut self, helper: Helper) {
self.helpers.insert(helper);
}
#[must_use]
pub fn helpers(&self) -> &HelperSet {
&self.helpers
}
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);
}
#[must_use]
pub fn finish(self) -> Emitted<L> {
Emitted {
text: self.text,
links: self.links,
helpers: self.helpers,
}
}
#[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;