refactor: cleanup parse function

This commit is contained in:
PoiScript 2019-02-08 21:34:58 +08:00
parent c1154a1853
commit c5b14256f0
25 changed files with 1299 additions and 1234 deletions

View file

@ -2,74 +2,50 @@ use memchr::{memchr, memchr2};
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub struct Cookie<'a> {
value: &'a str,
pub enum Cookie<'a> {
Percent(&'a str),
Slash(&'a str, &'a str),
}
impl<'a> Cookie<'a> {
pub fn parse(src: &'a str) -> Option<(Cookie<'a>, usize)> {
debug_assert!(src.starts_with('['));
#[inline]
pub fn parse(src: &str) -> Option<(Cookie<'_>, usize)> {
debug_assert!(src.starts_with('['));
let num1 = memchr2(b'%', b'/', src.as_bytes())
.filter(|&i| src.as_bytes()[1..i].iter().all(|c| c.is_ascii_digit()))?;
let bytes = src.as_bytes();
let num1 =
memchr2(b'%', b'/', bytes).filter(|&i| bytes[1..i].iter().all(|c| c.is_ascii_digit()))?;
if src.as_bytes()[num1] == b'%' && *src.as_bytes().get(num1 + 1)? == b']' {
Some((
Cookie {
value: &src[0..=num1 + 1],
},
num1 + 2,
))
} else {
let num2 = memchr(b']', src.as_bytes()).filter(|&i| {
src.as_bytes()[num1 + 1..i]
.iter()
.all(|c| c.is_ascii_digit())
})?;
if bytes[num1] == b'%' && *bytes.get(num1 + 1)? == b']' {
Some((Cookie::Percent(&src[1..num1]), num1 + 2))
} else {
let num2 = memchr(b']', bytes)
.filter(|&i| bytes[num1 + 1..i].iter().all(|c| c.is_ascii_digit()))?;
Some((
Cookie {
value: &src[0..=num2],
},
num2 + 1,
))
}
Some((Cookie::Slash(&src[1..num1], &src[num1 + 1..num2]), num2 + 1))
}
}
#[test]
fn parse() {
assert_eq!(
Cookie::parse("[1/10]").unwrap(),
(Cookie { value: "[1/10]" }, "[1/10]".len())
);
assert_eq!(
Cookie::parse("[1/1000]").unwrap(),
(Cookie { value: "[1/1000]" }, "[1/1000]".len())
);
assert_eq!(
Cookie::parse("[10%]").unwrap(),
(Cookie { value: "[10%]" }, "[10%]".len())
);
assert_eq!(
Cookie::parse("[%]").unwrap(),
(Cookie { value: "[%]" }, "[%]".len())
);
assert_eq!(
Cookie::parse("[/]").unwrap(),
(Cookie { value: "[/]" }, "[/]".len())
);
assert_eq!(
Cookie::parse("[100/]").unwrap(),
(Cookie { value: "[100/]" }, "[100/]".len())
);
assert_eq!(
Cookie::parse("[/100]").unwrap(),
(Cookie { value: "[/100]" }, "[/100]".len())
);
#[cfg(test)]
mod tests {
#[test]
fn parse() {
use super::parse;
use super::Cookie::*;
assert!(Cookie::parse("[10% ]").is_none(),);
assert!(Cookie::parse("[1//100]").is_none(),);
assert!(Cookie::parse("[1\\100]").is_none(),);
assert!(Cookie::parse("[10%%]").is_none(),);
assert_eq!(parse("[1/10]").unwrap(), (Slash("1", "10"), "[1/10]".len()));
assert_eq!(
parse("[1/1000]").unwrap(),
(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!(parse("[10% ]").is_none(),);
assert!(parse("[1//100]").is_none(),);
assert!(parse("[1\\100]").is_none(),);
assert!(parse("[10%%]").is_none(),);
}
}

View file

@ -1,44 +1,59 @@
use memchr::memchr;
pub struct Emphasis;
#[inline]
/// returns offset
pub fn parse(src: &str, marker: u8) -> Option<usize> {
debug_assert!(src.len() >= 3);
impl Emphasis {
// TODO: return usize instead of Option<usize>
pub fn parse(src: &str, marker: u8) -> Option<usize> {
expect!(src, 1, |c: u8| !c.is_ascii_whitespace())?;
let bytes = src.as_bytes();
let bytes = src.as_bytes();
let end = memchr(marker, &bytes[1..])
.map(|i| i + 1)
.filter(|&i| bytes[1..i].iter().filter(|&&c| c == b'\n').count() < 2)?;
if bytes[1].is_ascii_whitespace() {
return None;
}
expect!(src, end - 1, |c: u8| !c.is_ascii_whitespace())?;
let end = memchr(marker, &bytes[1..])
.map(|i| i + 1)
.filter(|&i| bytes[1..i].iter().filter(|&&c| c == b'\n').count() < 2)?;
if end < src.len() - 1 {
expect!(src, end + 1, |ch| ch == b' '
|| ch == b'-'
|| ch == b'.'
|| ch == b','
|| ch == b':'
|| ch == b'!'
|| ch == b'?'
|| ch == b'\''
|| ch == b'\n'
|| ch == b')'
|| ch == b'}')?;
if bytes[end - 1].is_ascii_whitespace() {
return None;
}
if end < src.len() - 1 {
let post = bytes[end + 1];
if post == b' '
|| post == b'-'
|| post == b'.'
|| post == b','
|| post == b':'
|| post == b'!'
|| post == b'?'
|| post == b'\''
|| post == b'\n'
|| post == b')'
|| post == b'}'
{
Some(end)
} else {
None
}
} else {
Some(end)
}
}
#[test]
fn parse() {
assert_eq!(Emphasis::parse("*bold*", b'*').unwrap(), "*bold".len());
assert_eq!(Emphasis::parse("*bo\nld*", b'*').unwrap(), "*bo\nld".len());
assert!(Emphasis::parse("*bold*a", b'*').is_none());
assert!(Emphasis::parse("*bold*", b'/').is_none());
assert!(Emphasis::parse("*bold *", b'*').is_none());
assert!(Emphasis::parse("* bold*", b'*').is_none());
assert!(Emphasis::parse("*b\nol\nd*", b'*').is_none());
#[cfg(test)]
mod tests {
#[test]
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());
}
}

View file

@ -1,106 +1,73 @@
use memchr::{memchr2, memchr2_iter};
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub struct FnRef<'a> {
label: Option<&'a str>,
definition: Option<&'a str>,
}
/// returns (footnote reference label, footnote reference definition, offset)
#[inline]
pub fn parse(src: &str) -> Option<(Option<&str>, Option<&str>, usize)> {
debug_assert!(src.starts_with("[fn:"));
impl<'a> FnRef<'a> {
pub fn parse(src: &'a str) -> Option<(FnRef<'a>, usize)> {
debug_assert!(src.starts_with("[fn:"));
let bytes = src.as_bytes();
let label = memchr2(b']', b':', &bytes[4..])
.map(|i| i + 4)
.filter(|&i| {
bytes[4..i]
.iter()
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
})?;
let bytes = src.as_bytes();
let label = memchr2(b']', b':', &bytes[4..])
.map(|i| i + 4)
.filter(|&i| {
bytes[4..i]
.iter()
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
if bytes[label] == b':' {
let mut pairs = 1;
let def = memchr2_iter(b'[', b']', &bytes[label..])
.map(|i| i + label)
.find(|&i| {
if bytes[i] == b'[' {
pairs += 1;
} else {
pairs -= 1;
}
pairs == 0
})?;
if bytes[label] == b':' {
let mut pairs = 1;
let def = memchr2_iter(b'[', b']', &bytes[label..])
.map(|i| i + label)
.find(|&i| {
if bytes[i] == b'[' {
pairs += 1;
} else {
pairs -= 1;
}
pairs == 0
})?;
Some((
FnRef {
label: if label == 4 {
None
} else {
Some(&src[4..label])
},
definition: Some(&src[label + 1..def]),
},
def + 1,
))
} else {
Some((
FnRef {
label: if label == 4 {
None
} else {
Some(&src[4..label])
},
definition: None,
},
label + 1,
))
}
Some((
if label == 4 {
None
} else {
Some(&src[4..label])
},
Some(&src[label + 1..def]),
def + 1,
))
} else {
Some((
if label == 4 {
None
} else {
Some(&src[4..label])
},
None,
label + 1,
))
}
}
#[test]
fn parse() {
assert_eq!(
FnRef::parse("[fn:1]").unwrap(),
(
FnRef {
label: Some("1"),
definition: None,
},
"[fn:1]".len()
)
);
assert_eq!(
FnRef::parse("[fn:1:2]").unwrap(),
(
FnRef {
label: Some("1"),
definition: Some("2"),
},
"[fn:1:2]".len()
)
);
assert_eq!(
FnRef::parse("[fn::2]").unwrap(),
(
FnRef {
label: None,
definition: Some("2"),
},
"[fn::2]".len()
)
);
assert_eq!(
FnRef::parse("[fn::[]]").unwrap(),
(
FnRef {
label: None,
definition: Some("[]"),
},
"[fn::[]]".len()
)
);
assert!(FnRef::parse("[fn::[]").is_none());
#[cfg(test)]
mod tests {
#[test]
fn parse() {
use super::parse;
assert_eq!(parse("[fn:1]").unwrap(), (Some("1"), None, "[fn:1]".len()));
assert_eq!(
parse("[fn:1:2]").unwrap(),
(Some("1"), Some("2"), "[fn:1:2]".len())
);
assert_eq!(
parse("[fn::2]").unwrap(),
(None, Some("2"), "[fn::2]".len())
);
assert_eq!(
parse("[fn::[]]").unwrap(),
(None, Some("[]"), "[fn::[]]".len())
);
assert!(parse("[fn::[]").is_none());
}
}

View file

@ -1,115 +1,88 @@
use memchr::{memchr, memchr2};
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub struct InlineCall<'a> {
pub name: &'a str,
pub args: &'a str,
// header args for block
pub inside_header: Option<&'a str>,
// header args for call line
pub end_header: Option<&'a str>,
/// returns (name, args, inside_header, end_header)
#[inline]
pub fn parse(src: &str) -> Option<(&str, &str, Option<&str>, Option<&str>, usize)> {
debug_assert!(src.starts_with("call_"));
// TODO: refactor
let bytes = src.as_bytes();
let mut pos =
memchr2(b'[', b'(', bytes).filter(|&i| bytes[5..i].iter().all(|c| c.is_ascii_graphic()))?;
let mut pos_;
let name = &src[5..pos];
let inside_header = if bytes[pos] == b'[' {
pos_ = pos;
pos = memchr(b']', &bytes[pos..])
.map(|i| i + pos)
.filter(|&i| bytes[pos..i].iter().all(|&c| c != b'\n'))?
+ 1;
expect!(src, pos, b'(')?;
Some(&src[pos_ + 1..pos - 1])
} else {
None
};
pos_ = pos;
pos = memchr(b')', &bytes[pos..])
.map(|i| i + pos)
.filter(|&i| bytes[pos..i].iter().all(|&c| c != b'\n'))?;
let args = &src[pos_ + 1..pos];
let end_header = if src.len() > pos + 1 && src.as_bytes()[pos + 1] == b'[' {
pos_ = pos;
pos = memchr(b']', &bytes[pos_ + 1..])
.map(|i| i + pos_ + 1)
.filter(|&i| bytes[pos_ + 1..i].iter().all(|&c| c != b'\n' && c != b')'))?;
Some(&src[pos_ + 2..pos])
} else {
None
};
Some((name, args, inside_header, end_header, pos + 1))
}
impl<'a> InlineCall<'a> {
pub fn parse(src: &'a str) -> Option<(InlineCall, usize)> {
debug_assert!(src.starts_with("call_"));
#[cfg(test)]
mod tests {
#[test]
fn parse() {
use super::parse;
let bytes = src.as_bytes();
let mut pos = memchr2(b'[', b'(', bytes)
.filter(|&i| bytes[5..i].iter().all(|c| c.is_ascii_graphic()))?;
let mut pos_;
let name = &src[5..pos];
let inside_header = if bytes[pos] == b'[' {
pos_ = pos;
pos = memchr(b']', &bytes[pos..])
.map(|i| i + pos)
.filter(|&i| bytes[pos..i].iter().all(|&c| c != b'\n'))?
+ 1;
expect!(src, pos, b'(')?;
Some(&src[pos_ + 1..pos - 1])
} else {
None
};
pos_ = pos;
pos = memchr(b')', &bytes[pos..])
.map(|i| i + pos)
.filter(|&i| bytes[pos..i].iter().all(|&c| c != b'\n'))?;
let args = &src[pos_ + 1..pos];
let end_header = if src.len() > pos + 1 && src.as_bytes()[pos + 1] == b'[' {
pos_ = pos;
pos = memchr(b']', &bytes[pos_ + 1..])
.map(|i| i + pos_ + 1)
.filter(|&i| bytes[pos_ + 1..i].iter().all(|&c| c != b'\n' && c != b')'))?;
Some(&src[pos_ + 2..pos])
} else {
None
};
Some((
InlineCall {
name,
inside_header,
args,
end_header,
},
pos + 1,
))
assert_eq!(
parse("call_square(4)").unwrap(),
("square", "4", None, None, "call_square(4)".len())
);
assert_eq!(
parse("call_square[:results output](4)").unwrap(),
(
"square",
"4",
Some(":results output"),
None,
"call_square[:results output](4)".len()
)
);
assert_eq!(
parse("call_square(4)[:results html]").unwrap(),
(
"square",
"4",
None,
Some(":results html"),
"call_square(4)[:results html]".len()
)
);
assert_eq!(
parse("call_square[:results output](4)[:results html]").unwrap(),
(
"square",
"4",
Some(":results output"),
Some(":results html"),
"call_square[:results output](4)[:results html]".len()
)
);
}
}
#[test]
fn parse() {
assert_eq!(
InlineCall::parse("call_square(4)").unwrap(),
(
InlineCall {
name: "square",
args: "4",
inside_header: None,
end_header: None,
},
"call_square(4)".len()
)
);
assert_eq!(
InlineCall::parse("call_square[:results output](4)").unwrap(),
(
InlineCall {
name: "square",
args: "4",
inside_header: Some(":results output"),
end_header: None,
},
"call_square[:results output](4)".len()
)
);
assert_eq!(
InlineCall::parse("call_square(4)[:results html]").unwrap(),
(
InlineCall {
name: "square",
args: "4",
inside_header: None,
end_header: Some(":results html"),
},
"call_square(4)[:results html]".len()
)
);
assert_eq!(
InlineCall::parse("call_square[:results output](4)[:results html]").unwrap(),
(
InlineCall {
name: "square",
args: "4",
inside_header: Some(":results output"),
end_header: Some(":results html"),
},
"call_square[:results output](4)[:results html]".len()
)
);
}

View file

@ -1,77 +1,54 @@
use memchr::{memchr, memchr2};
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub struct InlineSrc<'a> {
pub lang: &'a str,
pub option: Option<&'a str>,
pub body: &'a str,
}
/// returns (language, option, body, offset)
#[inline]
pub fn parse(src: &str) -> Option<(&str, Option<&str>, &str, usize)> {
debug_assert!(src.starts_with("src_"));
impl<'a> InlineSrc<'a> {
pub fn parse(src: &'a str) -> Option<(InlineSrc, usize)> {
debug_assert!(src.starts_with("src_"));
let bytes = src.as_bytes();
let lang = memchr2(b'[', b'{', bytes)
.filter(|&i| i != 4 && bytes[4..i].iter().all(|c| !c.is_ascii_whitespace()))?;
let bytes = src.as_bytes();
let lang = memchr2(b'[', b'{', bytes)
.filter(|&i| i != 4 && bytes[4..i].iter().all(|c| !c.is_ascii_whitespace()))?;
if bytes[lang] == b'[' {
let option = memchr(b']', bytes).filter(|&i| bytes[lang..i].iter().all(|c| *c != b'\n'))?;
let body = memchr(b'}', &bytes[option..])
.map(|i| i + option)
.filter(|&i| bytes[option..i].iter().all(|c| *c != b'\n'))?;
if bytes[lang] == b'[' {
let option =
memchr(b']', bytes).filter(|&i| bytes[lang..i].iter().all(|c| *c != b'\n'))?;
let body = memchr(b'}', &bytes[option..])
.map(|i| i + option)
.filter(|&i| bytes[option..i].iter().all(|c| *c != b'\n'))?;
Some((
&src[4..lang],
Some(&src[lang + 1..option]),
&src[option + 2..body],
body + 1,
))
} else {
let body = memchr(b'}', bytes).filter(|&i| bytes[lang..i].iter().all(|c| *c != b'\n'))?;
Some((
InlineSrc {
lang: &src[4..lang],
option: Some(&src[lang + 1..option]),
body: &src[option + 2..body],
},
body + 1,
))
} else {
let body =
memchr(b'}', bytes).filter(|&i| bytes[lang..i].iter().all(|c| *c != b'\n'))?;
Some((
InlineSrc {
lang: &src[4..lang],
option: None,
body: &src[lang + 1..body],
},
body + 1,
))
}
Some((&src[4..lang], None, &src[lang + 1..body], body + 1))
}
}
#[test]
fn parse() {
assert_eq!(
InlineSrc::parse("src_C{int a = 0;}").unwrap(),
(
InlineSrc {
lang: "C",
option: None,
body: "int a = 0;"
},
"src_C{int a = 0;}".len()
)
);
assert_eq!(
InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>}").unwrap(),
(
InlineSrc {
lang: "xml",
option: Some(":exports code"),
body: "<tag>text</tag>"
},
"src_xml[:exports code]{<tag>text</tag>}".len()
)
);
assert!(InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>").is_none());
assert!(InlineSrc::parse("src_[:exports code]{<tag>text</tag>}").is_none());
assert!(InlineSrc::parse("src_xml[:exports code]").is_none());
#[cfg(test)]
mod tests {
#[test]
fn parse() {
use super::parse;
assert_eq!(
parse("src_C{int a = 0;}").unwrap(),
("C", None, "int a = 0;", "src_C{int a = 0;}".len())
);
assert_eq!(
parse("src_xml[:exports code]{<tag>text</tag>}").unwrap(),
(
"xml",
Some(":exports code"),
"<tag>text</tag>",
"src_xml[:exports code]{<tag>text</tag>}".len()
)
);
assert!(parse("src_xml[:exports code]{<tag>text</tag>").is_none());
assert!(parse("src_[:exports code]{<tag>text</tag>}").is_none());
assert!(parse("src_xml[:exports code]").is_none());
}
}

View file

@ -1,71 +1,42 @@
use memchr::memchr;
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub struct Link<'a> {
pub path: &'a str,
pub desc: Option<&'a str>,
}
/// returns (link path, link description, offset)
#[inline]
pub fn parse(src: &str) -> Option<(&str, Option<&str>, usize)> {
debug_assert!(src.starts_with("[["));
impl<'a> Link<'a> {
pub fn parse(src: &'a str) -> Option<(Link<'a>, usize)> {
debug_assert!(src.starts_with("[["));
let bytes = src.as_bytes();
let path = memchr(b']', bytes).filter(|&i| {
bytes[2..i]
.iter()
.all(|&c| c != b'<' && c != b'>' && c != b'\n')
})?;
let bytes = src.as_bytes();
let path = memchr(b']', bytes).filter(|&i| {
bytes[2..i]
.iter()
.all(|&c| c != b'<' && c != b'>' && c != b'\n')
})?;
if *bytes.get(path + 1)? == b']' {
Some((&src[2..path], None, path + 2))
} else if bytes[path + 1] == b'[' {
let desc = memchr(b']', &bytes[path + 2..])
.map(|i| i + path + 2)
.filter(|&i| bytes[path + 2..i].iter().all(|&c| c != b'['))?;
expect!(src, desc + 1, b']')?;
if *bytes.get(path + 1)? == b']' {
Some((
Link {
path: &src[2..path],
desc: None,
},
path + 2,
))
} else if bytes[path + 1] == b'[' {
let desc = memchr(b']', &bytes[path + 2..])
.map(|i| i + path + 2)
.filter(|&i| bytes[path + 2..i].iter().all(|&c| c != b'['))?;
expect!(src, desc + 1, b']')?;
Some((
Link {
path: &src[2..path],
desc: Some(&src[path + 2..desc]),
},
desc + 2,
))
} else {
None
}
Some((&src[2..path], Some(&src[path + 2..desc]), desc + 2))
} else {
None
}
}
#[test]
fn parse() {
assert_eq!(
Link::parse("[[#id]]").unwrap(),
(
Link {
path: "#id",
desc: None,
},
"[[#id]]".len()
)
);
assert_eq!(
Link::parse("[[#id][desc]]").unwrap(),
(
Link {
path: "#id",
desc: Some("desc"),
},
"[[#id][desc]]".len()
)
);
assert!(Link::parse("[[#id][desc]").is_none());
#[cfg(test)]
mod tests {
#[test]
fn parse() {
use super::parse;
assert_eq!(parse("[[#id]]").unwrap(), ("#id", None, "[[#id]]".len()));
assert_eq!(
parse("[[#id][desc]]").unwrap(),
("#id", Some("desc"), "[[#id][desc]]".len())
);
assert!(parse("[[#id][desc]").is_none());
}
}

View file

@ -1,90 +1,62 @@
use jetscii::Substring;
use memchr::memchr2;
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub struct Macros<'a> {
pub name: &'a str,
pub args: Option<&'a str>,
/// returns (macros name, macros arguments, offset)
#[inline]
pub fn parse(src: &str) -> Option<(&str, Option<&str>, usize)> {
debug_assert!(src.starts_with("{{{"));
expect!(src, 3, |c: u8| c.is_ascii_alphabetic())?;
let bytes = src.as_bytes();
let name = memchr2(b'}', b'(', bytes).filter(|&i| {
bytes[3..i]
.iter()
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
})?;
Some(if bytes[name] == b'}' {
expect!(src, name + 1, b'}')?;
expect!(src, name + 2, b'}')?;
(&src[3..name], None, name + 3)
} else {
let end = Substring::new(")}}}")
.find(&src[name..])
.map(|i| i + name)?;
(
&src[3..name],
if name == end {
None
} else {
Some(&src[name + 1..end])
},
end + 4,
)
})
}
impl<'a> Macros<'a> {
pub fn parse(src: &'a str) -> Option<(Macros<'a>, usize)> {
debug_assert!(src.starts_with("{{{"));
#[cfg(test)]
mod tests {
#[test]
fn parse() {
use super::parse;
expect!(src, 3, |c: u8| c.is_ascii_alphabetic())?;
assert_eq!(
parse("{{{poem(red,blue)}}}"),
Some(("poem", Some("red,blue"), "{{{poem(red,blue)}}}".len()))
);
assert_eq!(
parse("{{{poem())}}}"),
Some(("poem", Some(")"), "{{{poem())}}}".len()))
);
assert_eq!(
parse("{{{author}}}"),
Some(("author", None, "{{{author}}}".len()))
);
let bytes = src.as_bytes();
let name = memchr2(b'}', b'(', bytes).filter(|&i| {
bytes[3..i]
.iter()
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
})?;
Some(if bytes[name] == b'}' {
expect!(src, name + 1, b'}')?;
expect!(src, name + 2, b'}')?;
(
Macros {
name: &src[3..name],
args: None,
},
name + 3,
)
} else {
let end = Substring::new(")}}}")
.find(&src[name..])
.map(|i| i + name)?;
(
Macros {
name: &src[3..name],
args: if name == end {
None
} else {
Some(&src[name + 1..end])
},
},
end + 4,
)
})
assert_eq!(parse("{{{0uthor}}}"), None);
assert_eq!(parse("{{{author}}"), None);
assert_eq!(parse("{{{poem(}}}"), None);
assert_eq!(parse("{{{poem)}}}"), None);
}
}
#[test]
fn parse() {
assert_eq!(
Macros::parse("{{{poem(red,blue)}}}"),
Some((
Macros {
name: "poem",
args: Some("red,blue")
},
"{{{poem(red,blue)}}}".len()
))
);
assert_eq!(
Macros::parse("{{{poem())}}}"),
Some((
Macros {
name: "poem",
args: Some(")")
},
"{{{poem())}}}".len()
))
);
assert_eq!(
Macros::parse("{{{author}}}"),
Some((
Macros {
name: "author",
args: None
},
"{{{author}}}".len()
))
);
assert_eq!(Macros::parse("{{{0uthor}}}"), None);
assert_eq!(Macros::parse("{{{author}}"), None);
assert_eq!(Macros::parse("{{{poem(}}}"), None);
assert_eq!(Macros::parse("{{{poem)}}}"), None);
}

View file

@ -5,37 +5,63 @@ mod inline_call;
mod inline_src;
mod link;
mod macros;
mod radio_target;
mod snippet;
mod target;
pub use self::cookie::Cookie;
pub use self::emphasis::Emphasis;
pub use self::fn_ref::FnRef;
pub use self::inline_call::InlineCall;
pub use self::inline_src::InlineSrc;
pub use self::link::Link;
pub use self::macros::Macros;
pub use self::snippet::Snippet;
pub use self::target::{RadioTarget, Target};
use jetscii::bytes;
#[cfg_attr(test, derive(PartialEq, Debug))]
pub enum Object<'a> {
Cookie(Cookie<'a>),
FnRef(FnRef<'a>),
InlineCall(InlineCall<'a>),
InlineSrc(InlineSrc<'a>),
Link(Link<'a>),
Macros(Macros<'a>),
RadioTarget(RadioTarget<'a>),
Snippet(Snippet<'a>),
Target(Target<'a>),
FnRef {
label: Option<&'a str>,
def: Option<&'a str>,
},
InlineCall {
name: &'a str,
args: &'a str,
inside_header: Option<&'a str>,
end_header: Option<&'a str>,
},
InlineSrc {
lang: &'a str,
option: Option<&'a str>,
body: &'a str,
},
Link {
path: &'a str,
desc: Option<&'a str>,
},
Macros {
name: &'a str,
args: Option<&'a str>,
},
RadioTarget {
target: &'a str,
},
Snippet {
name: &'a str,
value: &'a str,
},
Target {
target: &'a str,
},
// `end` indicates the position of the second marker
Bold { end: usize },
Italic { end: usize },
Strike { end: usize },
Underline { end: usize },
Bold {
end: usize,
},
Italic {
end: usize,
},
Strike {
end: usize,
},
Underline {
end: usize,
},
Verbatim(&'a str),
Code(&'a str),
@ -68,40 +94,40 @@ impl<'a> Object<'a> {
match bytes[pos] {
b'@' if bytes[pos + 1] == b'@' => {
if let Some((snippet, off)) = Snippet::parse(&src[pos..]) {
brk!(Object::Snippet(snippet), off, pos);
if let Some((name, value, off)) = snippet::parse(&src[pos..]) {
brk!(Object::Snippet { name, value }, off, pos);
}
}
b'{' if bytes[pos + 1] == b'{' && bytes[pos + 2] == b'{' => {
if let Some((macros, off)) = Macros::parse(&src[pos..]) {
brk!(Object::Macros(macros), off, pos);
if let Some((name, args, off)) = macros::parse(&src[pos..]) {
brk!(Object::Macros { name, args }, off, pos);
}
}
b'<' if bytes[pos + 1] == b'<' => {
if bytes[pos + 2] == b'<' {
if let Some((target, off)) = RadioTarget::parse(&src[pos..]) {
brk!(Object::RadioTarget(target), off, pos);
if let Some((target, off)) = radio_target::parse(&src[pos..]) {
brk!(Object::RadioTarget { target }, off, pos);
}
} else if bytes[pos + 2] != b'\n' {
if let Some((target, off)) = Target::parse(&src[pos..]) {
brk!(Object::Target(target), off, pos);
if let Some((target, off)) = target::parse(&src[pos..]) {
brk!(Object::Target { target }, off, pos);
}
}
}
b'[' => {
if bytes[pos + 1..].starts_with(b"fn:") {
if let Some((fn_ref, off)) = FnRef::parse(&src[pos..]) {
brk!(Object::FnRef(fn_ref), off, pos);
if let Some((label, def, off)) = fn_ref::parse(&src[pos..]) {
brk!(Object::FnRef { label, def }, off, pos);
}
}
if bytes[pos + 1] == b'[' {
if let Some((link, off)) = Link::parse(&src[pos..]) {
brk!(Object::Link(link), off, pos);
if let Some((path, desc, off)) = link::parse(&src[pos..]) {
brk!(Object::Link { path, desc }, off, pos);
}
}
if let Some((cookie, off)) = Cookie::parse(&src[pos..]) {
if let Some((cookie, off)) = cookie::parse(&src[pos..]) {
brk!(Object::Cookie(cookie), off, pos);
}
// TODO: Timestamp
@ -112,43 +138,54 @@ impl<'a> Object<'a> {
match bytes[pre] {
b'*' => {
if let Some(end) = Emphasis::parse(&src[pre..], b'*') {
if let Some(end) = emphasis::parse(&src[pre..], b'*') {
brk!(Object::Bold { end }, 1, pre);
}
}
b'+' => {
if let Some(end) = Emphasis::parse(&src[pre..], b'+') {
if let Some(end) = emphasis::parse(&src[pre..], b'+') {
brk!(Object::Strike { end }, 1, pre);
}
}
b'/' => {
if let Some(end) = Emphasis::parse(&src[pre..], b'/') {
if let Some(end) = emphasis::parse(&src[pre..], b'/') {
brk!(Object::Italic { end }, 1, pre);
}
}
b'_' => {
if let Some(end) = Emphasis::parse(&src[pre..], b'_') {
if let Some(end) = emphasis::parse(&src[pre..], b'_') {
brk!(Object::Underline { end }, 1, pre);
}
}
b'=' => {
if let Some(end) = Emphasis::parse(&src[pre..], b'=') {
if let Some(end) = emphasis::parse(&src[pre..], b'=') {
brk!(Object::Verbatim(&src[pre + 1..pre + end]), end + 1, pre);
}
}
b'~' => {
if let Some(end) = Emphasis::parse(&src[pre..], b'~') {
if let Some(end) = emphasis::parse(&src[pre..], b'~') {
brk!(Object::Code(&src[pre + 1..pre + end]), end + 1, pre);
}
}
b'c' if src[pre..].starts_with("call_") => {
if let Some((call, off)) = InlineCall::parse(&src[pre..]) {
brk!(Object::InlineCall(call), off, pre);
if let Some((name, args, inside_header, end_header, off)) =
inline_call::parse(&src[pre..])
{
brk!(
Object::InlineCall {
name,
args,
inside_header,
end_header,
},
off,
pre
);
}
}
b's' if src[pre..].starts_with("src_") => {
if let Some((src, off)) = InlineSrc::parse(&src[pre..]) {
brk!(Object::InlineSrc(src), off, pre);
if let Some((lang, option, body, off)) = inline_src::parse(&src[pre..]) {
brk!(Object::InlineSrc { lang, option, body }, off, pre);
}
}
_ => (),

View file

@ -0,0 +1,44 @@
use jetscii::Substring;
// TODO: text-markup, entities, latex-fragments, subscript and superscript
#[inline]
pub fn parse(src: &str) -> Option<(&str, usize)> {
debug_assert!(src.starts_with("<<<"));
expect!(src, 3, |c| c != b' ')?;
let end = Substring::new(">>>").find(src).filter(|&i| {
src.as_bytes()[3..i]
.iter()
.all(|&c| c != b'<' && c != b'\n' && c != b'>')
})?;
if src.as_bytes()[end - 1] == b' ' {
return None;
}
Some((&src[3..end], end + 3))
}
#[cfg(test)]
mod tests {
#[test]
fn parse() {
use super::parse;
assert_eq!(
parse("<<<target>>>").unwrap(),
("target", "<<<target>>>".len())
);
assert_eq!(
parse("<<<tar get>>>").unwrap(),
("tar get", "<<<tar get>>>".len())
);
assert_eq!(parse("<<<target >>>"), None);
assert_eq!(parse("<<< target>>>"), None);
assert_eq!(parse("<<<ta<get>>>"), None);
assert_eq!(parse("<<<ta>get>>>"), None);
assert_eq!(parse("<<<ta\nget>>>"), None);
assert_eq!(parse("<<<target>>"), None);
}
}

View file

@ -1,71 +1,46 @@
use jetscii::Substring;
use memchr::memchr;
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub struct Snippet<'a> {
pub name: &'a str,
pub value: &'a str,
/// returns (snippet name, snippet value, offset)
#[inline]
pub fn parse(src: &str) -> Option<(&str, &str, usize)> {
debug_assert!(src.starts_with("@@"));
let name = memchr(b':', src.as_bytes()).filter(|&i| {
i != 2
&& src.as_bytes()[2..i]
.iter()
.all(|&c| c.is_ascii_alphanumeric() || c == b'-')
})?;
let end = Substring::new("@@")
.find(&src[name + 1..])
.map(|i| i + name + 1)?;
Some((&src[2..name], &src[name + 1..end], end + 2))
}
impl<'a> Snippet<'a> {
pub fn parse(src: &'a str) -> Option<(Snippet<'a>, usize)> {
debug_assert!(src.starts_with("@@"));
#[cfg(test)]
mod tests {
#[test]
fn parse() {
use super::parse;
let name = memchr(b':', src.as_bytes()).filter(|&i| {
i != 2
&& src.as_bytes()[2..i]
.iter()
.all(|&c| c.is_ascii_alphanumeric() || c == b'-')
})?;
let end = Substring::new("@@")
.find(&src[name + 1..])
.map(|i| i + name + 1)?;
Some((
Snippet {
name: &src[2..name],
value: &src[name + 1..end],
},
end + 2,
))
assert_eq!(
parse("@@html:<b>@@").unwrap(),
("html", "<b>", "@@html:<b>@@".len())
);
assert_eq!(
parse("@@latex:any arbitrary LaTeX code@@").unwrap(),
(
"latex",
"any arbitrary LaTeX code",
"@@latex:any arbitrary LaTeX code@@".len()
)
);
assert_eq!(parse("@@html:@@").unwrap(), ("html", "", "@@html:@@".len()));
assert!(parse("@@html:<b>@").is_none());
assert!(parse("@@html<b>@@").is_none());
assert!(parse("@@:<b>@@").is_none());
}
}
#[test]
fn parse() {
assert_eq!(
Snippet::parse("@@html:<b>@@").unwrap(),
(
Snippet {
name: "html",
value: "<b>"
},
"@@html:<b>@@".len()
)
);
assert_eq!(
Snippet::parse("@@latex:any arbitrary LaTeX code@@").unwrap(),
(
Snippet {
name: "latex",
value: "any arbitrary LaTeX code"
},
"@@latex:any arbitrary LaTeX code@@".len()
)
);
assert_eq!(
Snippet::parse("@@html:@@").unwrap(),
(
Snippet {
name: "html",
value: ""
},
"@@html:@@".len()
)
);
assert!(Snippet::parse("@@html:<b>@").is_none());
assert!(Snippet::parse("@@html<b>@@").is_none());
assert!(Snippet::parse("@@:<b>@@").is_none());
}

View file

@ -1,79 +1,40 @@
use jetscii::Substring;
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
// TODO: text-markup, entities, latex-fragments, subscript and superscript
pub struct RadioTarget<'a>(&'a str);
#[inline]
pub fn parse(src: &str) -> Option<(&str, usize)> {
debug_assert!(src.starts_with("<<"));
impl<'a> RadioTarget<'a> {
pub fn parse(src: &'a str) -> Option<(RadioTarget<'a>, usize)> {
debug_assert!(src.starts_with("<<<"));
expect!(src, 2, |c| c != b' ')?;
expect!(src, 3, |c| c != b' ')?;
let end = Substring::new(">>").find(src).filter(|&i| {
src.as_bytes()[2..i]
.iter()
.all(|&c| c != b'<' && c != b'\n' && c != b'>')
})?;
let end = Substring::new(">>>").find(src).filter(|&i| {
src.as_bytes()[3..i]
.iter()
.all(|&c| c != b'<' && c != b'\n' && c != b'>')
})?;
if src.as_bytes()[end - 1] == b' ' {
return None;
}
expect!(src, end - 1, |c| c != b' ')?;
Some((&src[2..end], end + 2))
}
Some((RadioTarget(&src[3..end]), end + 3))
#[cfg(test)]
mod tests {
#[test]
fn parse() {
use super::parse;
assert_eq!(parse("<<target>>").unwrap(), ("target", "<<target>>".len()));
assert_eq!(
parse("<<tar get>>").unwrap(),
("tar get", "<<tar get>>".len())
);
assert_eq!(parse("<<target >>"), None);
assert_eq!(parse("<< target>>"), None);
assert_eq!(parse("<<ta<get>>"), None);
assert_eq!(parse("<<ta>get>>"), None);
assert_eq!(parse("<<ta\nget>>"), None);
assert_eq!(parse("<<target>"), None);
}
}
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub struct Target<'a>(&'a str);
impl<'a> Target<'a> {
pub fn parse(src: &'a str) -> Option<(Target<'a>, usize)> {
debug_assert!(src.starts_with("<<"));
expect!(src, 2, |c| c != b' ')?;
let end = Substring::new(">>").find(src).filter(|&i| {
src.as_bytes()[2..i]
.iter()
.all(|&c| c != b'<' && c != b'\n' && c != b'>')
})?;
expect!(src, end - 1, |c| c != b' ')?;
Some((Target(&src[2..end]), end + 2))
}
}
#[test]
fn parse() {
assert_eq!(
RadioTarget::parse("<<<target>>>").unwrap(),
(RadioTarget("target"), "<<<target>>>".len())
);
assert_eq!(
RadioTarget::parse("<<<tar get>>>").unwrap(),
(RadioTarget("tar get"), "<<<tar get>>>".len())
);
assert_eq!(RadioTarget::parse("<<<target >>>"), None);
assert_eq!(RadioTarget::parse("<<< target>>>"), None);
assert_eq!(RadioTarget::parse("<<<ta<get>>>"), None);
assert_eq!(RadioTarget::parse("<<<ta>get>>>"), None);
assert_eq!(RadioTarget::parse("<<<ta\nget>>>"), None);
assert_eq!(RadioTarget::parse("<<<target>>"), None);
assert_eq!(
Target::parse("<<target>>").unwrap(),
(Target("target"), "<<target>>".len())
);
assert_eq!(
Target::parse("<<tar get>>").unwrap(),
(Target("tar get"), "<<tar get>>".len())
);
assert_eq!(Target::parse("<<target >>"), None);
assert_eq!(Target::parse("<< target>>"), None);
assert_eq!(Target::parse("<<ta<get>>"), None);
assert_eq!(Target::parse("<<ta>get>>"), None);
assert_eq!(Target::parse("<<ta\nget>>"), None);
assert_eq!(Target::parse("<<target>"), None);
}