feat: list parsing
This commit is contained in:
parent
75362bd2a8
commit
ad9f29bcb9
|
@ -5,3 +5,5 @@ authors = ["PoiScript <poiscript@gmail.com>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
jetscii = "0.4.3"
|
jetscii = "0.4.3"
|
||||||
|
lazy_static = "1.2.0"
|
||||||
|
regex = "1"
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Block;
|
pub struct Block;
|
||||||
|
@ -12,9 +14,9 @@ impl Block {
|
||||||
let args = eol!(src);
|
let args = eol!(src);
|
||||||
let name = until_while!(src, 8, |c| c == b' ' || c == b'\n', |c: u8| c
|
let name = until_while!(src, 8, |c| c == b' ' || c == b'\n', |c: u8| c
|
||||||
.is_ascii_alphabetic())?;
|
.is_ascii_alphabetic())?;
|
||||||
// TODO: ignore case match
|
let end_re = format!(r"(?im)^[ \t]*#\+END_{}[ \t]*$", &src[8..name]);
|
||||||
let content = src.find(&format!("\n#+END_{}", &src[8..name]))?;
|
let end_re = Regex::new(&end_re).unwrap();
|
||||||
let end = eol!(src, content + 1);
|
let (content, end) = end_re.find(src).map(|m| (m.start(), m.end()))?;
|
||||||
|
|
||||||
Some((
|
Some((
|
||||||
&src[8..name],
|
&src[8..name],
|
||||||
|
@ -24,13 +26,34 @@ impl Block {
|
||||||
Some(&src[name..args])
|
Some(&src[name..args])
|
||||||
},
|
},
|
||||||
args,
|
args,
|
||||||
content + 1,
|
content,
|
||||||
end + 1,
|
// including the eol character
|
||||||
|
if end < src.len() && src.as_bytes()[end] == b'\n' {
|
||||||
|
end + 1
|
||||||
|
} else {
|
||||||
|
end
|
||||||
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse() {
|
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
|
||||||
}
|
}
|
||||||
|
|
226
src/elements/list.rs
Normal file
226
src/elements/list.rs
Normal file
|
@ -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)
|
||||||
|
);
|
||||||
|
}
|
|
@ -72,6 +72,11 @@ pub enum Element<'a> {
|
||||||
},
|
},
|
||||||
Rule,
|
Rule,
|
||||||
Comment(&'a str),
|
Comment(&'a str),
|
||||||
|
List {
|
||||||
|
ident: usize,
|
||||||
|
is_ordered: bool,
|
||||||
|
end: usize,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Element<'a> {
|
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' {
|
if bytes[pos] == b'\n' {
|
||||||
return (start, Some(Element::Paragraph { end, trailing: pos }), None);
|
return (start, Some(Element::Paragraph { end, trailing: pos }), None);
|
||||||
}
|
}
|
||||||
|
@ -134,7 +156,8 @@ impl<'a> Element<'a> {
|
||||||
|
|
||||||
// Rule
|
// Rule
|
||||||
if bytes[pos] == b'-' {
|
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);
|
ret!(Element::Rule, off);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -227,7 +250,7 @@ impl<'a> Element<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comment
|
// 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..]
|
let eol = src[pos..]
|
||||||
.find('\n')
|
.find('\n')
|
||||||
.map(|i| i + pos + 1)
|
.map(|i| i + pos + 1)
|
||||||
|
|
|
@ -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))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Rule;
|
pub struct Rule;
|
||||||
|
|
||||||
impl Rule {
|
impl Rule {
|
||||||
pub fn parse(src: &str) -> Option<usize> {
|
pub fn parse(src: &str) -> usize {
|
||||||
let end = eol!(src);
|
RULE_REGEX.find(src).map(|m| m.end()).unwrap_or(0)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse() {
|
fn parse() {
|
||||||
assert_eq!(Rule::parse("-----").unwrap(), "-----".len());
|
assert_eq!(Rule::parse("-----"), "-----".len());
|
||||||
assert_eq!(Rule::parse("--------").unwrap(), "--------".len());
|
assert_eq!(Rule::parse("--------"), "--------".len());
|
||||||
assert_eq!(Rule::parse(" -----").unwrap(), " -----".len());
|
assert_eq!(Rule::parse(" -----"), " -----".len());
|
||||||
assert_eq!(Rule::parse("\t\t-----").unwrap(), "\t\t-----".len());
|
assert_eq!(Rule::parse("\t\t-----"), "\t\t-----".len());
|
||||||
|
assert_eq!(Rule::parse("\t\t-----\n"), "\t\t-----\n".len());
|
||||||
assert!(Rule::parse("").is_none());
|
assert_eq!(Rule::parse("\t\t----- \n"), "\t\t----- \n".len());
|
||||||
assert!(Rule::parse("----").is_none());
|
assert_eq!(Rule::parse(""), 0);
|
||||||
assert!(Rule::parse(" ----").is_none());
|
assert_eq!(Rule::parse("----"), 0);
|
||||||
assert!(Rule::parse(" 0----").is_none());
|
assert_eq!(Rule::parse(" ----"), 0);
|
||||||
assert!(Rule::parse("0 ----").is_none());
|
assert_eq!(Rule::parse(" 0----"), 0);
|
||||||
assert!(Rule::parse("0------").is_none());
|
assert_eq!(Rule::parse("0 ----"), 0);
|
||||||
assert!(Rule::parse("----0----").is_none());
|
assert_eq!(Rule::parse("0------"), 0);
|
||||||
assert!(Rule::parse("\t\t----").is_none());
|
assert_eq!(Rule::parse("----0----"), 0);
|
||||||
assert!(Rule::parse("------0").is_none());
|
assert_eq!(Rule::parse("\t\t----"), 0);
|
||||||
assert!(Rule::parse("----- 0").is_none());
|
assert_eq!(Rule::parse("------0"), 0);
|
||||||
|
assert_eq!(Rule::parse("----- 0"), 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,11 +85,25 @@ impl<W: Write> Handler<W> for HtmlHandler {
|
||||||
fn handle_end_dyn_block(&mut self, w: &mut W) -> Result<()> {
|
fn handle_end_dyn_block(&mut self, w: &mut W) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn handle_list_start(&mut self, w: &mut W) -> Result<()> {
|
fn handle_start_list(&mut self, w: &mut W, is_ordered: bool) -> Result<()> {
|
||||||
Ok(())
|
if is_ordered {
|
||||||
|
write!(w, "<ol>")
|
||||||
|
} else {
|
||||||
|
write!(w, "<ul>")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn handle_list_end(&mut self, w: &mut W) -> Result<()> {
|
fn handle_end_list(&mut self, w: &mut W, is_ordered: bool) -> Result<()> {
|
||||||
Ok(())
|
if is_ordered {
|
||||||
|
write!(w, "</ol>")
|
||||||
|
} else {
|
||||||
|
write!(w, "</ul>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn handle_start_list_item(&mut self, w: &mut W) -> Result<()> {
|
||||||
|
write!(w, "<li>")
|
||||||
|
}
|
||||||
|
fn handle_end_list_item(&mut self, w: &mut W) -> Result<()> {
|
||||||
|
write!(w, "</li>")
|
||||||
}
|
}
|
||||||
fn handle_aff_keywords(&mut self, w: &mut W) -> Result<()> {
|
fn handle_aff_keywords(&mut self, w: &mut W) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -34,8 +34,10 @@ pub trait Handler<W: Write> {
|
||||||
fn handle_verse_block(&mut self, w: &mut W, contents: &str, args: Option<&str>) -> Result<()>;
|
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_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_end_dyn_block(&mut self, w: &mut W) -> Result<()>;
|
||||||
fn handle_list_start(&mut self, w: &mut W) -> Result<()>;
|
fn handle_start_list(&mut self, w: &mut W, is_ordered: bool) -> Result<()>;
|
||||||
fn handle_list_end(&mut self, w: &mut W) -> 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_aff_keywords(&mut self, w: &mut W) -> Result<()>;
|
||||||
fn handle_call(&mut self, w: &mut W) -> Result<()>;
|
fn handle_call(&mut self, w: &mut W) -> Result<()>;
|
||||||
fn handle_clock(&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<W>> Render<'a, W, H> {
|
||||||
pub fn render(&mut self) -> Result<()> {
|
pub fn render(&mut self) -> Result<()> {
|
||||||
use parser::Event::*;
|
use parser::Event::*;
|
||||||
|
|
||||||
|
let w = &mut self.writer;
|
||||||
|
|
||||||
for event in &mut self.parser {
|
for event in &mut self.parser {
|
||||||
match event {
|
match event {
|
||||||
StartHeadline(hdl) => self.handler.handle_start_headline(&mut self.writer, hdl)?,
|
StartHeadline(hdl) => self.handler.handle_start_headline(w, hdl)?,
|
||||||
EndHeadline => self.handler.handle_end_headline(&mut self.writer)?,
|
EndHeadline => self.handler.handle_end_headline(w)?,
|
||||||
StartSection => self.handler.handle_start_section(&mut self.writer)?,
|
StartSection => self.handler.handle_start_section(w)?,
|
||||||
EndSection => self.handler.handle_end_section(&mut self.writer)?,
|
EndSection => self.handler.handle_end_section(w)?,
|
||||||
StartParagraph => self.handler.handle_start_paragraph(&mut self.writer)?,
|
StartParagraph => self.handler.handle_start_paragraph(w)?,
|
||||||
EndParagraph => self.handler.handle_end_paragraph(&mut self.writer)?,
|
EndParagraph => self.handler.handle_end_paragraph(w)?,
|
||||||
StartCenterBlock => self.handler.handle_start_center_block(&mut self.writer)?,
|
StartCenterBlock => self.handler.handle_start_center_block(w)?,
|
||||||
EndCenterBlock => self.handler.handle_end_center_block(&mut self.writer)?,
|
EndCenterBlock => self.handler.handle_end_center_block(w)?,
|
||||||
StartQuoteBlock => self.handler.handle_start_quote_block(&mut self.writer)?,
|
StartQuoteBlock => self.handler.handle_start_quote_block(w)?,
|
||||||
EndQuoteBlock => self.handler.handle_end_quote_block(&mut self.writer)?,
|
EndQuoteBlock => self.handler.handle_end_quote_block(w)?,
|
||||||
StartSpecialBlock { name, args } => {
|
StartSpecialBlock { name, args } => {
|
||||||
self.handler
|
self.handler.handle_start_special_block(w, name, args)?
|
||||||
.handle_start_special_block(&mut self.writer, name, args)?
|
|
||||||
}
|
}
|
||||||
EndSpecialBlock => self.handler.handle_end_special_block(&mut self.writer)?,
|
EndSpecialBlock => self.handler.handle_end_special_block(w)?,
|
||||||
CommentBlock { contents, args } => {
|
CommentBlock { contents, args } => {
|
||||||
self.handler
|
self.handler.handle_comment_block(w, contents, args)?
|
||||||
.handle_comment_block(&mut self.writer, contents, args)?
|
|
||||||
}
|
}
|
||||||
ExampleBlock { contents, args } => {
|
ExampleBlock { contents, args } => {
|
||||||
self.handler
|
self.handler.handle_example_block(w, contents, args)?
|
||||||
.handle_example_block(&mut self.writer, contents, args)?
|
|
||||||
}
|
}
|
||||||
ExportBlock { contents, args } => {
|
ExportBlock { contents, args } => {
|
||||||
self.handler
|
self.handler.handle_export_block(w, contents, args)?
|
||||||
.handle_export_block(&mut self.writer, contents, args)?
|
|
||||||
}
|
|
||||||
SrcBlock { contents, args } => {
|
|
||||||
self.handler
|
|
||||||
.handle_src_block(&mut self.writer, contents, args)?
|
|
||||||
}
|
}
|
||||||
|
SrcBlock { contents, args } => self.handler.handle_src_block(w, contents, args)?,
|
||||||
VerseBlock { contents, args } => {
|
VerseBlock { contents, args } => {
|
||||||
self.handler
|
self.handler.handle_verse_block(w, contents, args)?
|
||||||
.handle_verse_block(&mut self.writer, contents, args)?
|
|
||||||
}
|
}
|
||||||
StartDynBlock { name, args } => {
|
StartDynBlock { name, args } => {
|
||||||
self.handler
|
self.handler.handle_start_dyn_block(w, name, args)?
|
||||||
.handle_start_dyn_block(&mut self.writer, name, args)?
|
|
||||||
}
|
}
|
||||||
EndDynBlock => self.handler.handle_end_dyn_block(&mut self.writer)?,
|
EndDynBlock => self.handler.handle_end_dyn_block(w)?,
|
||||||
ListStart => self.handler.handle_list_start(&mut self.writer)?,
|
StartList { is_ordered } => self.handler.handle_start_list(w, is_ordered)?,
|
||||||
ListEnd => self.handler.handle_list_end(&mut self.writer)?,
|
EndList { is_ordered } => self.handler.handle_end_list(w, is_ordered)?,
|
||||||
AffKeywords => self.handler.handle_aff_keywords(&mut self.writer)?,
|
StartListItem => self.handler.handle_start_list_item(w)?,
|
||||||
Call => self.handler.handle_call(&mut self.writer)?,
|
EndListItem => self.handler.handle_end_list_item(w)?,
|
||||||
Clock => self.handler.handle_clock(&mut self.writer)?,
|
AffKeywords => self.handler.handle_aff_keywords(w)?,
|
||||||
Comment(c) => self.handler.handle_comment(&mut self.writer, c)?,
|
Call => self.handler.handle_call(w)?,
|
||||||
TableStart => self.handler.handle_table_start(&mut self.writer)?,
|
Clock => self.handler.handle_clock(w)?,
|
||||||
TableEnd => self.handler.handle_table_end(&mut self.writer)?,
|
Comment(c) => self.handler.handle_comment(w, c)?,
|
||||||
TableCell => self.handler.handle_table_cell(&mut self.writer)?,
|
TableStart => self.handler.handle_table_start(w)?,
|
||||||
LatexEnv => self.handler.handle_latex_env(&mut self.writer)?,
|
TableEnd => self.handler.handle_table_end(w)?,
|
||||||
FnDef { label, contents } => {
|
TableCell => self.handler.handle_table_cell(w)?,
|
||||||
self.handler
|
LatexEnv => self.handler.handle_latex_env(w)?,
|
||||||
.handle_fn_def(&mut self.writer, label, contents)?
|
FnDef { label, contents } => self.handler.handle_fn_def(w, label, contents)?,
|
||||||
}
|
Keyword { key, value } => self.handler.handle_keyword(w, key, value)?,
|
||||||
Keyword { key, value } => {
|
Rule => self.handler.handle_rule(w)?,
|
||||||
self.handler.handle_keyword(&mut self.writer, key, value)?
|
Cookie(cookie) => self.handler.handle_cookie(w, cookie)?,
|
||||||
}
|
FnRef(fnref) => self.handler.handle_fn_ref(w, fnref)?,
|
||||||
Rule => self.handler.handle_rule(&mut self.writer)?,
|
InlineCall(inlinecall) => self.handler.handle_inline_call(w, inlinecall)?,
|
||||||
Cookie(cookie) => self.handler.handle_cookie(&mut self.writer, cookie)?,
|
InlineSrc(inlinesrc) => self.handler.handle_inline_src(w, inlinesrc)?,
|
||||||
FnRef(fnref) => self.handler.handle_fn_ref(&mut self.writer, fnref)?,
|
Link(link) => self.handler.handle_link(w, link)?,
|
||||||
InlineCall(inlinecall) => self
|
Macros(macros) => self.handler.handle_macros(w, macros)?,
|
||||||
.handler
|
RadioTarget(radiotarget) => self.handler.handle_radio_target(w, radiotarget)?,
|
||||||
.handle_inline_call(&mut self.writer, inlinecall)?,
|
Snippet(snippet) => self.handler.handle_snippet(w, snippet)?,
|
||||||
InlineSrc(inlinesrc) => self
|
Target(target) => self.handler.handle_target(w, target)?,
|
||||||
.handler
|
StartBold => self.handler.handle_start_bold(w)?,
|
||||||
.handle_inline_src(&mut self.writer, inlinesrc)?,
|
EndBold => self.handler.handle_end_bold(w)?,
|
||||||
Link(link) => self.handler.handle_link(&mut self.writer, link)?,
|
StartItalic => self.handler.handle_start_italic(w)?,
|
||||||
Macros(macros) => self.handler.handle_macros(&mut self.writer, macros)?,
|
EndItalic => self.handler.handle_end_italic(w)?,
|
||||||
RadioTarget(radiotarget) => self
|
StartStrike => self.handler.handle_start_strike(w)?,
|
||||||
.handler
|
EndStrike => self.handler.handle_end_strike(w)?,
|
||||||
.handle_radio_target(&mut self.writer, radiotarget)?,
|
StartUnderline => self.handler.handle_start_underline(w)?,
|
||||||
Snippet(snippet) => self.handler.handle_snippet(&mut self.writer, snippet)?,
|
EndUnderline => self.handler.handle_end_underline(w)?,
|
||||||
Target(target) => self.handler.handle_target(&mut self.writer, target)?,
|
Verbatim(contents) => self.handler.handle_verbatim(w, contents)?,
|
||||||
StartBold => self.handler.handle_start_bold(&mut self.writer)?,
|
Code(contents) => self.handler.handle_code(w, contents)?,
|
||||||
EndBold => self.handler.handle_end_bold(&mut self.writer)?,
|
Text(contents) => self.handler.handle_text(w, contents)?,
|
||||||
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)?,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
#[macro_use]
|
||||||
extern crate jetscii;
|
extern crate jetscii;
|
||||||
|
extern crate regex;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
138
src/parser.rs
138
src/parser.rs
|
@ -5,19 +5,56 @@ use objects::*;
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum Container {
|
pub enum Container {
|
||||||
Headline { beg: usize, end: usize },
|
Headline {
|
||||||
Section { end: usize },
|
beg: usize,
|
||||||
|
end: usize,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
end: usize,
|
||||||
|
},
|
||||||
|
|
||||||
Paragraph { end: usize, trailing: usize },
|
Paragraph {
|
||||||
CenterBlock { contents_end: usize, end: usize },
|
end: usize,
|
||||||
QuoteBlock { contents_end: usize, end: usize },
|
trailing: usize,
|
||||||
SpecialBlock { contents_end: usize, end: usize },
|
},
|
||||||
DynBlock { contents_end: usize, end: 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 },
|
List {
|
||||||
Strike { end: usize },
|
ident: usize,
|
||||||
Bold { end: usize },
|
is_ordered: bool,
|
||||||
Underline { end: usize },
|
end: usize,
|
||||||
|
},
|
||||||
|
ListItem {
|
||||||
|
end: usize,
|
||||||
|
},
|
||||||
|
|
||||||
|
Italic {
|
||||||
|
end: usize,
|
||||||
|
},
|
||||||
|
Strike {
|
||||||
|
end: usize,
|
||||||
|
},
|
||||||
|
Bold {
|
||||||
|
end: usize,
|
||||||
|
},
|
||||||
|
Underline {
|
||||||
|
end: usize,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
|
@ -68,8 +105,14 @@ pub enum Event<'a> {
|
||||||
contents: &'a str,
|
contents: &'a str,
|
||||||
},
|
},
|
||||||
|
|
||||||
ListStart,
|
StartList {
|
||||||
ListEnd,
|
is_ordered: bool,
|
||||||
|
},
|
||||||
|
EndList {
|
||||||
|
is_ordered: bool,
|
||||||
|
},
|
||||||
|
StartListItem,
|
||||||
|
EndListItem,
|
||||||
|
|
||||||
AffKeywords,
|
AffKeywords,
|
||||||
|
|
||||||
|
@ -200,6 +243,15 @@ impl<'a> Parser<'a> {
|
||||||
contents_end: contents_end + self.off,
|
contents_end: contents_end + self.off,
|
||||||
end: 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;
|
self.off += off;
|
||||||
|
@ -238,6 +290,15 @@ impl<'a> Parser<'a> {
|
||||||
obj.into()
|
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> {
|
fn end(&mut self) -> Event<'a> {
|
||||||
match self.stack.pop().unwrap() {
|
match self.stack.pop().unwrap() {
|
||||||
Container::Paragraph { .. } => Event::EndParagraph,
|
Container::Paragraph { .. } => Event::EndParagraph,
|
||||||
|
@ -251,6 +312,38 @@ impl<'a> Parser<'a> {
|
||||||
Container::QuoteBlock { .. } => Event::EndQuoteBlock,
|
Container::QuoteBlock { .. } => Event::EndQuoteBlock,
|
||||||
Container::SpecialBlock { .. } => Event::EndSpecialBlock,
|
Container::SpecialBlock { .. } => Event::EndSpecialBlock,
|
||||||
Container::DynBlock { .. } => Event::EndDynBlock,
|
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>;
|
type Item = Event<'a>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Event<'a>> {
|
fn next(&mut self) -> Option<Event<'a>> {
|
||||||
|
//
|
||||||
|
self.check_off();
|
||||||
|
|
||||||
if self.stack.is_empty() {
|
if self.stack.is_empty() {
|
||||||
if self.off >= self.text.len() {
|
if self.off >= self.text.len() {
|
||||||
None
|
None
|
||||||
|
@ -299,6 +395,21 @@ impl<'a> Iterator for Parser<'a> {
|
||||||
self.next_ele(contents_end)
|
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 } => {
|
Container::Section { end } => {
|
||||||
if self.off >= end {
|
if self.off >= end {
|
||||||
self.end()
|
self.end()
|
||||||
|
@ -370,6 +481,7 @@ impl<'a> From<Element<'a>> for Event<'a> {
|
||||||
Element::ExportBlock { args, contents } => Event::ExportBlock { args, contents },
|
Element::ExportBlock { args, contents } => Event::ExportBlock { args, contents },
|
||||||
Element::SrcBlock { args, contents } => Event::SrcBlock { args, contents },
|
Element::SrcBlock { args, contents } => Event::SrcBlock { args, contents },
|
||||||
Element::VerseBlock { args, contents } => Event::VerseBlock { args, contents },
|
Element::VerseBlock { args, contents } => Event::VerseBlock { args, contents },
|
||||||
|
Element::List { is_ordered, .. } => Event::StartList { is_ordered },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue