use alloc::{string::String, vec::Vec};
use super::Line;
use crate::{
ast::{Inline, NodeMeta},
diagnostic::Diagnostic,
span::Span,
};
#[derive(Clone, Copy, Debug)]
pub(super) struct Segment {
text_start: usize,
text_end: usize,
source_start: usize,
source_end: usize,
}
impl Segment {
pub(super) fn text_start(&self) -> usize {
self.text_start
}
pub(super) fn text_end(&self) -> usize {
self.text_end
}
fn verbatim(&self) -> bool {
self.text_end - self.text_start == self.source_end - self.source_start
}
fn start_at(&self, position: usize) -> usize {
if self.verbatim() {
self.source_start + (position - self.text_start)
} else {
self.source_start
}
}
fn end_at(&self, position: usize) -> usize {
if self.verbatim() {
self.source_start + (position - self.text_start)
} else {
self.source_end
}
}
}
pub(super) fn start_of(segments: &[Segment], position: usize) -> usize {
match segments.iter().find(|segment| position < segment.text_end) {
Some(segment) => segment.start_at(position.max(segment.text_start)),
None => segments
.last()
.map_or(0, |segment| segment.end_at(segment.text_end)),
}
}
pub(super) fn end_of(segments: &[Segment], position: usize) -> usize {
match segments
.iter()
.rev()
.find(|segment| position > segment.text_start)
{
Some(segment) => segment.end_at(position.min(segment.text_end)),
None => segments
.first()
.map_or(0, |segment| segment.start_at(segment.text_start)),
}
}
#[derive(Clone, Debug, Default)]
pub(super) struct SourceMap {
segments: Vec<Segment>,
}
impl SourceMap {
pub(super) fn verbatim(len: usize, source_start: usize) -> Self {
Self {
segments: alloc::vec![Segment {
text_start: 0,
text_end: len,
source_start,
source_end: source_start + len,
}],
}
}
pub(super) fn segments(&self) -> &[Segment] {
&self.segments
}
pub(super) fn push(
&mut self,
text_start: usize,
text_len: usize,
source_start: usize,
source_end: usize,
) {
if text_len == 0 {
return;
}
let segment = Segment {
text_start,
text_end: text_start + text_len,
source_start,
source_end,
};
if let Some(last) = self.segments.last_mut() {
if last.verbatim()
&& segment.verbatim()
&& last.text_end == segment.text_start
&& last.source_end == segment.source_start
{
last.text_end = segment.text_end;
last.source_end = segment.source_end;
return;
}
}
self.segments.push(segment);
}
fn truncate(&mut self, len: usize) {
while self
.segments
.last()
.is_some_and(|last| last.text_start >= len)
{
self.segments.pop();
}
if let Some(last) = self.segments.last_mut() {
if last.text_end > len {
if last.verbatim() {
last.source_end -= last.text_end - len;
}
last.text_end = len;
}
}
}
pub(super) fn copy(&mut self, text_start: usize, segments: &[Segment], from: usize, to: usize) {
let mut covered = from;
for segment in segments {
if segment.text_end <= from || segment.text_start >= to {
continue;
}
let start = segment.text_start.max(from);
let end = segment.text_end.min(to);
let (source_start, source_end) = if segment.verbatim() {
(segment.start_at(start), segment.end_at(end))
} else {
(segment.source_start, segment.source_end)
};
self.push(
text_start + (start - from),
end - start,
source_start,
source_end,
);
covered = end;
}
if covered < to {
let at = end_of(segments, covered);
self.push(text_start + (covered - from), to - covered, at, at);
}
}
}
#[derive(Clone, Debug, Default)]
pub(super) struct DerivedText {
pub(super) text: String,
map: SourceMap,
pending_eol: Option<(usize, usize)>,
}
impl DerivedText {
pub(super) fn map(&self) -> &SourceMap {
&self.map
}
pub(super) fn into_parts(self) -> (String, SourceMap) {
(self.text, self.map)
}
fn join(&mut self) {
if !self.text.is_empty() || self.pending_eol.is_some() {
let (start, end) = self.pending_eol.unwrap_or_else(|| {
let at = end_of(self.map.segments(), self.text.len());
(at, at)
});
self.map.push(self.text.len(), 1, start, end);
self.text.push('\n');
}
}
pub(super) fn push_line(&mut self, line: &Line<'_>, slice: &str) {
self.join();
self.append(line, slice);
self.pending_eol = Some(line.eol_source());
}
pub(super) fn append(&mut self, line: &Line<'_>, slice: &str) {
let Some(offset) = slice_offset(line.text, slice) else {
debug_assert!(slice.is_empty(), "a slice of the line");
if !slice.is_empty() {
let (start, end) = (line.source_start(0), line.source_end(line.text.len()));
self.append_replacing(slice, start, end);
}
return;
};
line.copy_into(&mut self.map, self.text.len(), offset, offset + slice.len());
self.text.push_str(slice);
}
pub(super) fn trim_final_whitespace(&mut self) {
let len = self.text.trim_end_matches([' ', '\t']).len();
self.text.truncate(len);
self.map.truncate(len);
}
pub(super) fn append_replacing(&mut self, text: &str, source_start: usize, source_end: usize) {
self.map
.push(self.text.len(), text.len(), source_start, source_end);
self.text.push_str(text);
}
}
fn slice_offset(text: &str, slice: &str) -> Option<usize> {
let start = text.as_ptr() as usize;
let offset = (slice.as_ptr() as usize).checked_sub(start)?;
(offset + slice.len() <= text.len()).then_some(offset)
}
pub(super) fn translate_inlines(
map: &SourceMap,
inlines: &mut [Inline],
diagnostics: &mut [Diagnostic],
) {
let mut translator = Translator {
segments: map.segments(),
cursor: 0,
};
translator.inlines(inlines);
translator.cursor = 0;
for diagnostic in diagnostics {
if let Some(span) = diagnostic.span {
diagnostic.span = Some(translator.span(span));
}
}
}
pub(super) fn translate_tokens(map: &SourceMap, tokens: &mut [crate::parse::InlineToken]) {
let mut translator = Translator {
segments: map.segments(),
cursor: 0,
};
for token in tokens {
token.span = translator.span(token.span);
}
}
pub(super) fn translate_span(map: &SourceMap, span: Span) -> Span {
let segments = map.segments();
let cursor = segments
.partition_point(|segment| segment.text_end <= span.start)
.min(segments.len().saturating_sub(1));
Translator { segments, cursor }.span(span)
}
struct Translator<'a> {
segments: &'a [Segment],
cursor: usize,
}
impl Translator<'_> {
fn span(&mut self, span: Span) -> Span {
if self.segments.is_empty() {
return span;
}
if span.start < self.segments[self.cursor].text_start {
self.cursor = self
.segments
.partition_point(|segment| segment.text_end <= span.start)
.min(self.segments.len() - 1);
}
while self.cursor + 1 < self.segments.len()
&& span.start >= self.segments[self.cursor].text_end
{
self.cursor += 1;
}
let start = start_of(&self.segments[self.cursor..], span.start);
let last = self.end_segment(span.end);
let end = end_of(&self.segments[self.cursor..=last], span.end);
Span::new(start, end.max(start))
}
fn end_segment(&self, end: usize) -> usize {
let last = self.segments.len() - 1;
if end > self.segments[last].text_start {
return last;
}
let covers = |index: usize| end <= self.segments[index].text_end;
let mut low = self.cursor;
let mut step = 1;
while !covers(low) {
let next = (low + step).min(last);
if covers(next) {
let found =
self.segments[low + 1..=next].partition_point(|segment| end > segment.text_end);
return low + 1 + found;
}
low = next;
step *= 2;
}
low
}
fn meta(&mut self, meta: &mut NodeMeta) {
if let Some(span) = meta.span {
meta.span = Some(self.span(span));
}
}
fn inlines(&mut self, inlines: &mut [Inline]) {
for inline in inlines {
match inline {
Inline::Text(node) => self.meta(&mut node.meta),
Inline::Escape(node) => self.meta(&mut node.meta),
Inline::CharacterReference(node) => self.meta(&mut node.meta),
Inline::SoftBreak(node) => self.meta(&mut node.meta),
Inline::LineBreak(node) => self.meta(&mut node.meta),
Inline::Shortcode(node) => self.meta(&mut node.meta),
Inline::Code(node) => self.meta(&mut node.meta),
Inline::Autolink(node) => self.meta(&mut node.meta),
Inline::Html(node) => self.meta(&mut node.meta),
Inline::Math(node) => self.meta(&mut node.meta),
Inline::FootnoteReference(node) => self.meta(&mut node.meta),
Inline::WikiLink(node) => self.meta(&mut node.meta),
Inline::Tag(node) => self.meta(&mut node.meta),
Inline::Emphasis(node) => self.container(&mut node.meta, &mut node.children),
Inline::Strong(node) => self.container(&mut node.meta, &mut node.children),
Inline::Delete(node) => self.container(&mut node.meta, &mut node.children),
Inline::Mark(node) => self.container(&mut node.meta, &mut node.children),
Inline::InlineFootnote(node) => self.container(&mut node.meta, &mut node.children),
Inline::Link(node) => self.container(&mut node.meta, &mut node.children),
Inline::Image(node) => self.container(&mut node.meta, &mut node.alt),
Inline::LinkReference(node) => self.container(&mut node.meta, &mut node.children),
Inline::ImageReference(node) => self.container(&mut node.meta, &mut node.alt),
Inline::TextDirective(node) => self.container(&mut node.meta, &mut node.label),
}
}
}
fn container(&mut self, meta: &mut NodeMeta, children: &mut [Inline]) {
self.meta(meta);
self.inlines(children);
}
}