block attributes
This commit is contained in:
parent
be333b8715
commit
352be02ccf
3 changed files with 486 additions and 114 deletions
328
src/attr.rs
Normal file
328
src/attr.rs
Normal file
|
@ -0,0 +1,328 @@
|
||||||
|
use crate::CowStr;
|
||||||
|
use crate::DiscontinuousString;
|
||||||
|
use crate::Span;
|
||||||
|
|
||||||
|
use State::*;
|
||||||
|
|
||||||
|
pub fn valid<I: Iterator<Item = char>>(chars: I) -> bool {
|
||||||
|
!Parser::new(chars).any(|e| matches!(e, Element::Invalid))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attributes are relatively rare, we choose to pay 8 bytes always and sometimes an extra
|
||||||
|
// indirection instead of always 24 bytes.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
|
pub struct Attributes<'s>(Option<Box<Vec<(&'s str, CowStr<'s>)>>>);
|
||||||
|
|
||||||
|
impl<'s> Attributes<'s> {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn take(&mut self) -> Self {
|
||||||
|
Self(self.0.take())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse<S: DiscontinuousString<'s>>(&mut self, input: &S) -> bool {
|
||||||
|
for elem in Parser::new(input.chars()) {
|
||||||
|
match elem {
|
||||||
|
Element::Class(c) => self.add("class", input.src(c)),
|
||||||
|
Element::Identifier(i) => self.add("id", input.src(i)),
|
||||||
|
Element::Attribute(a, v) => self.add(
|
||||||
|
match input.src(a) {
|
||||||
|
CowStr::Owned(_) => panic!(),
|
||||||
|
CowStr::Borrowed(s) => s,
|
||||||
|
},
|
||||||
|
input.src(v),
|
||||||
|
),
|
||||||
|
Element::Invalid => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(&mut self, attr: &'s str, val: CowStr<'s>) {
|
||||||
|
if self.0.is_none() {
|
||||||
|
self.0 = Some(Vec::new().into());
|
||||||
|
};
|
||||||
|
|
||||||
|
let attrs = self.0.as_mut().unwrap();
|
||||||
|
attrs.push((attr, val));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (&'s str, &str)> + '_ {
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.flat_map(|v| v.iter().map(|(a, b)| (*a, b.as_ref())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl<'s> FromIterator<(&'s str, &'s str)> for Attributes<'s> {
|
||||||
|
fn from_iter<I: IntoIterator<Item = (&'s str, &'s str)>>(iter: I) -> Self {
|
||||||
|
let attrs = iter
|
||||||
|
.into_iter()
|
||||||
|
.map(|(a, v)| (a, v.into()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if attrs.is_empty() {
|
||||||
|
Attributes::new()
|
||||||
|
} else {
|
||||||
|
Attributes(Some(attrs.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum State {
|
||||||
|
Start,
|
||||||
|
Whitespace,
|
||||||
|
Comment,
|
||||||
|
ClassFirst,
|
||||||
|
Class,
|
||||||
|
IdentifierFirst,
|
||||||
|
Identifier,
|
||||||
|
Attribute,
|
||||||
|
ValueFirst,
|
||||||
|
Value,
|
||||||
|
ValueQuoted,
|
||||||
|
Done,
|
||||||
|
Invalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Parser<I> {
|
||||||
|
chars: I,
|
||||||
|
pos: usize,
|
||||||
|
state: State,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: Iterator<Item = char>> Parser<I> {
|
||||||
|
fn new(chars: I) -> Self {
|
||||||
|
Parser {
|
||||||
|
chars,
|
||||||
|
pos: 0,
|
||||||
|
state: Start,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step_char(&mut self) -> Option<State> {
|
||||||
|
self.chars.next().map(|c| {
|
||||||
|
self.pos += c.len_utf8();
|
||||||
|
match self.state {
|
||||||
|
Start => match c {
|
||||||
|
'{' => Whitespace,
|
||||||
|
c if c.is_whitespace() => Start,
|
||||||
|
_ => Invalid,
|
||||||
|
},
|
||||||
|
Whitespace => match c {
|
||||||
|
'}' => Done,
|
||||||
|
'.' => ClassFirst,
|
||||||
|
'#' => IdentifierFirst,
|
||||||
|
'%' => Comment,
|
||||||
|
c if c.is_ascii_alphanumeric() || matches!(c, '_' | ':' | '-') => Attribute,
|
||||||
|
c if c.is_whitespace() => Whitespace,
|
||||||
|
_ => Invalid,
|
||||||
|
},
|
||||||
|
Comment => {
|
||||||
|
if c == '%' {
|
||||||
|
Whitespace
|
||||||
|
} else {
|
||||||
|
Comment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s @ (ClassFirst | IdentifierFirst) => {
|
||||||
|
if is_name_start(c) {
|
||||||
|
match s {
|
||||||
|
ClassFirst => Class,
|
||||||
|
IdentifierFirst => Identifier,
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Invalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s @ (Class | Identifier | Value) => {
|
||||||
|
if is_name(c) {
|
||||||
|
s
|
||||||
|
} else if c.is_whitespace() {
|
||||||
|
Whitespace
|
||||||
|
} else if c == '}' {
|
||||||
|
Done
|
||||||
|
} else {
|
||||||
|
Invalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Attribute => {
|
||||||
|
if is_name(c) {
|
||||||
|
Attribute
|
||||||
|
} else if c == '=' {
|
||||||
|
ValueFirst
|
||||||
|
} else {
|
||||||
|
Invalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ValueFirst => {
|
||||||
|
if is_name(c) {
|
||||||
|
Value
|
||||||
|
} else if c == '"' {
|
||||||
|
ValueQuoted
|
||||||
|
} else {
|
||||||
|
Invalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ValueQuoted => {
|
||||||
|
if c == '"' {
|
||||||
|
Whitespace
|
||||||
|
} else {
|
||||||
|
ValueQuoted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Done => {
|
||||||
|
if c.is_whitespace() {
|
||||||
|
Done
|
||||||
|
} else {
|
||||||
|
Invalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Invalid => panic!(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step(&mut self) -> (State, Span) {
|
||||||
|
let start = self.pos.saturating_sub(1);
|
||||||
|
|
||||||
|
while let Some(state_next) = self.step_char() {
|
||||||
|
if self.state != state_next {
|
||||||
|
return (
|
||||||
|
std::mem::replace(&mut self.state, state_next),
|
||||||
|
Span::new(start, self.pos - 1),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
if self.state == Done { Done } else { Invalid },
|
||||||
|
Span::new(start, self.pos),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_name_start(c: char) -> bool {
|
||||||
|
c.is_ascii_alphanumeric() || matches!(c, '_' | ':')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_name(c: char) -> bool {
|
||||||
|
is_name_start(c) || c.is_ascii_digit() || matches!(c, '-' | '.')
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Element {
|
||||||
|
Class(Span),
|
||||||
|
Identifier(Span),
|
||||||
|
Attribute(Span, Span),
|
||||||
|
Invalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: Iterator<Item = char>> Iterator for Parser<I> {
|
||||||
|
type Item = Element;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
loop {
|
||||||
|
let (st, span0) = self.step();
|
||||||
|
return match st {
|
||||||
|
ClassFirst | IdentifierFirst => {
|
||||||
|
let (st, span1) = self.step();
|
||||||
|
Some(match st {
|
||||||
|
Class => Element::Class(span1),
|
||||||
|
Identifier => Element::Identifier(span1),
|
||||||
|
_ => return Some(Element::Invalid),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Attribute => {
|
||||||
|
let (st, _span1) = self.step();
|
||||||
|
match st {
|
||||||
|
ValueFirst => {
|
||||||
|
let (st, span2) = self.step();
|
||||||
|
match st {
|
||||||
|
Value => Some(Element::Attribute(span0, span2)),
|
||||||
|
ValueQuoted => Some(Element::Attribute(span0, span2.skip(1))),
|
||||||
|
Invalid => Some(Element::Invalid),
|
||||||
|
_ => panic!("{:?}", st),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Invalid => Some(Element::Invalid),
|
||||||
|
_ => panic!("{:?}", st),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Comment | Start | Whitespace => continue,
|
||||||
|
Done => None,
|
||||||
|
Invalid => Some(Element::Invalid),
|
||||||
|
_ => panic!("{:?}", st),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
macro_rules! test_attr {
|
||||||
|
($src:expr $(,$($av:expr),* $(,)?)?) => {
|
||||||
|
#[allow(unused)]
|
||||||
|
let mut attr =super::Attributes::new();
|
||||||
|
attr.parse(&$src);
|
||||||
|
let actual = attr.iter().collect::<Vec<_>>();
|
||||||
|
let expected = &[$($($av),*,)?];
|
||||||
|
assert_eq!(actual, expected, "\n\n{}\n\n", $src);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty() {
|
||||||
|
test_attr!("{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn class_id() {
|
||||||
|
test_attr!(
|
||||||
|
"{.some_class #some_id}",
|
||||||
|
("class", "some_class"),
|
||||||
|
("id", "some_id"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn value_unquoted() {
|
||||||
|
test_attr!(
|
||||||
|
"{attr0=val0 attr1=val1}",
|
||||||
|
("attr0", "val0"),
|
||||||
|
("attr1", "val1"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn value_quoted() {
|
||||||
|
test_attr!(
|
||||||
|
r#"{attr0="val0" attr1="val1"}"#,
|
||||||
|
("attr0", "val0"),
|
||||||
|
("attr1", "val1"),
|
||||||
|
);
|
||||||
|
test_attr!(
|
||||||
|
r#"{#id .class style="color:red"}"#,
|
||||||
|
("id", "id"),
|
||||||
|
("class", "class"),
|
||||||
|
("style", "color:red")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn comment() {
|
||||||
|
test_attr!("{%%}");
|
||||||
|
test_attr!("{ % abc % }");
|
||||||
|
test_attr!(
|
||||||
|
"{ .some_class % abc % #some_id}",
|
||||||
|
("class", "some_class"),
|
||||||
|
("id", "some_id"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
18
src/block.rs
18
src/block.rs
|
@ -1,6 +1,7 @@
|
||||||
use crate::Span;
|
use crate::Span;
|
||||||
use crate::EOF;
|
use crate::EOF;
|
||||||
|
|
||||||
|
use crate::attr;
|
||||||
use crate::tree;
|
use crate::tree;
|
||||||
|
|
||||||
use Atom::*;
|
use Atom::*;
|
||||||
|
@ -70,7 +71,7 @@ pub enum Leaf {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Container {
|
pub enum Container {
|
||||||
/// Span is first `>` character.
|
/// Span is `>`.
|
||||||
Blockquote,
|
Blockquote,
|
||||||
|
|
||||||
/// Span is class specifier.
|
/// Span is class specifier.
|
||||||
|
@ -79,7 +80,7 @@ pub enum Container {
|
||||||
/// Span is the list marker.
|
/// Span is the list marker.
|
||||||
ListItem,
|
ListItem,
|
||||||
|
|
||||||
/// Span is first `[^` instance.
|
/// Span is `[^`.
|
||||||
Footnote,
|
Footnote,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,6 +270,8 @@ impl BlockParser {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
'{' => attr::valid(line_t.chars())
|
||||||
|
.then(|| (Block::Atom(Attributes), Span::by_len(start, line_t.len()))),
|
||||||
'|' => (&line_t[line_t.len() - 1..] == "|"
|
'|' => (&line_t[line_t.len() - 1..] == "|"
|
||||||
&& &line_t[line_t.len() - 2..line_t.len() - 1] != "\\")
|
&& &line_t[line_t.len() - 2..line_t.len() - 1] != "\\")
|
||||||
.then(|| (Block::Leaf(Table), Span::by_len(start, 1))),
|
.then(|| (Block::Leaf(Table), Span::by_len(start, 1))),
|
||||||
|
@ -614,6 +617,17 @@ mod test {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_attr() {
|
||||||
|
test_parse!(
|
||||||
|
"{.some_class}\npara\n",
|
||||||
|
(Atom(Attributes), "{.some_class}\n"),
|
||||||
|
(Enter(Leaf(Paragraph)), ""),
|
||||||
|
(Inline, "para"),
|
||||||
|
(Exit(Leaf(Paragraph)), ""),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! test_block {
|
macro_rules! test_block {
|
||||||
($src:expr, $kind:expr, $str:expr, $len:expr $(,)?) => {
|
($src:expr, $kind:expr, $str:expr, $len:expr $(,)?) => {
|
||||||
let lines = super::lines($src).map(|sp| sp.of($src));
|
let lines = super::lines($src).map(|sp| sp.of($src));
|
||||||
|
|
254
src/lib.rs
254
src/lib.rs
|
@ -1,5 +1,6 @@
|
||||||
pub mod html;
|
pub mod html;
|
||||||
|
|
||||||
|
mod attr;
|
||||||
mod block;
|
mod block;
|
||||||
mod inline;
|
mod inline;
|
||||||
mod lex;
|
mod lex;
|
||||||
|
@ -8,6 +9,8 @@ mod tree;
|
||||||
|
|
||||||
use span::Span;
|
use span::Span;
|
||||||
|
|
||||||
|
pub use attr::Attributes;
|
||||||
|
|
||||||
type CowStr<'s> = std::borrow::Cow<'s, str>;
|
type CowStr<'s> = std::borrow::Cow<'s, str>;
|
||||||
|
|
||||||
const EOF: char = '\0';
|
const EOF: char = '\0';
|
||||||
|
@ -217,11 +220,11 @@ pub enum Atom {
|
||||||
EmDash,
|
EmDash,
|
||||||
/// A thematic break, typically a horizontal rule.
|
/// A thematic break, typically a horizontal rule.
|
||||||
ThematicBreak,
|
ThematicBreak,
|
||||||
/// A space that may not break a line.
|
/// A space that must not break a line.
|
||||||
NonBreakingSpace,
|
NonBreakingSpace,
|
||||||
/// A newline that may or may not break a line in the output format.
|
/// A newline that may or may not break a line in the output.
|
||||||
Softbreak,
|
Softbreak,
|
||||||
/// A newline that must break a line.
|
/// A newline that must break a line in the output.
|
||||||
Hardbreak,
|
Hardbreak,
|
||||||
/// An escape character, not visible in output.
|
/// An escape character, not visible in output.
|
||||||
Escape,
|
Escape,
|
||||||
|
@ -251,27 +254,6 @@ impl<'s> Container<'s> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attributes are relatively rare, we choose to pay 8 bytes always and sometimes an extra
|
|
||||||
// indirection instead of always 24 bytes.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct Attributes<'s>(Option<Box<Vec<(&'s str, &'s str)>>>);
|
|
||||||
|
|
||||||
impl<'s> Attributes<'s> {
|
|
||||||
#[must_use]
|
|
||||||
pub fn none() -> Self {
|
|
||||||
Self(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn take(&mut self) -> Self {
|
|
||||||
Self(self.0.take())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse(&mut self, src: &'s str) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct InlineChars<'s, 't> {
|
struct InlineChars<'s, 't> {
|
||||||
src: &'s str,
|
src: &'s str,
|
||||||
|
@ -300,10 +282,86 @@ impl<'s, 't> Iterator for InlineChars<'s, 't> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait DiscontinuousString<'s> {
|
||||||
|
type Chars: Iterator<Item = char>;
|
||||||
|
|
||||||
|
fn src(&self, span: Span) -> CowStr<'s>;
|
||||||
|
|
||||||
|
fn chars(&self) -> Self::Chars;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> DiscontinuousString<'s> for &'s str {
|
||||||
|
type Chars = std::str::Chars<'s>;
|
||||||
|
|
||||||
|
fn src(&self, span: Span) -> CowStr<'s> {
|
||||||
|
span.of(self).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chars(&self) -> Self::Chars {
|
||||||
|
str::chars(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> DiscontinuousString<'s> for InlineSpans<'s> {
|
||||||
|
type Chars = InlineChars<'s, 'static>;
|
||||||
|
|
||||||
|
/// Borrow if continuous, copy if discontiunous.
|
||||||
|
fn src(&self, span: Span) -> CowStr<'s> {
|
||||||
|
let mut a = 0;
|
||||||
|
let mut s = String::new();
|
||||||
|
for sp in &self.spans {
|
||||||
|
let b = a + sp.len();
|
||||||
|
if span.start() < b {
|
||||||
|
let r = if a <= span.start() {
|
||||||
|
if span.end() <= b {
|
||||||
|
// continuous
|
||||||
|
return CowStr::Borrowed(
|
||||||
|
&sp.of(self.src)[span.start() - a..span.end() - a],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
(span.start() - a)..sp.len()
|
||||||
|
} else {
|
||||||
|
0..sp.len().min(span.end() - a)
|
||||||
|
};
|
||||||
|
s.push_str(&sp.of(self.src)[r]);
|
||||||
|
}
|
||||||
|
a = b;
|
||||||
|
}
|
||||||
|
assert_eq!(span.len(), s.len());
|
||||||
|
CowStr::Owned(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chars(&self) -> Self::Chars {
|
||||||
|
// SAFETY: do not call set_spans while chars is in use
|
||||||
|
unsafe { std::mem::transmute(InlineChars::new(self.src, &self.spans)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct InlineSpans<'s> {
|
||||||
|
src: &'s str,
|
||||||
|
spans: Vec<Span>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> InlineSpans<'s> {
|
||||||
|
fn new(src: &'s str) -> Self {
|
||||||
|
Self {
|
||||||
|
src,
|
||||||
|
spans: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_spans(&mut self, spans: impl Iterator<Item = Span>) {
|
||||||
|
// avoid allocating new vec if size is sufficient
|
||||||
|
self.spans.clear();
|
||||||
|
self.spans.extend(spans);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Parser<'s> {
|
pub struct Parser<'s> {
|
||||||
src: &'s str,
|
src: &'s str,
|
||||||
tree: block::Tree,
|
tree: block::Tree,
|
||||||
inline_spans: Vec<Span>,
|
inlines: InlineSpans<'s>,
|
||||||
inline_parser: Option<inline::Parser<InlineChars<'s, 'static>>>,
|
inline_parser: Option<inline::Parser<InlineChars<'s, 'static>>>,
|
||||||
inline_start: usize,
|
inline_start: usize,
|
||||||
block_attributes: Attributes<'s>,
|
block_attributes: Attributes<'s>,
|
||||||
|
@ -315,10 +373,10 @@ impl<'s> Parser<'s> {
|
||||||
Self {
|
Self {
|
||||||
src,
|
src,
|
||||||
tree: block::parse(src),
|
tree: block::parse(src),
|
||||||
inline_spans: Vec::new(),
|
inlines: InlineSpans::new(src),
|
||||||
inline_parser: None,
|
inline_parser: None,
|
||||||
inline_start: 0,
|
inline_start: 0,
|
||||||
block_attributes: Attributes::none(),
|
block_attributes: Attributes::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,7 +391,10 @@ impl<'s> Parser<'s> {
|
||||||
inline::Container::InlineMath => Container::Math { display: false },
|
inline::Container::InlineMath => Container::Math { display: false },
|
||||||
inline::Container::DisplayMath => Container::Math { display: true },
|
inline::Container::DisplayMath => Container::Math { display: true },
|
||||||
inline::Container::RawFormat => Container::RawInline {
|
inline::Container::RawFormat => Container::RawInline {
|
||||||
format: self.inline_str_cont(inline.span),
|
format: match self.inlines.src(inline.span) {
|
||||||
|
CowStr::Owned(_) => panic!(),
|
||||||
|
CowStr::Borrowed(s) => s,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
inline::Container::Subscript => Container::Subscript,
|
inline::Container::Subscript => Container::Subscript,
|
||||||
inline::Container::Superscript => Container::Superscript,
|
inline::Container::Superscript => Container::Superscript,
|
||||||
|
@ -345,14 +406,14 @@ impl<'s> Parser<'s> {
|
||||||
inline::Container::SingleQuoted => Container::SingleQuoted,
|
inline::Container::SingleQuoted => Container::SingleQuoted,
|
||||||
inline::Container::DoubleQuoted => Container::DoubleQuoted,
|
inline::Container::DoubleQuoted => Container::DoubleQuoted,
|
||||||
inline::Container::InlineLink => Container::Link(
|
inline::Container::InlineLink => Container::Link(
|
||||||
match self.inline_str(inline.span) {
|
match self.inlines.src(inline.span) {
|
||||||
CowStr::Owned(s) => s.replace('\n', "").into(),
|
CowStr::Owned(s) => s.replace('\n', "").into(),
|
||||||
s @ CowStr::Borrowed(_) => s,
|
s @ CowStr::Borrowed(_) => s,
|
||||||
},
|
},
|
||||||
LinkType::Span(SpanLinkType::Inline),
|
LinkType::Span(SpanLinkType::Inline),
|
||||||
),
|
),
|
||||||
inline::Container::InlineImage => Container::Image(
|
inline::Container::InlineImage => Container::Image(
|
||||||
match self.inline_str(inline.span) {
|
match self.inlines.src(inline.span) {
|
||||||
CowStr::Owned(s) => s.replace('\n', "").into(),
|
CowStr::Owned(s) => s.replace('\n', "").into(),
|
||||||
s @ CowStr::Borrowed(_) => s,
|
s @ CowStr::Borrowed(_) => s,
|
||||||
},
|
},
|
||||||
|
@ -361,7 +422,7 @@ impl<'s> Parser<'s> {
|
||||||
_ => todo!("{:?}", c),
|
_ => todo!("{:?}", c),
|
||||||
};
|
};
|
||||||
if matches!(inline.kind, inline::EventKind::Enter(_)) {
|
if matches!(inline.kind, inline::EventKind::Enter(_)) {
|
||||||
Event::Start(t, Attributes::none())
|
Event::Start(t, Attributes::new())
|
||||||
} else {
|
} else {
|
||||||
Event::End(t)
|
Event::End(t)
|
||||||
}
|
}
|
||||||
|
@ -375,38 +436,10 @@ impl<'s> Parser<'s> {
|
||||||
inline::Atom::Hardbreak => Event::Atom(Atom::Hardbreak),
|
inline::Atom::Hardbreak => Event::Atom(Atom::Hardbreak),
|
||||||
inline::Atom::Escape => Event::Atom(Atom::Escape),
|
inline::Atom::Escape => Event::Atom(Atom::Escape),
|
||||||
},
|
},
|
||||||
inline::EventKind::Str => Event::Str(self.inline_str(inline.span)),
|
inline::EventKind::Str => Event::Str(self.inlines.src(inline.span)),
|
||||||
inline::EventKind::Attributes => todo!(),
|
inline::EventKind::Attributes => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inline_str_cont(&self, span: Span) -> &'s str {
|
|
||||||
span.translate(self.inline_spans[0].start()).of(self.src)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copy string if discontinuous.
|
|
||||||
fn inline_str(&self, span: Span) -> CowStr<'s> {
|
|
||||||
let mut a = 0;
|
|
||||||
let mut s = String::new();
|
|
||||||
for sp in &self.inline_spans {
|
|
||||||
let b = a + sp.len();
|
|
||||||
if span.start() < b {
|
|
||||||
let r = if a <= span.start() {
|
|
||||||
if span.end() <= b {
|
|
||||||
// continuous
|
|
||||||
return CowStr::Borrowed(self.inline_str_cont(span));
|
|
||||||
}
|
|
||||||
(span.start() - a)..sp.len()
|
|
||||||
} else {
|
|
||||||
0..sp.len().min(span.end() - a)
|
|
||||||
};
|
|
||||||
s.push_str(&sp.of(self.src)[r]);
|
|
||||||
}
|
|
||||||
a = b;
|
|
||||||
}
|
|
||||||
assert_eq!(span.len(), s.len());
|
|
||||||
CowStr::Owned(s)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Iterator for Parser<'s> {
|
impl<'s> Iterator for Parser<'s> {
|
||||||
|
@ -415,6 +448,7 @@ impl<'s> Iterator for Parser<'s> {
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
if let Some(parser) = &mut self.inline_parser {
|
if let Some(parser) = &mut self.inline_parser {
|
||||||
if let Some(inline) = parser.next() {
|
if let Some(inline) = parser.next() {
|
||||||
|
// SAFETY: cannot set lifetime 's on self due to trait
|
||||||
return Some(self.inline(inline));
|
return Some(self.inline(inline));
|
||||||
}
|
}
|
||||||
self.inline_parser = None;
|
self.inline_parser = None;
|
||||||
|
@ -427,17 +461,15 @@ impl<'s> Iterator for Parser<'s> {
|
||||||
block::Atom::Blankline => Event::Atom(Atom::Blankline),
|
block::Atom::Blankline => Event::Atom(Atom::Blankline),
|
||||||
block::Atom::ThematicBreak => Event::Atom(Atom::ThematicBreak),
|
block::Atom::ThematicBreak => Event::Atom(Atom::ThematicBreak),
|
||||||
block::Atom::Attributes => {
|
block::Atom::Attributes => {
|
||||||
self.block_attributes.parse(content);
|
self.block_attributes.parse(&content);
|
||||||
|
dbg!(&self.block_attributes);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tree::EventKind::Enter(c) => match c {
|
tree::EventKind::Enter(c) => match c {
|
||||||
block::Node::Leaf(l) => {
|
block::Node::Leaf(l) => {
|
||||||
self.inline_spans = self.tree.inlines().collect();
|
self.inlines.set_spans(self.tree.inlines());
|
||||||
let chars = InlineChars::new(self.src, unsafe {
|
self.inline_parser = Some(inline::Parser::new(self.inlines.chars()));
|
||||||
std::mem::transmute(self.inline_spans.as_slice())
|
|
||||||
});
|
|
||||||
self.inline_parser = Some(inline::Parser::new(chars));
|
|
||||||
self.inline_start = ev.span.end();
|
self.inline_start = ev.span.end();
|
||||||
let container = match l {
|
let container = match l {
|
||||||
block::Leaf::CodeBlock { .. } => {
|
block::Leaf::CodeBlock { .. } => {
|
||||||
|
@ -538,15 +570,15 @@ mod test {
|
||||||
fn heading() {
|
fn heading() {
|
||||||
test_parse!(
|
test_parse!(
|
||||||
"#\n",
|
"#\n",
|
||||||
Start(Heading { level: 1 }, Attributes::none()),
|
Start(Heading { level: 1 }, Attributes::new()),
|
||||||
End(Heading { level: 1 }),
|
End(Heading { level: 1 }),
|
||||||
);
|
);
|
||||||
test_parse!(
|
test_parse!(
|
||||||
"# abc\ndef\n",
|
"# abc\ndef\n",
|
||||||
Start(Heading { level: 1 }, Attributes::none()),
|
Start(Heading { level: 1 }, Attributes::new()),
|
||||||
Str(CowStr::Borrowed("abc")),
|
Str("abc".into()),
|
||||||
Atom(Softbreak),
|
Atom(Softbreak),
|
||||||
Str(CowStr::Borrowed("def")),
|
Str("def".into()),
|
||||||
End(Heading { level: 1 }),
|
End(Heading { level: 1 }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -555,7 +587,7 @@ mod test {
|
||||||
fn blockquote() {
|
fn blockquote() {
|
||||||
test_parse!(
|
test_parse!(
|
||||||
">\n",
|
">\n",
|
||||||
Start(Blockquote, Attributes::none()),
|
Start(Blockquote, Attributes::new()),
|
||||||
Atom(Blankline),
|
Atom(Blankline),
|
||||||
End(Blockquote),
|
End(Blockquote),
|
||||||
);
|
);
|
||||||
|
@ -565,24 +597,24 @@ mod test {
|
||||||
fn para() {
|
fn para() {
|
||||||
test_parse!(
|
test_parse!(
|
||||||
"para",
|
"para",
|
||||||
Start(Paragraph, Attributes::none()),
|
Start(Paragraph, Attributes::new()),
|
||||||
Str(CowStr::Borrowed("para")),
|
Str("para".into()),
|
||||||
End(Paragraph),
|
End(Paragraph),
|
||||||
);
|
);
|
||||||
test_parse!(
|
test_parse!(
|
||||||
"pa ra",
|
"pa ra",
|
||||||
Start(Paragraph, Attributes::none()),
|
Start(Paragraph, Attributes::new()),
|
||||||
Str(CowStr::Borrowed("pa ra")),
|
Str("pa ra".into()),
|
||||||
End(Paragraph),
|
End(Paragraph),
|
||||||
);
|
);
|
||||||
test_parse!(
|
test_parse!(
|
||||||
"para0\n\npara1",
|
"para0\n\npara1",
|
||||||
Start(Paragraph, Attributes::none()),
|
Start(Paragraph, Attributes::new()),
|
||||||
Str(CowStr::Borrowed("para0")),
|
Str("para0".into()),
|
||||||
End(Paragraph),
|
End(Paragraph),
|
||||||
Atom(Blankline),
|
Atom(Blankline),
|
||||||
Start(Paragraph, Attributes::none()),
|
Start(Paragraph, Attributes::new()),
|
||||||
Str(CowStr::Borrowed("para1")),
|
Str("para1".into()),
|
||||||
End(Paragraph),
|
End(Paragraph),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -591,9 +623,9 @@ mod test {
|
||||||
fn verbatim() {
|
fn verbatim() {
|
||||||
test_parse!(
|
test_parse!(
|
||||||
"`abc\ndef",
|
"`abc\ndef",
|
||||||
Start(Paragraph, Attributes::none()),
|
Start(Paragraph, Attributes::new()),
|
||||||
Start(Verbatim, Attributes::none()),
|
Start(Verbatim, Attributes::new()),
|
||||||
Str(CowStr::Borrowed("abc\ndef")),
|
Str("abc\ndef".into()),
|
||||||
End(Verbatim),
|
End(Verbatim),
|
||||||
End(Paragraph),
|
End(Paragraph),
|
||||||
);
|
);
|
||||||
|
@ -602,10 +634,10 @@ mod test {
|
||||||
"> `abc\n",
|
"> `abc\n",
|
||||||
"> def\n", //
|
"> def\n", //
|
||||||
),
|
),
|
||||||
Start(Blockquote, Attributes::none()),
|
Start(Blockquote, Attributes::new()),
|
||||||
Start(Paragraph, Attributes::none()),
|
Start(Paragraph, Attributes::new()),
|
||||||
Start(Verbatim, Attributes::none()),
|
Start(Verbatim, Attributes::new()),
|
||||||
Str(CowStr::Owned("abc\ndef".to_string())),
|
Str("abc\ndef".into()),
|
||||||
End(Verbatim),
|
End(Verbatim),
|
||||||
End(Paragraph),
|
End(Paragraph),
|
||||||
End(Blockquote),
|
End(Blockquote),
|
||||||
|
@ -616,9 +648,9 @@ mod test {
|
||||||
fn raw_inline() {
|
fn raw_inline() {
|
||||||
test_parse!(
|
test_parse!(
|
||||||
"``raw\nraw``{=format}",
|
"``raw\nraw``{=format}",
|
||||||
Start(Paragraph, Attributes::none()),
|
Start(Paragraph, Attributes::new()),
|
||||||
Start(RawInline { format: "format" }, Attributes::none()),
|
Start(RawInline { format: "format" }, Attributes::new()),
|
||||||
Str(CowStr::Borrowed("raw\nraw")),
|
Str("raw\nraw".into()),
|
||||||
End(RawInline { format: "format" }),
|
End(RawInline { format: "format" }),
|
||||||
End(Paragraph),
|
End(Paragraph),
|
||||||
);
|
);
|
||||||
|
@ -628,19 +660,13 @@ mod test {
|
||||||
fn link_inline() {
|
fn link_inline() {
|
||||||
test_parse!(
|
test_parse!(
|
||||||
"[text](url)",
|
"[text](url)",
|
||||||
Start(Paragraph, Attributes::none()),
|
Start(Paragraph, Attributes::new()),
|
||||||
Start(
|
Start(
|
||||||
Link(
|
Link("url".into(), LinkType::Span(SpanLinkType::Inline)),
|
||||||
CowStr::Borrowed("url"),
|
Attributes::new()
|
||||||
LinkType::Span(SpanLinkType::Inline),
|
|
||||||
),
|
|
||||||
Attributes::none()
|
|
||||||
),
|
),
|
||||||
Str(CowStr::Borrowed("text")),
|
Str("text".into()),
|
||||||
End(Link(
|
End(Link("url".into(), LinkType::Span(SpanLinkType::Inline))),
|
||||||
CowStr::Borrowed("url"),
|
|
||||||
LinkType::Span(SpanLinkType::Inline)
|
|
||||||
)),
|
|
||||||
End(Paragraph),
|
End(Paragraph),
|
||||||
);
|
);
|
||||||
test_parse!(
|
test_parse!(
|
||||||
|
@ -648,22 +674,26 @@ mod test {
|
||||||
"> [text](url\n",
|
"> [text](url\n",
|
||||||
"> url)\n", //
|
"> url)\n", //
|
||||||
),
|
),
|
||||||
Start(Blockquote, Attributes::none()),
|
Start(Blockquote, Attributes::new()),
|
||||||
Start(Paragraph, Attributes::none()),
|
Start(Paragraph, Attributes::new()),
|
||||||
Start(
|
Start(
|
||||||
Link(
|
Link("urlurl".into(), LinkType::Span(SpanLinkType::Inline)),
|
||||||
CowStr::Owned("urlurl".to_string()),
|
Attributes::new()
|
||||||
LinkType::Span(SpanLinkType::Inline)
|
|
||||||
),
|
|
||||||
Attributes::none()
|
|
||||||
),
|
),
|
||||||
Str(CowStr::Borrowed("text")),
|
Str("text".into()),
|
||||||
End(Link(
|
End(Link("urlurl".into(), LinkType::Span(SpanLinkType::Inline))),
|
||||||
CowStr::Borrowed("urlurl"),
|
|
||||||
LinkType::Span(SpanLinkType::Inline)
|
|
||||||
)),
|
|
||||||
End(Paragraph),
|
End(Paragraph),
|
||||||
End(Blockquote),
|
End(Blockquote),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn attr_block() {
|
||||||
|
test_parse!(
|
||||||
|
"{.some_class}\npara\n",
|
||||||
|
Start(Paragraph, [("class", "some_class")].into_iter().collect()),
|
||||||
|
Str("para".into()),
|
||||||
|
End(Paragraph),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue