use vize_l0::{Allocator, Span, String, Vec};
use crate::markup::entity::{EntityContext, decode_one, needs_decoding};
mod map;
pub use map::{DecodeMap, DecodeSegment, DecodeSegmentKind};
mod interpolation;
pub use interpolation::prepare_vue_interpolation_in;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SourceError {
InvalidAuthoredSpan,
InvalidDecodedSpan,
SourceTooLarge,
InvalidMapCoverage,
InvalidIdentitySegment,
InvalidEntitySegment,
PartialEntityBoundary,
}
#[derive(Debug, Clone, Copy)]
pub struct EmbedSource<'a> {
authored_root: &'a str,
span: Span,
text: &'a str,
decoded: Option<DecodeMap<'a>>,
}
impl<'a> EmbedSource<'a> {
pub fn authored(source: &'a str, span: Span) -> Result<Self, SourceError> {
checked_len(source.len())?;
let text = source
.get(span.start as usize..span.end as usize)
.ok_or(SourceError::InvalidAuthoredSpan)?;
Ok(Self {
authored_root: source,
span,
text,
decoded: None,
})
}
#[must_use]
pub const fn authored_root(self) -> &'a str {
self.authored_root
}
#[must_use]
pub const fn span(self) -> Span {
self.span
}
#[must_use]
pub const fn text(self) -> &'a str {
self.text
}
#[must_use]
pub const fn decode_map(self) -> Option<DecodeMap<'a>> {
self.decoded
}
pub fn slice_in(self, allocator: &'a Allocator, relative: Span) -> Result<Self, SourceError> {
let text = self
.text
.get(relative.start as usize..relative.end as usize)
.ok_or(SourceError::InvalidDecodedSpan)?;
let span = self.authored_span(relative)?;
if relative.start == 0 && relative.end as usize == self.text.len() {
return Ok(self);
}
let decoded = self
.decoded
.map(|map| map.slice_in(allocator, relative))
.transpose()?
.flatten();
Ok(Self {
authored_root: self.authored_root,
span,
text,
decoded,
})
}
pub fn authored_span(self, relative: Span) -> Result<Span, SourceError> {
self.project(relative, false)
}
pub fn authored_covering_span(self, relative: Span) -> Result<Span, SourceError> {
self.project(relative, true)
}
fn project(self, relative: Span, covering: bool) -> Result<Span, SourceError> {
self.text
.get(relative.start as usize..relative.end as usize)
.ok_or(SourceError::InvalidDecodedSpan)?;
if let Some(map) = self.decoded {
map.project(relative, covering)
} else {
Ok(Span::new(
self.span
.start
.checked_add(relative.start)
.ok_or(SourceError::SourceTooLarge)?,
self.span
.start
.checked_add(relative.end)
.ok_or(SourceError::SourceTooLarge)?,
))
}
}
}
fn checked_len(length: usize) -> Result<u32, SourceError> {
u32::try_from(length).map_err(|_| SourceError::SourceTooLarge)
}
fn checked_span(start: usize, end: usize) -> Result<Span, SourceError> {
Ok(Span::new(checked_len(start)?, checked_len(end)?))
}
fn checked_add(start: usize, length: usize) -> Result<usize, SourceError> {
let end = start
.checked_add(length)
.ok_or(SourceError::SourceTooLarge)?;
checked_len(end)?;
Ok(end)
}
fn append_identity<'a>(
output: &mut String,
segments: &mut Vec<'a, DecodeSegment>,
raw: &str,
base: usize,
start: usize,
end: usize,
) -> Result<(), SourceError> {
if start == end {
return Ok(());
}
let literal = raw
.get(start..end)
.ok_or(SourceError::InvalidAuthoredSpan)?;
let decoded_start = output.len();
let decoded_end = checked_add(decoded_start, literal.len())?;
output.push_str(literal);
segments.push(DecodeSegment::new(
checked_span(decoded_start, decoded_end)?,
checked_span(checked_add(base, start)?, checked_add(base, end)?)?,
DecodeSegmentKind::Identity,
));
Ok(())
}
pub fn prepare_attribute_value<'a>(
allocator: &'a Allocator,
authored_source: &'a str,
span: Span,
) -> Result<EmbedSource<'a>, SourceError> {
prepare_decoded_value(allocator, authored_source, span, EntityContext::Attribute)
}
pub(crate) fn prepare_text_value<'a>(
allocator: &'a Allocator,
authored_source: &'a str,
span: Span,
) -> Result<EmbedSource<'a>, SourceError> {
prepare_decoded_value(allocator, authored_source, span, EntityContext::Text)
}
fn prepare_decoded_value<'a>(
allocator: &'a Allocator,
authored_source: &'a str,
span: Span,
context: EntityContext,
) -> Result<EmbedSource<'a>, SourceError> {
let plain = EmbedSource::authored(authored_source, span)?;
let raw = plain.text();
if !needs_decoding(raw.as_bytes()) {
return Ok(plain);
}
let mut output = String::default();
let mut segments = Vec::new_in(&allocator);
let mut cursor = 0;
for (at, _) in raw.match_indices('&') {
if at < cursor {
continue;
}
let Some((value, consumed)) = raw
.as_bytes()
.get(at..)
.and_then(|bytes| decode_one(bytes, context))
else {
continue;
};
let end = checked_add(at, consumed)?;
if consumed == 0 || raw.get(at..end).is_none() {
return Err(SourceError::InvalidEntitySegment);
}
append_identity(
&mut output,
&mut segments,
raw,
span.start as usize,
cursor,
at,
)?;
let decoded_start = output.len();
let mut width = Some(0usize);
value.for_each(|scalar| {
width = width.and_then(|n| n.checked_add(scalar.len_utf8()));
});
let decoded_end = checked_add(decoded_start, width.ok_or(SourceError::SourceTooLarge)?)?;
value.for_each(|scalar| output.push(scalar));
segments.push(DecodeSegment::new(
checked_span(decoded_start, decoded_end)?,
checked_span(
checked_add(span.start as usize, at)?,
checked_add(span.start as usize, end)?,
)?,
DecodeSegmentKind::Entity,
));
cursor = end;
}
if segments.is_empty() {
return Ok(plain);
}
append_identity(
&mut output,
&mut segments,
raw,
span.start as usize,
cursor,
raw.len(),
)?;
let text = allocator.alloc_str(output.as_str());
let segments = segments.into_boxed_slice().into_arena_slice();
let decoded = DecodeMap::checked(authored_source, span, text, segments)?;
Ok(EmbedSource {
authored_root: authored_source,
span,
text,
decoded: Some(decoded),
})
}
#[cfg(test)]
mod tests;
#[cfg(test)]
mod slice_tests;
#[cfg(test)]
mod origin_tests;