diff --git a/STATUS.md b/STATUS.md index 0da5997..264b795 100644 --- a/STATUS.md +++ b/STATUS.md @@ -46,6 +46,10 @@ Check out https://orgmode.org/worg/dev/org-syntax.html for more information. - [x] Inline Babel Calls and Source Blocks - [ ] Line Breaks - [x] Links + - [x] Regular link + - [ ] Plain link + - [ ] Angle link + - [ ] Radio link - [x] Macros - [x] Targets and Radio Targets - [x] Statistics Cookies diff --git a/src/elements/dyn_block.rs b/src/elements/dyn_block.rs index 5d82461..8dcad41 100644 --- a/src/elements/dyn_block.rs +++ b/src/elements/dyn_block.rs @@ -1,5 +1,5 @@ use crate::lines::Lines; -use memchr::memchr2; +use memchr::{memchr, memchr2}; /// return (name, parameters, contents-begin, contents-end, end) #[inline] @@ -10,34 +10,32 @@ pub fn parse(src: &str) -> Option<(&str, Option<&str>, usize, usize, usize)> { return None; } - let bytes = src.as_bytes(); - - let args = memchr::memchr(b'\n', src.as_bytes()) - .map(|i| i + 1) - .unwrap_or_else(|| src.len()); - let name = memchr2(b' ', b'\n', &bytes[9..]) - .map(|i| i + 9) - .filter(|&i| { - src.as_bytes()[9..i] - .iter() - .all(|&c| c.is_ascii_alphabetic()) - })?; let mut lines = Lines::new(src); let (mut pre_cont_end, _, _) = lines.next()?; for (cont_end, end, line) in lines { if line.trim().eq_ignore_ascii_case("#+END:") { - return Some(( - &src[8..name].trim(), - if name == args { - None - } else { - Some(&src[name..args].trim()) - }, - args, - pre_cont_end, - end, - )); + let bytes = src.as_bytes(); + + let i = memchr2(b' ', b'\n', &bytes[9..]) + .map(|i| i + 9) + .filter(|&i| bytes[9..i].iter().all(|&c| c.is_ascii_alphabetic()))?; + let name = &src[8..i].trim(); + + return Some(if bytes[i] == b'\n' { + (name, None, i, pre_cont_end, end) + } else { + let cont_beg = memchr(b'\n', bytes) + .map(|i| i + 1) + .unwrap_or_else(|| src.len()); + ( + name, + Some(&src[i..cont_beg].trim()), + cont_beg, + pre_cont_end, + end, + ) + }); } pre_cont_end = cont_end; } diff --git a/src/elements/fn_def.rs b/src/elements/fn_def.rs index c490503..460598e 100644 --- a/src/elements/fn_def.rs +++ b/src/elements/fn_def.rs @@ -23,32 +23,32 @@ mod tests { use super::parse; assert_eq!( - parse("[fn:1] https://orgmode.org").unwrap(), - ( + parse("[fn:1] https://orgmode.org"), + Some(( "1", " https://orgmode.org", "[fn:1] https://orgmode.org".len() - ) + )) ); assert_eq!( - parse("[fn:word_1] https://orgmode.org").unwrap(), - ( + parse("[fn:word_1] https://orgmode.org"), + Some(( "word_1", " https://orgmode.org", "[fn:word_1] https://orgmode.org".len() - ) + )) ); assert_eq!( - parse("[fn:WORD-1] https://orgmode.org").unwrap(), - ( + parse("[fn:WORD-1] https://orgmode.org"), + Some(( "WORD-1", " https://orgmode.org", "[fn:WORD-1] https://orgmode.org".len() - ) + )) ); - assert_eq!(parse("[fn:WORD]").unwrap(), ("WORD", "", "[fn:WORD]".len())); - assert!(parse("[fn:] https://orgmode.org").is_none()); - assert!(parse("[fn:wor d] https://orgmode.org").is_none()); - assert!(parse("[fn:WORD https://orgmode.org").is_none()); + assert_eq!(parse("[fn:WORD]"), Some(("WORD", "", "[fn:WORD]".len()))); + assert_eq!(parse("[fn:] https://orgmode.org"), None); + assert_eq!(parse("[fn:wor d] https://orgmode.org"), None); + assert_eq!(parse("[fn:WORD https://orgmode.org"), None); } } diff --git a/src/elements/keyword.rs b/src/elements/keyword.rs index dfbe9a6..178d387 100644 --- a/src/elements/keyword.rs +++ b/src/elements/keyword.rs @@ -101,8 +101,8 @@ mod tests { parse("#+KEY:VALUE\n"), Some((Key::Custom("KEY"), "VALUE", "#+KEY:VALUE\n".len())) ); - assert!(parse("#+KE Y: VALUE").is_none()); - assert!(parse("#+ KEY: VALUE").is_none()); + assert_eq!(parse("#+KE Y: VALUE"), None); + assert_eq!(parse("#+ KEY: VALUE"), None); assert_eq!( parse("#+RESULTS:"), diff --git a/src/elements/list.rs b/src/elements/list.rs index 8bb9965..2fd5b80 100644 --- a/src/elements/list.rs +++ b/src/elements/list.rs @@ -77,7 +77,7 @@ pub fn parse(src: &str, ident: usize) -> (&str, usize, usize, usize, bool) { } } - let line_ident = self::ident(line); + let line_ident = count_ident(line); if line_ident < ident { return (bullet, beg, pre_cont_end, pre_end, false); @@ -99,7 +99,7 @@ pub fn parse(src: &str, ident: usize) -> (&str, usize, usize, usize, bool) { } #[inline] -fn ident(src: &str) -> usize { +fn count_ident(src: &str) -> usize { src.as_bytes() .iter() .position(|&c| c != b' ' && c != b'\t') diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 94e778a..d1e1ea3 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -81,7 +81,7 @@ pub enum Element<'a> { // return (element, off, next element, next offset) // the end of first element is relative to the offset // next offset is relative to the end of the first element -pub fn parse<'a>(src: &'a str) -> (Option>, usize, Option<(Element<'a>, usize)>) { +pub fn parse(src: &str) -> (Option>, usize, Option<(Element<'_>, usize)>) { // skip empty lines let mut pos = match src.chars().position(|c| c != '\n') { Some(pos) => pos, diff --git a/src/headline.rs b/src/headline.rs index 8c5e1c0..e391895 100644 --- a/src/headline.rs +++ b/src/headline.rs @@ -76,6 +76,7 @@ impl<'a> Headline<'a> { pub fn parse(src: &'a str) -> (Headline<'a>, usize, usize) { let level = memchr2(b'\n', b' ', src.as_bytes()).unwrap_or_else(|| src.len()); + debug_assert!(level > 0); debug_assert!(src.as_bytes()[0..level].iter().all(|&c| c == b'*')); let (eol, end) = memchr::memchr(b'\n', src.as_bytes()) diff --git a/src/objects/cookie.rs b/src/objects/cookie.rs index 53cac3a..4f7881b 100644 --- a/src/objects/cookie.rs +++ b/src/objects/cookie.rs @@ -32,20 +32,20 @@ mod tests { use super::parse; use super::Cookie::*; - assert_eq!(parse("[1/10]").unwrap(), (Slash("1", "10"), "[1/10]".len())); + assert_eq!(parse("[1/10]"), Some((Slash("1", "10"), "[1/10]".len()))); assert_eq!( - parse("[1/1000]").unwrap(), - (Slash("1", "1000"), "[1/1000]".len()) + parse("[1/1000]"), + Some((Slash("1", "1000"), "[1/1000]".len())) ); - assert_eq!(parse("[10%]").unwrap(), (Percent("10"), "[10%]".len())); - assert_eq!(parse("[%]").unwrap(), (Percent(""), "[%]".len())); - assert_eq!(parse("[/]").unwrap(), (Slash("", ""), "[/]".len())); - assert_eq!(parse("[100/]").unwrap(), (Slash("100", ""), "[100/]".len())); - assert_eq!(parse("[/100]").unwrap(), (Slash("", "100"), "[/100]".len())); + assert_eq!(parse("[10%]"), Some((Percent("10"), "[10%]".len()))); + assert_eq!(parse("[%]"), Some((Percent(""), "[%]".len()))); + assert_eq!(parse("[/]"), Some((Slash("", ""), "[/]".len()))); + assert_eq!(parse("[100/]"), Some((Slash("100", ""), "[100/]".len()))); + assert_eq!(parse("[/100]"), Some((Slash("", "100"), "[/100]".len()))); - assert!(parse("[10% ]").is_none(),); - assert!(parse("[1//100]").is_none(),); - assert!(parse("[1\\100]").is_none(),); - assert!(parse("[10%%]").is_none(),); + assert_eq!(parse("[10% ]"), None); + assert_eq!(parse("[1//100]"), None); + assert_eq!(parse("[1\\100]"), None); + assert_eq!(parse("[10%%]"), None); } } diff --git a/src/objects/emphasis.rs b/src/objects/emphasis.rs index d7f52c8..4df7f5f 100644 --- a/src/objects/emphasis.rs +++ b/src/objects/emphasis.rs @@ -1,3 +1,4 @@ +use bytecount::count; use memchr::memchr; #[inline] @@ -13,14 +14,13 @@ pub fn parse(src: &str, marker: u8) -> Option { let end = memchr(marker, &bytes[1..]) .map(|i| i + 1) - .filter(|&i| bytes[1..i].iter().filter(|&&c| c == b'\n').count() < 2)?; + .filter(|&i| count(&bytes[1..i], b'\n') < 2)?; if bytes[end - 1].is_ascii_whitespace() { return None; } - if end < src.len() - 1 { - let post = bytes[end + 1]; + if let Some(&post) = bytes.get(end + 1) { if post == b' ' || post == b'-' || post == b'.' @@ -48,12 +48,12 @@ mod tests { fn parse() { use super::parse; - assert_eq!(parse("*bold*", b'*').unwrap(), "*bold".len()); - assert_eq!(parse("*bo\nld*", b'*').unwrap(), "*bo\nld".len()); - assert!(parse("*bold*a", b'*').is_none()); - assert!(parse("*bold*", b'/').is_none()); - assert!(parse("*bold *", b'*').is_none()); - assert!(parse("* bold*", b'*').is_none()); - assert!(parse("*b\nol\nd*", b'*').is_none()); + assert_eq!(parse("*bold*", b'*'), Some("*bold".len())); + assert_eq!(parse("*bo\nld*", b'*'), Some("*bo\nld".len())); + assert_eq!(parse("*bold*a", b'*'), None); + assert_eq!(parse("*bold*", b'/'), None); + assert_eq!(parse("*bold *", b'*'), None); + assert_eq!(parse("* bold*", b'*'), None); + assert_eq!(parse("*b\nol\nd*", b'*'), None); } } diff --git a/src/objects/fn_ref.rs b/src/objects/fn_ref.rs index 733801e..962812f 100644 --- a/src/objects/fn_ref.rs +++ b/src/objects/fn_ref.rs @@ -55,19 +55,16 @@ mod tests { fn parse() { use super::parse; - assert_eq!(parse("[fn:1]").unwrap(), (Some("1"), None, "[fn:1]".len())); + assert_eq!(parse("[fn:1]"), Some((Some("1"), None, "[fn:1]".len()))); assert_eq!( - parse("[fn:1:2]").unwrap(), - (Some("1"), Some("2"), "[fn:1:2]".len()) + parse("[fn:1:2]"), + Some((Some("1"), Some("2"), "[fn:1:2]".len())) ); + assert_eq!(parse("[fn::2]"), Some((None, Some("2"), "[fn::2]".len()))); assert_eq!( - parse("[fn::2]").unwrap(), - (None, Some("2"), "[fn::2]".len()) + parse("[fn::[]]"), + Some((None, Some("[]"), "[fn::[]]".len())) ); - assert_eq!( - parse("[fn::[]]").unwrap(), - (None, Some("[]"), "[fn::[]]".len()) - ); - assert!(parse("[fn::[]").is_none()); + assert_eq!(parse("[fn::[]"), None); } } diff --git a/src/objects/inline_call.rs b/src/objects/inline_call.rs index cc07a08..37db232 100644 --- a/src/objects/inline_call.rs +++ b/src/objects/inline_call.rs @@ -51,38 +51,38 @@ mod tests { use super::parse; assert_eq!( - parse("call_square(4)").unwrap(), - ("square", "4", None, None, "call_square(4)".len()) + parse("call_square(4)"), + Some(("square", "4", None, None, "call_square(4)".len())) ); assert_eq!( - parse("call_square[:results output](4)").unwrap(), - ( + parse("call_square[:results output](4)"), + Some(( "square", "4", Some(":results output"), None, "call_square[:results output](4)".len() - ) + )) ); assert_eq!( - parse("call_square(4)[:results html]").unwrap(), - ( + parse("call_square(4)[:results html]"), + Some(( "square", "4", None, Some(":results html"), "call_square(4)[:results html]".len() - ) + )) ); assert_eq!( - parse("call_square[:results output](4)[:results html]").unwrap(), - ( + parse("call_square[:results output](4)[:results html]"), + Some(( "square", "4", Some(":results output"), Some(":results html"), "call_square[:results output](4)[:results html]".len() - ) + )) ); } } diff --git a/src/objects/inline_src.rs b/src/objects/inline_src.rs index 38a97ce..1af37f1 100644 --- a/src/objects/inline_src.rs +++ b/src/objects/inline_src.rs @@ -35,20 +35,20 @@ mod tests { use super::parse; assert_eq!( - parse("src_C{int a = 0;}").unwrap(), - ("C", None, "int a = 0;", "src_C{int a = 0;}".len()) + parse("src_C{int a = 0;}"), + Some(("C", None, "int a = 0;", "src_C{int a = 0;}".len())) ); assert_eq!( - parse("src_xml[:exports code]{text}").unwrap(), - ( + parse("src_xml[:exports code]{text}"), + Some(( "xml", Some(":exports code"), "text", "src_xml[:exports code]{text}".len() - ) + )) ); - assert!(parse("src_xml[:exports code]{text").is_none()); - assert!(parse("src_[:exports code]{text}").is_none()); - assert!(parse("src_xml[:exports code]").is_none()); + assert_eq!(parse("src_xml[:exports code]{text"), None); + assert_eq!(parse("src_[:exports code]{text}"), None); + assert_eq!(parse("src_xml[:exports code]"), None); } } diff --git a/src/objects/link.rs b/src/objects/link.rs index ead534d..d40512a 100644 --- a/src/objects/link.rs +++ b/src/objects/link.rs @@ -32,11 +32,11 @@ mod tests { fn parse() { use super::parse; - assert_eq!(parse("[[#id]]").unwrap(), ("#id", None, "[[#id]]".len())); + assert_eq!(parse("[[#id]]"), Some(("#id", None, "[[#id]]".len()))); assert_eq!( - parse("[[#id][desc]]").unwrap(), - ("#id", Some("desc"), "[[#id][desc]]".len()) + parse("[[#id][desc]]"), + Some(("#id", Some("desc"), "[[#id][desc]]".len())) ); - assert!(parse("[[#id][desc]").is_none()); + assert_eq!(parse("[[#id][desc]"), None); } } diff --git a/src/objects/mod.rs b/src/objects/mod.rs index 9eb0a6e..fe6d25e 100644 --- a/src/objects/mod.rs +++ b/src/objects/mod.rs @@ -68,20 +68,25 @@ pub enum Object<'a> { Text(&'a str), } -pub fn parse<'a>(src: &'a str) -> (Object<'a>, usize, Option<(Object<'a>, usize)>) { +pub fn parse(src: &str) -> (Object<'_>, usize, Option<(Object<'_>, usize)>) { let bytes = src.as_bytes(); - - if src.len() <= 2 { - return (Object::Text(src), src.len(), None); - } - let bs = bytes!(b'@', b' ', b'"', b'(', b'\n', b'{', b'<', b'['); let mut pos = 0; - loop { + while let Some(off) = if pos == 0 { + Some(0) + } else { + bs.find(&bytes[pos..]) + } { + pos += off; + + if src.len() - pos < 3 { + return (Object::Text(src), src.len(), None); + } + macro_rules! brk { ($obj:expr, $off:expr, $pos:expr) => { - break if $pos == 0 { + return if $pos == 0 { ($obj, $off, None) } else { (Object::Text(&src[0..$pos]), $pos, Some(($obj, $off))) @@ -141,19 +146,13 @@ pub fn parse<'a>(src: &'a str) -> (Object<'a>, usize, Option<(Object<'a>, usize) } } - if let Some(off) = bs - .find(&bytes[pos + 1..]) - .map(|i| i + pos + 1) - .filter(|&i| i < src.len() - 3) - { - pos = off; - } else { - break (Object::Text(src), src.len(), None); - } + pos += 1; } + + (Object::Text(src), src.len(), None) } -fn parse_text_markup<'a>(src: &'a str) -> Option<(Object<'a>, usize)> { +fn parse_text_markup(src: &str) -> Option<(Object<'_>, usize)> { match src.as_bytes()[0] { b'*' => emphasis::parse(src, b'*').map(|end| (Object::Bold { end }, 1)), b'+' => emphasis::parse(src, b'+').map(|end| (Object::Strike { end }, 1)), diff --git a/src/objects/radio_target.rs b/src/objects/radio_target.rs index 4a78eb4..df2aac2 100644 --- a/src/objects/radio_target.rs +++ b/src/objects/radio_target.rs @@ -7,13 +7,14 @@ pub fn parse(src: &str) -> Option<(&str, usize)> { expect!(src, 3, |c| c != b' ')?; + let bytes = src.as_bytes(); let end = Substring::new(">>>").find(src).filter(|&i| { - src.as_bytes()[3..i] + bytes[3..i] .iter() .all(|&c| c != b'<' && c != b'\n' && c != b'>') })?; - if src.as_bytes()[end - 1] == b' ' { + if bytes[end - 1] == b' ' { return None; } @@ -27,12 +28,12 @@ mod tests { use super::parse; assert_eq!( - parse("<<>>").unwrap(), - ("target", "<<>>".len()) + parse("<<>>"), + Some(("target", "<<>>".len())) ); assert_eq!( - parse("<<>>").unwrap(), - ("tar get", "<<>>".len()) + parse("<<>>"), + Some(("tar get", "<<>>".len())) ); assert_eq!(parse("<<>>"), None); assert_eq!(parse("<<< target>>>"), None); diff --git a/src/objects/snippet.rs b/src/objects/snippet.rs index 7228be4..8991d06 100644 --- a/src/objects/snippet.rs +++ b/src/objects/snippet.rs @@ -27,20 +27,20 @@ mod tests { use super::parse; assert_eq!( - parse("@@html:@@").unwrap(), - ("html", "", "@@html:@@".len()) + parse("@@html:@@"), + Some(("html", "", "@@html:@@".len())) ); assert_eq!( - parse("@@latex:any arbitrary LaTeX code@@").unwrap(), - ( + parse("@@latex:any arbitrary LaTeX code@@"), + Some(( "latex", "any arbitrary LaTeX code", "@@latex:any arbitrary LaTeX code@@".len() - ) + )) ); - assert_eq!(parse("@@html:@@").unwrap(), ("html", "", "@@html:@@".len())); - assert!(parse("@@html:@").is_none()); - assert!(parse("@@html@@").is_none()); - assert!(parse("@@:@@").is_none()); + assert_eq!(parse("@@html:@@"), Some(("html", "", "@@html:@@".len()))); + assert_eq!(parse("@@html:@"), None); + assert_eq!(parse("@@html@@"), None); + assert_eq!(parse("@@:@@"), None); } } diff --git a/src/objects/target.rs b/src/objects/target.rs index 50a6d23..ccf685f 100644 --- a/src/objects/target.rs +++ b/src/objects/target.rs @@ -25,11 +25,8 @@ mod tests { fn parse() { use super::parse; - assert_eq!(parse("<>").unwrap(), ("target", "<>".len())); - assert_eq!( - parse("<>").unwrap(), - ("tar get", "<>".len()) - ); + assert_eq!(parse("<>"), Some(("target", "<>".len()))); + assert_eq!(parse("<>"), Some(("tar get", "<>".len()))); assert_eq!(parse("<>"), None); assert_eq!(parse("<< target>>"), None); assert_eq!(parse("<>"), None); diff --git a/src/parser.rs b/src/parser.rs index 7ea55bd..a65c576 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -239,8 +239,7 @@ impl<'a> Parser<'a> { } fn next_hdl(&mut self) -> Event<'a> { - let tail = &self.text[self.off..]; - let (hdl, off, end) = Headline::parse(tail); + let (hdl, off, end) = Headline::parse(&self.text[self.off..]); debug_assert!(end <= self.text[self.off..].len()); self.stack.push(Container::Headline { beg: self.off + off, @@ -467,6 +466,7 @@ impl<'a> Iterator for Parser<'a> { | Container::SplBlock { cont_end, end, .. } | Container::ListItem { cont_end, end } => { debug_assert!(self.off <= cont_end); + debug_assert!(self.off <= end); if self.off >= cont_end { self.off = end; self.end() @@ -495,6 +495,7 @@ impl<'a> Iterator for Parser<'a> { | Container::Underline { cont_end, end } | Container::Italic { cont_end, end } | Container::Strike { cont_end, end } => { + debug_assert!(self.off <= cont_end); debug_assert!(self.off <= end); if self.off >= cont_end { self.off = end;