mod block;
mod inline;
mod section;
pub(crate) mod simd;
mod special_char;
#[cfg(test)]
mod fuzz_finds;
#[cfg(test)]
mod tests;
pub use inline::Inline;
pub(crate) trait OffsetExt {
fn pool_offset(self) -> u32;
fn lines_offset(self) -> u32;
}
impl OffsetExt for usize {
fn pool_offset(self) -> u32 {
u32::try_from(self).expect("inline pool exceeds u32::MAX elements")
}
fn lines_offset(self) -> u32 {
u32::try_from(self).expect("lines pool exceeds u32::MAX elements")
}
}
use crate::simd::ByteSliceExt;
pub use section::{InlineSpan, OrderedListDelimiter, Section, SpanSlice};
pub use special_char::SpecialChar;
use std::borrow::Cow;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MarkdownFile<'src, const MAX_INLINE_DEPTH: u8 = 16, const INLINE_STACK_CAP: usize = 32> {
pub sections: Vec<Section<'src>>,
pool: Vec<Inline<'src>>,
span_pool: Vec<InlineSpan>,
}
impl MarkdownFile<'_, 16, 32> {
#[must_use]
pub fn normalize(input: &str) -> Cow<'_, str> {
let bytes = input.as_bytes();
if bytes
.find_byte(0, SpecialChar::CarriageReturn.byte())
.is_none()
{
return Cow::Borrowed(input);
}
let mut out = String::with_capacity(input.len());
let mut start = 0;
while let Some(cr) = bytes.find_byte(start, SpecialChar::CarriageReturn.byte()) {
out.push_str(&input[start..cr]);
out.push('\n');
start = cr + 1;
if bytes.get(start) == Some(&SpecialChar::Newline.byte()) {
start += 1;
}
}
out.push_str(&input[start..]);
Cow::Owned(out)
}
}
impl<'src, const MAX_INLINE_DEPTH: u8, const INLINE_STACK_CAP: usize>
MarkdownFile<'src, MAX_INLINE_DEPTH, INLINE_STACK_CAP>
{
#[must_use]
pub fn inlines(&self, span: InlineSpan) -> &[Inline<'src>] {
&self[span]
}
#[must_use]
pub fn item_spans(&self, slice: SpanSlice) -> &[InlineSpan] {
&self[slice]
}
#[cfg(test)]
pub(crate) fn walk_all_inlines(&self) {
for section in &self.sections {
match section {
Section::UnorderedList { items } | Section::OrderedList { items, .. } => {
for &span in self.item_spans(*items) {
let _ = self.inlines(span);
}
}
Section::Heading { content, .. }
| Section::Paragraph { content }
| Section::Blockquote { content } => {
let _ = self.inlines(*content);
}
Section::CodeBlock { .. } | Section::HorizontalRule => {}
}
}
}
}
impl<'src, const MAX_INLINE_DEPTH: u8, const INLINE_STACK_CAP: usize> std::ops::Index<InlineSpan>
for MarkdownFile<'src, MAX_INLINE_DEPTH, INLINE_STACK_CAP>
{
type Output = [Inline<'src>];
fn index(&self, span: InlineSpan) -> &[Inline<'src>] {
let start = span.start as usize;
let end = start + span.len as usize;
&self.pool[start..end]
}
}
impl<const MAX_INLINE_DEPTH: u8, const INLINE_STACK_CAP: usize> std::ops::Index<SpanSlice>
for MarkdownFile<'_, MAX_INLINE_DEPTH, INLINE_STACK_CAP>
{
type Output = [InlineSpan];
fn index(&self, slice: SpanSlice) -> &[InlineSpan] {
let start = slice.start as usize;
let end = start + slice.len as usize;
&self.span_pool[start..end]
}
}