diff --git a/src/html.rs b/src/html.rs index a82b329..a484b34 100644 --- a/src/html.rs +++ b/src/html.rs @@ -402,6 +402,7 @@ impl<'s, I: Iterator>, W: std::fmt::Write> Writer<'s, I, W> { number, number, number )?; } + Atom::Symbol(sym) => write!(self.out, ":{}:", sym)?, Atom::LeftSingleQuote => self.out.write_str("‘")?, Atom::RightSingleQuote => self.out.write_str("’")?, Atom::LeftDoubleQuote => self.out.write_str("“")?, diff --git a/src/inline.rs b/src/inline.rs index 1a52bfb..93a624a 100644 --- a/src/inline.rs +++ b/src/inline.rs @@ -11,6 +11,7 @@ use Container::*; #[derive(Debug, Clone, PartialEq, Eq)] pub enum Atom { FootnoteReference, + Symbol, Softbreak, Hardbreak, Escape, @@ -115,6 +116,7 @@ impl + Clone> Parser { self.parse_verbatim(&first) .or_else(|| self.parse_attributes(&first)) .or_else(|| self.parse_autolink(&first)) + .or_else(|| self.parse_symbol(&first)) .or_else(|| self.parse_footnote_reference(&first)) .or_else(|| self.parse_container(&first)) .or_else(|| self.parse_atom(&first)) @@ -351,6 +353,37 @@ impl + Clone> Parser { } } + fn parse_symbol(&mut self, first: &lex::Token) -> Option { + if first.kind == lex::Kind::Sym(Symbol::Colon) { + let mut ahead = self.lexer.chars(); + let mut end = false; + let mut valid = true; + let len = (&mut ahead) + .take_while(|c| { + if *c == ':' { + end = true; + } else if !c.is_ascii_alphanumeric() && !matches!(c, '-' | '+' | '_') { + valid = false; + } + !end && !c.is_whitespace() + }) + .map(char::len_utf8) + .sum(); + (end && valid).then(|| { + self.lexer = lex::Lexer::new(ahead); + self.span = self.span.after(len); + let span = self.span; + self.span = self.span.after(1); + Event { + kind: EventKind::Atom(Symbol), + span, + } + }) + } else { + None + } + } + fn parse_footnote_reference(&mut self, first: &lex::Token) -> Option { if first.kind == lex::Kind::Open(Delimiter::Bracket) && matches!( diff --git a/src/lex.rs b/src/lex.rs index ebb70db..25f11e1 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -49,6 +49,7 @@ pub enum Symbol { Quote2, Tilde, Underscore, + Colon, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -232,6 +233,7 @@ impl + Clone> Lexer { } '<' => Sym(Lt), '|' => Sym(Pipe), + ':' => Sym(Colon), '`' => self.eat_seq(Backtick), '$' => self.eat_seq(Dollar), diff --git a/src/lib.rs b/src/lib.rs index 8553d4b..3d759f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -236,6 +236,8 @@ pub enum OrderedListStyle { pub enum Atom<'s> { /// A footnote reference. FootnoteReference(&'s str, usize), + /// A symbol, by default rendered literally but may be treated specially. + Symbol(CowStr<'s>), /// Left single quotation mark. LeftSingleQuote, /// Right double quotation mark. @@ -654,6 +656,7 @@ impl<'s> Parser<'s> { number, ) } + inline::Atom::Symbol => Atom::Symbol(self.inlines.src(inline.span)), inline::Atom::Quote { ty, left } => match (ty, left) { (inline::QuoteType::Single, true) => Atom::LeftSingleQuote, (inline::QuoteType::Single, false) => Atom::RightSingleQuote, @@ -1088,6 +1091,18 @@ mod test { ); } + #[test] + fn symbol() { + test_parse!( + "abc :+1: def", + Start(Paragraph, Attributes::new()), + Str("abc ".into()), + Atom(Symbol("+1".into())), + Str(" def".into()), + End(Paragraph), + ); + } + #[test] fn link_inline() { test_parse!(