From ad9f29bcb9066f2db5ff630a4dca8b6249b3b9f6 Mon Sep 17 00:00:00 2001 From: PoiScript Date: Thu, 17 Jan 2019 11:47:14 +0800 Subject: [PATCH] feat: list parsing --- Cargo.toml | 2 + src/elements/block.rs | 35 +++++-- src/elements/list.rs | 226 ++++++++++++++++++++++++++++++++++++++++++ src/elements/mod.rs | 27 ++++- src/elements/rule.rs | 47 ++++----- src/export/html.rs | 22 +++- src/export/mod.rs | 134 +++++++++++-------------- src/lib.rs | 3 + src/parser.rs | 138 +++++++++++++++++++++++--- 9 files changed, 512 insertions(+), 122 deletions(-) create mode 100644 src/elements/list.rs diff --git a/Cargo.toml b/Cargo.toml index 149608e..7769294 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,5 @@ authors = ["PoiScript "] [dependencies] jetscii = "0.4.3" +lazy_static = "1.2.0" +regex = "1" diff --git a/src/elements/block.rs b/src/elements/block.rs index 972fbf3..06f3e55 100644 --- a/src/elements/block.rs +++ b/src/elements/block.rs @@ -1,3 +1,5 @@ +use regex::Regex; + #[cfg_attr(test, derive(PartialEq))] #[derive(Debug)] pub struct Block; @@ -12,9 +14,9 @@ impl Block { let args = eol!(src); let name = until_while!(src, 8, |c| c == b' ' || c == b'\n', |c: u8| c .is_ascii_alphabetic())?; - // TODO: ignore case match - let content = src.find(&format!("\n#+END_{}", &src[8..name]))?; - let end = eol!(src, content + 1); + let end_re = format!(r"(?im)^[ \t]*#\+END_{}[ \t]*$", &src[8..name]); + let end_re = Regex::new(&end_re).unwrap(); + let (content, end) = end_re.find(src).map(|m| (m.start(), m.end()))?; Some(( &src[8..name], @@ -24,13 +26,34 @@ impl Block { Some(&src[name..args]) }, args, - content + 1, - end + 1, + content, + // including the eol character + if end < src.len() && src.as_bytes()[end] == b'\n' { + end + 1 + } else { + end + }, )) } } #[test] fn parse() { - // TODO: testing + assert_eq!( + Block::parse("#+BEGIN_SRC\n#+END_SRC"), + Some(("SRC", None, 11, 12, 21)) + ); + assert_eq!( + Block::parse( + r#"#+BEGIN_SRC rust +fn main() { + // print "Hello World!" to the console + println!("Hello World!"); +} +#+END_SRC +"# + ), + Some(("SRC", Some(" rust"), 16, 104, 114)) + ); + // TODO: more testing } diff --git a/src/elements/list.rs b/src/elements/list.rs new file mode 100644 index 0000000..1d1a458 --- /dev/null +++ b/src/elements/list.rs @@ -0,0 +1,226 @@ +pub struct List; + +impl List { + #[inline] + fn is_item(src: &str) -> bool { + if src.len() < 2 { + return false; + } + + let bytes = src.as_bytes(); + let i = match bytes[0] { + b'*' | b'-' | b'+' => 1, + b'0'...b'9' => { + let i = bytes + .iter() + .position(|&c| !c.is_ascii_digit()) + .unwrap_or_else(|| src.len()); + if i >= src.len() - 1 { + return false; + } + let c = bytes[i]; + if !(c == b'.' || c == b')') { + return false; + } + i + 1 + } + _ => return false, + }; + + // bullet is follwed by a space or line ending + bytes[i] == b' ' || bytes[i] == b'\n' + } + + #[inline] + pub fn is_ordered(byte: u8) -> bool { + match byte { + b'*' | b'-' | b'+' => false, + b'0'...b'9' => true, + _ => unreachable!(), + } + } + + // returns (contents_begin, contents_end) + // TODO: handle nested list + pub fn parse_item(src: &str, ident: usize) -> (usize, usize) { + ( + src[ident..].find(' ').map(|i| ident + i + 1).unwrap(), + if ident > 0 { + src.find(&format!("\n{:1$}", " ", ident)) + .map(|i| i + 1) + .unwrap_or_else(|| src.len()) + } else { + src.find('\n').map(|i| i + 1).unwrap_or_else(|| src.len()) + }, + ) + } + + // return (ident, is_ordered, end) + pub fn parse(src: &str) -> Option<(usize, bool, usize)> { + macro_rules! ident { + ($src:expr) => { + $src.as_bytes() + .iter() + .position(|&c| c != b' ' && c != b'\t') + .unwrap_or(0) + }; + } + + let bytes = src.as_bytes(); + let starting_ident = ident!(src); + + if !Self::is_item(&src[starting_ident..]) { + return None; + } + + let is_ordered = Self::is_ordered(bytes[starting_ident]); + let mut pos = starting_ident; + while let Some(i) = src[pos..] + .find('\n') + .map(|i| i + pos + 1) + .filter(|&i| i != src.len()) + { + let ident = ident!(src[i..]); + + // less indented than its starting line + if ident < starting_ident { + return Some((starting_ident, is_ordered, i - 1)); + } + + if ident > starting_ident { + pos = i; + continue; + } + + if bytes[ident + i] == b'\n' && pos < src.len() { + let nextline_ident = ident!(src[ident + i + 1..]); + + // check if it's two consecutive empty lines + if nextline_ident < starting_ident + || (ident + i + 1 + nextline_ident < src.len() + && bytes[ident + i + 1 + nextline_ident] == b'\n') + { + return Some((starting_ident, is_ordered, ident + i + 1 + nextline_ident)); + } + + if nextline_ident == starting_ident { + if Self::is_item(&src[i + nextline_ident + 1..]) { + pos = i + nextline_ident + 1; + continue; + } else { + return Some((starting_ident, is_ordered, ident + i + 1 + nextline_ident)); + } + } + } + + if Self::is_item(&src[i + ident..]) { + pos = i; + continue; + } else { + return Some((starting_ident, is_ordered, i - 1)); + } + } + + Some((starting_ident, is_ordered, src.len())) + } +} + +#[test] +fn parse() { + assert_eq!( + List::parse( + r"+ item1 ++ item2 ++ item3" + ), + Some((0, false, 23)) + ); + assert_eq!( + List::parse( + r"* item1 +* item2 + +* item3" + ), + Some((0, false, 24)) + ); + assert_eq!( + List::parse( + r"- item1 +- item2 + + +- item1" + ), + Some((0, false, 17)) + ); + assert_eq!( + List::parse( + r"1. item1 + 2. item1 +3. item2" + ), + Some((0, true, 28)) + ); + assert_eq!( + List::parse( + r" 1) item1 + 2) item1 + 3) item2" + ), + Some((2, true, 10)) + ); + assert_eq!( + List::parse( + r" + item1 + 1) item1 + + item2" + ), + Some((2, false, 32)) + ); + assert_eq!( + List::parse( + r" item1 + + item1 + + item2" + ), + None + ); +} + +#[test] +fn is_item() { + assert!(List::is_item("+ item")); + assert!(List::is_item("- item")); + assert!(List::is_item("10. item")); + assert!(List::is_item("10) item")); + assert!(List::is_item("1. item")); + assert!(List::is_item("1) item")); + assert!(List::is_item("10. ")); + assert!(List::is_item("10.\n")); + assert!(!List::is_item("10.")); + assert!(!List::is_item("-item")); + assert!(!List::is_item("+item")); +} + +#[test] +fn parse_item() { + assert_eq!(List::parse_item("+ Item1\n+ Item2", 0), (2, 8)); + assert_eq!( + List::parse_item( + r"+ item1 + + item1 + + item2", + 0 + ), + (2, 8) + ); + assert_eq!( + List::parse_item( + r" 1. item1 + + item2", + 2 + ), + (5, 11) + ); +} diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 6e0e9f1..440ae6d 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -72,6 +72,11 @@ pub enum Element<'a> { }, Rule, Comment(&'a str), + List { + ident: usize, + is_ordered: bool, + end: usize, + }, } impl<'a> Element<'a> { @@ -125,6 +130,23 @@ impl<'a> Element<'a> { }; } + if bytes[pos] == b'+' + || bytes[pos] == b'-' + || bytes[pos] == b'*' + || (bytes[pos] >= b'0' && bytes[pos] <= b'9') + { + if let Some((ident, is_ordered, list_end)) = List::parse(&src[end..]) { + ret!( + Element::List { + ident, + is_ordered, + end: list_end + }, + end + ); + } + } + if bytes[pos] == b'\n' { return (start, Some(Element::Paragraph { end, trailing: pos }), None); } @@ -134,7 +156,8 @@ impl<'a> Element<'a> { // Rule if bytes[pos] == b'-' { - if let Some(off) = Rule::parse(&src[pos..]) { + let off = Rule::parse(&src[pos..]); + if off != 0 { ret!(Element::Rule, off); } } @@ -227,7 +250,7 @@ impl<'a> Element<'a> { } // Comment - if bytes[pos] == b'#' && bytes.get(pos + 1).filter(|&&b| b == b' ').is_some() { + if bytes[pos] == b'#' && bytes.get(pos + 1).map(|&b| b == b' ').unwrap_or(false) { let eol = src[pos..] .find('\n') .map(|i| i + pos + 1) diff --git a/src/elements/rule.rs b/src/elements/rule.rs index 287c4bf..5b11bcf 100644 --- a/src/elements/rule.rs +++ b/src/elements/rule.rs @@ -1,34 +1,35 @@ +use regex::Regex; + +lazy_static! { + static ref RULE_REGEX: Regex = Regex::new(r"^[ \t]*-{5,}[ \t]*\n?$").unwrap(); +} + #[cfg_attr(test, derive(PartialEq))] #[derive(Debug)] pub struct Rule; impl Rule { - pub fn parse(src: &str) -> Option { - let end = eol!(src); - let leading = until_while!(src, 0, b'-', |c| c == b' ' || c == b'\t')?; - if src[leading..end].chars().all(|c| c == '-') && end - leading > 4 { - Some(end) - } else { - None - } + pub fn parse(src: &str) -> usize { + RULE_REGEX.find(src).map(|m| m.end()).unwrap_or(0) } } #[test] fn parse() { - assert_eq!(Rule::parse("-----").unwrap(), "-----".len()); - assert_eq!(Rule::parse("--------").unwrap(), "--------".len()); - assert_eq!(Rule::parse(" -----").unwrap(), " -----".len()); - assert_eq!(Rule::parse("\t\t-----").unwrap(), "\t\t-----".len()); - - assert!(Rule::parse("").is_none()); - assert!(Rule::parse("----").is_none()); - assert!(Rule::parse(" ----").is_none()); - assert!(Rule::parse(" 0----").is_none()); - assert!(Rule::parse("0 ----").is_none()); - assert!(Rule::parse("0------").is_none()); - assert!(Rule::parse("----0----").is_none()); - assert!(Rule::parse("\t\t----").is_none()); - assert!(Rule::parse("------0").is_none()); - assert!(Rule::parse("----- 0").is_none()); + assert_eq!(Rule::parse("-----"), "-----".len()); + assert_eq!(Rule::parse("--------"), "--------".len()); + assert_eq!(Rule::parse(" -----"), " -----".len()); + assert_eq!(Rule::parse("\t\t-----"), "\t\t-----".len()); + assert_eq!(Rule::parse("\t\t-----\n"), "\t\t-----\n".len()); + assert_eq!(Rule::parse("\t\t----- \n"), "\t\t----- \n".len()); + assert_eq!(Rule::parse(""), 0); + assert_eq!(Rule::parse("----"), 0); + assert_eq!(Rule::parse(" ----"), 0); + assert_eq!(Rule::parse(" 0----"), 0); + assert_eq!(Rule::parse("0 ----"), 0); + assert_eq!(Rule::parse("0------"), 0); + assert_eq!(Rule::parse("----0----"), 0); + assert_eq!(Rule::parse("\t\t----"), 0); + assert_eq!(Rule::parse("------0"), 0); + assert_eq!(Rule::parse("----- 0"), 0); } diff --git a/src/export/html.rs b/src/export/html.rs index b4d4605..8805eb4 100644 --- a/src/export/html.rs +++ b/src/export/html.rs @@ -85,11 +85,25 @@ impl Handler for HtmlHandler { fn handle_end_dyn_block(&mut self, w: &mut W) -> Result<()> { Ok(()) } - fn handle_list_start(&mut self, w: &mut W) -> Result<()> { - Ok(()) + fn handle_start_list(&mut self, w: &mut W, is_ordered: bool) -> Result<()> { + if is_ordered { + write!(w, "
    ") + } else { + write!(w, "
      ") + } } - fn handle_list_end(&mut self, w: &mut W) -> Result<()> { - Ok(()) + fn handle_end_list(&mut self, w: &mut W, is_ordered: bool) -> Result<()> { + if is_ordered { + write!(w, "
") + } else { + write!(w, "") + } + } + fn handle_start_list_item(&mut self, w: &mut W) -> Result<()> { + write!(w, "
  • ") + } + fn handle_end_list_item(&mut self, w: &mut W) -> Result<()> { + write!(w, "
  • ") } fn handle_aff_keywords(&mut self, w: &mut W) -> Result<()> { Ok(()) diff --git a/src/export/mod.rs b/src/export/mod.rs index 5dbd19b..af8679f 100644 --- a/src/export/mod.rs +++ b/src/export/mod.rs @@ -34,8 +34,10 @@ pub trait Handler { fn handle_verse_block(&mut self, w: &mut W, contents: &str, args: Option<&str>) -> Result<()>; fn handle_start_dyn_block(&mut self, w: &mut W, name: &str, args: Option<&str>) -> Result<()>; fn handle_end_dyn_block(&mut self, w: &mut W) -> Result<()>; - fn handle_list_start(&mut self, w: &mut W) -> Result<()>; - fn handle_list_end(&mut self, w: &mut W) -> Result<()>; + fn handle_start_list(&mut self, w: &mut W, is_ordered: bool) -> Result<()>; + fn handle_end_list(&mut self, w: &mut W, is_ordered: bool) -> Result<()>; + fn handle_start_list_item(&mut self, w: &mut W) -> Result<()>; + fn handle_end_list_item(&mut self, w: &mut W) -> Result<()>; fn handle_aff_keywords(&mut self, w: &mut W) -> Result<()>; fn handle_call(&mut self, w: &mut W) -> Result<()>; fn handle_clock(&mut self, w: &mut W) -> Result<()>; @@ -91,92 +93,76 @@ impl<'a, W: Write, H: Handler> Render<'a, W, H> { pub fn render(&mut self) -> Result<()> { use parser::Event::*; + let w = &mut self.writer; + for event in &mut self.parser { match event { - StartHeadline(hdl) => self.handler.handle_start_headline(&mut self.writer, hdl)?, - EndHeadline => self.handler.handle_end_headline(&mut self.writer)?, - StartSection => self.handler.handle_start_section(&mut self.writer)?, - EndSection => self.handler.handle_end_section(&mut self.writer)?, - StartParagraph => self.handler.handle_start_paragraph(&mut self.writer)?, - EndParagraph => self.handler.handle_end_paragraph(&mut self.writer)?, - StartCenterBlock => self.handler.handle_start_center_block(&mut self.writer)?, - EndCenterBlock => self.handler.handle_end_center_block(&mut self.writer)?, - StartQuoteBlock => self.handler.handle_start_quote_block(&mut self.writer)?, - EndQuoteBlock => self.handler.handle_end_quote_block(&mut self.writer)?, + StartHeadline(hdl) => self.handler.handle_start_headline(w, hdl)?, + EndHeadline => self.handler.handle_end_headline(w)?, + StartSection => self.handler.handle_start_section(w)?, + EndSection => self.handler.handle_end_section(w)?, + StartParagraph => self.handler.handle_start_paragraph(w)?, + EndParagraph => self.handler.handle_end_paragraph(w)?, + StartCenterBlock => self.handler.handle_start_center_block(w)?, + EndCenterBlock => self.handler.handle_end_center_block(w)?, + StartQuoteBlock => self.handler.handle_start_quote_block(w)?, + EndQuoteBlock => self.handler.handle_end_quote_block(w)?, StartSpecialBlock { name, args } => { - self.handler - .handle_start_special_block(&mut self.writer, name, args)? + self.handler.handle_start_special_block(w, name, args)? } - EndSpecialBlock => self.handler.handle_end_special_block(&mut self.writer)?, + EndSpecialBlock => self.handler.handle_end_special_block(w)?, CommentBlock { contents, args } => { - self.handler - .handle_comment_block(&mut self.writer, contents, args)? + self.handler.handle_comment_block(w, contents, args)? } ExampleBlock { contents, args } => { - self.handler - .handle_example_block(&mut self.writer, contents, args)? + self.handler.handle_example_block(w, contents, args)? } ExportBlock { contents, args } => { - self.handler - .handle_export_block(&mut self.writer, contents, args)? - } - SrcBlock { contents, args } => { - self.handler - .handle_src_block(&mut self.writer, contents, args)? + self.handler.handle_export_block(w, contents, args)? } + SrcBlock { contents, args } => self.handler.handle_src_block(w, contents, args)?, VerseBlock { contents, args } => { - self.handler - .handle_verse_block(&mut self.writer, contents, args)? + self.handler.handle_verse_block(w, contents, args)? } StartDynBlock { name, args } => { - self.handler - .handle_start_dyn_block(&mut self.writer, name, args)? + self.handler.handle_start_dyn_block(w, name, args)? } - EndDynBlock => self.handler.handle_end_dyn_block(&mut self.writer)?, - ListStart => self.handler.handle_list_start(&mut self.writer)?, - ListEnd => self.handler.handle_list_end(&mut self.writer)?, - AffKeywords => self.handler.handle_aff_keywords(&mut self.writer)?, - Call => self.handler.handle_call(&mut self.writer)?, - Clock => self.handler.handle_clock(&mut self.writer)?, - Comment(c) => self.handler.handle_comment(&mut self.writer, c)?, - TableStart => self.handler.handle_table_start(&mut self.writer)?, - TableEnd => self.handler.handle_table_end(&mut self.writer)?, - TableCell => self.handler.handle_table_cell(&mut self.writer)?, - LatexEnv => self.handler.handle_latex_env(&mut self.writer)?, - FnDef { label, contents } => { - self.handler - .handle_fn_def(&mut self.writer, label, contents)? - } - Keyword { key, value } => { - self.handler.handle_keyword(&mut self.writer, key, value)? - } - Rule => self.handler.handle_rule(&mut self.writer)?, - Cookie(cookie) => self.handler.handle_cookie(&mut self.writer, cookie)?, - FnRef(fnref) => self.handler.handle_fn_ref(&mut self.writer, fnref)?, - InlineCall(inlinecall) => self - .handler - .handle_inline_call(&mut self.writer, inlinecall)?, - InlineSrc(inlinesrc) => self - .handler - .handle_inline_src(&mut self.writer, inlinesrc)?, - Link(link) => self.handler.handle_link(&mut self.writer, link)?, - Macros(macros) => self.handler.handle_macros(&mut self.writer, macros)?, - RadioTarget(radiotarget) => self - .handler - .handle_radio_target(&mut self.writer, radiotarget)?, - Snippet(snippet) => self.handler.handle_snippet(&mut self.writer, snippet)?, - Target(target) => self.handler.handle_target(&mut self.writer, target)?, - StartBold => self.handler.handle_start_bold(&mut self.writer)?, - EndBold => self.handler.handle_end_bold(&mut self.writer)?, - StartItalic => self.handler.handle_start_italic(&mut self.writer)?, - EndItalic => self.handler.handle_end_italic(&mut self.writer)?, - StartStrike => self.handler.handle_start_strike(&mut self.writer)?, - EndStrike => self.handler.handle_end_strike(&mut self.writer)?, - StartUnderline => self.handler.handle_start_underline(&mut self.writer)?, - EndUnderline => self.handler.handle_end_underline(&mut self.writer)?, - Verbatim(contents) => self.handler.handle_verbatim(&mut self.writer, contents)?, - Code(contents) => self.handler.handle_code(&mut self.writer, contents)?, - Text(contents) => self.handler.handle_text(&mut self.writer, contents)?, + EndDynBlock => self.handler.handle_end_dyn_block(w)?, + StartList { is_ordered } => self.handler.handle_start_list(w, is_ordered)?, + EndList { is_ordered } => self.handler.handle_end_list(w, is_ordered)?, + StartListItem => self.handler.handle_start_list_item(w)?, + EndListItem => self.handler.handle_end_list_item(w)?, + AffKeywords => self.handler.handle_aff_keywords(w)?, + Call => self.handler.handle_call(w)?, + Clock => self.handler.handle_clock(w)?, + Comment(c) => self.handler.handle_comment(w, c)?, + TableStart => self.handler.handle_table_start(w)?, + TableEnd => self.handler.handle_table_end(w)?, + TableCell => self.handler.handle_table_cell(w)?, + LatexEnv => self.handler.handle_latex_env(w)?, + FnDef { label, contents } => self.handler.handle_fn_def(w, label, contents)?, + Keyword { key, value } => self.handler.handle_keyword(w, key, value)?, + Rule => self.handler.handle_rule(w)?, + Cookie(cookie) => self.handler.handle_cookie(w, cookie)?, + FnRef(fnref) => self.handler.handle_fn_ref(w, fnref)?, + InlineCall(inlinecall) => self.handler.handle_inline_call(w, inlinecall)?, + InlineSrc(inlinesrc) => self.handler.handle_inline_src(w, inlinesrc)?, + Link(link) => self.handler.handle_link(w, link)?, + Macros(macros) => self.handler.handle_macros(w, macros)?, + RadioTarget(radiotarget) => self.handler.handle_radio_target(w, radiotarget)?, + Snippet(snippet) => self.handler.handle_snippet(w, snippet)?, + Target(target) => self.handler.handle_target(w, target)?, + StartBold => self.handler.handle_start_bold(w)?, + EndBold => self.handler.handle_end_bold(w)?, + StartItalic => self.handler.handle_start_italic(w)?, + EndItalic => self.handler.handle_end_italic(w)?, + StartStrike => self.handler.handle_start_strike(w)?, + EndStrike => self.handler.handle_end_strike(w)?, + StartUnderline => self.handler.handle_start_underline(w)?, + EndUnderline => self.handler.handle_end_underline(w)?, + Verbatim(contents) => self.handler.handle_verbatim(w, contents)?, + Code(contents) => self.handler.handle_code(w, contents)?, + Text(contents) => self.handler.handle_text(w, contents)?, } } diff --git a/src/lib.rs b/src/lib.rs index 4440456..443c8a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,8 @@ #[macro_use] +extern crate lazy_static; +#[macro_use] extern crate jetscii; +extern crate regex; #[macro_use] mod utils; diff --git a/src/parser.rs b/src/parser.rs index 00e84bb..f2fcd96 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -5,19 +5,56 @@ use objects::*; #[cfg_attr(test, derive(PartialEq))] #[derive(Copy, Clone, Debug)] pub enum Container { - Headline { beg: usize, end: usize }, - Section { end: usize }, + Headline { + beg: usize, + end: usize, + }, + Section { + end: usize, + }, - Paragraph { end: usize, trailing: usize }, - CenterBlock { contents_end: usize, end: usize }, - QuoteBlock { contents_end: usize, end: usize }, - SpecialBlock { contents_end: usize, end: usize }, - DynBlock { contents_end: usize, end: usize }, + Paragraph { + end: usize, + trailing: usize, + }, + CenterBlock { + contents_end: usize, + end: usize, + }, + QuoteBlock { + contents_end: usize, + end: usize, + }, + SpecialBlock { + contents_end: usize, + end: usize, + }, + DynBlock { + contents_end: usize, + end: usize, + }, - Italic { end: usize }, - Strike { end: usize }, - Bold { end: usize }, - Underline { end: usize }, + List { + ident: usize, + is_ordered: bool, + end: usize, + }, + ListItem { + end: usize, + }, + + Italic { + end: usize, + }, + Strike { + end: usize, + }, + Bold { + end: usize, + }, + Underline { + end: usize, + }, } #[cfg_attr(test, derive(PartialEq))] @@ -68,8 +105,14 @@ pub enum Event<'a> { contents: &'a str, }, - ListStart, - ListEnd, + StartList { + is_ordered: bool, + }, + EndList { + is_ordered: bool, + }, + StartListItem, + EndListItem, AffKeywords, @@ -200,6 +243,15 @@ impl<'a> Parser<'a> { contents_end: contents_end + self.off, end: end + self.off, }), + Element::List { + ident, + is_ordered, + end, + } => self.stack.push(Container::List { + ident, + is_ordered, + end: end + self.off, + }), _ => (), } self.off += off; @@ -238,6 +290,15 @@ impl<'a> Parser<'a> { obj.into() } + fn next_list_item(&mut self, end: usize, ident: usize) -> Event<'a> { + let (beg, end) = List::parse_item(&self.text[self.off..end], ident); + self.stack.push(Container::ListItem { + end: self.off + end, + }); + self.off += beg; + Event::StartListItem + } + fn end(&mut self) -> Event<'a> { match self.stack.pop().unwrap() { Container::Paragraph { .. } => Event::EndParagraph, @@ -251,6 +312,38 @@ impl<'a> Parser<'a> { Container::QuoteBlock { .. } => Event::EndQuoteBlock, Container::SpecialBlock { .. } => Event::EndSpecialBlock, Container::DynBlock { .. } => Event::EndDynBlock, + Container::List { is_ordered, .. } => Event::EndList { is_ordered }, + Container::ListItem { .. } => Event::EndListItem, + } + } + + fn check_off(&self) { + use self::Container::*; + + if let Some(container) = self.stack.last() { + match *container { + Headline { end, .. } + | Section { end } + | List { end, .. } + | ListItem { end } + | Italic { end } + | Strike { end } + | Bold { end } + | Underline { end } => { + assert!(self.off <= end); + } + Paragraph { end, trailing } => { + assert!(self.off <= trailing); + assert!(self.off <= end); + } + CenterBlock { contents_end, end } + | QuoteBlock { contents_end, end } + | SpecialBlock { contents_end, end } + | DynBlock { contents_end, end } => { + assert!(self.off <= contents_end); + assert!(self.off <= end); + } + } } } } @@ -259,6 +352,9 @@ impl<'a> Iterator for Parser<'a> { type Item = Event<'a>; fn next(&mut self) -> Option> { + // + self.check_off(); + if self.stack.is_empty() { if self.off >= self.text.len() { None @@ -299,6 +395,21 @@ impl<'a> Iterator for Parser<'a> { self.next_ele(contents_end) } } + Container::List { end, ident, .. } => { + if self.off >= end { + self.end() + } else { + self.next_list_item(end, ident) + } + } + Container::ListItem { end } => { + if self.off >= end { + self.end() + } else { + // TODO: handle nested list + self.next_obj(end) + } + } Container::Section { end } => { if self.off >= end { self.end() @@ -370,6 +481,7 @@ impl<'a> From> for Event<'a> { Element::ExportBlock { args, contents } => Event::ExportBlock { args, contents }, Element::SrcBlock { args, contents } => Event::SrcBlock { args, contents }, Element::VerseBlock { args, contents } => Event::VerseBlock { args, contents }, + Element::List { is_ordered, .. } => Event::StartList { is_ordered }, } } }