update
This commit is contained in:
parent
a85efe2056
commit
6f7fa9c920
16 changed files with 622 additions and 229 deletions
|
|
@ -1,7 +1,8 @@
|
|||
pub struct Emphasis;
|
||||
|
||||
impl Emphasis {
|
||||
pub fn parse(src: &str, marker: u8) -> Option<(&'_ str, usize)> {
|
||||
// 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 mut lines = 0;
|
||||
|
|
@ -23,24 +24,19 @@ impl Emphasis {
|
|||
|| ch == b'!'
|
||||
|| ch == b'?'
|
||||
|| ch == b'\''
|
||||
|| ch == b'\n'
|
||||
|| ch == b')'
|
||||
|| ch == b'}');
|
||||
}
|
||||
|
||||
Some((&src[1..end], end + 1))
|
||||
Some(end - 1)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert_eq!(
|
||||
Emphasis::parse("*bold*", b'*').unwrap(),
|
||||
("bold", "*bold*".len())
|
||||
);
|
||||
assert_eq!(
|
||||
Emphasis::parse("*bo\nld*", b'*').unwrap(),
|
||||
("bo\nld", "*bo\nld*".len())
|
||||
);
|
||||
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());
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ pub struct Entity<'a> {
|
|||
|
||||
impl<'a> Entity<'a> {
|
||||
pub fn parse(src: &'a str) -> Option<(Entity<'a>, usize)> {
|
||||
expect!(src, 0, b'\\');
|
||||
expect!(src, 0, b'\\')?;
|
||||
|
||||
let name = position!(src, 1, |c| !c.is_ascii_alphabetic());
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ impl<'a> Link<'a> {
|
|||
&& c != b'['
|
||||
&& c != b'\n');
|
||||
|
||||
expect!(src, desc + 1, b']');
|
||||
expect!(src, desc + 1, b']')?;
|
||||
|
||||
Some((
|
||||
Link {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ pub struct Macros<'a> {
|
|||
}
|
||||
|
||||
fn valid_name(ch: u8) -> bool {
|
||||
ch.is_ascii_alphanumeric() || ch == b'-' && ch == b'_'
|
||||
ch.is_ascii_alphanumeric() || ch == b'-' || ch == b'_'
|
||||
}
|
||||
|
||||
impl<'a> Macros<'a> {
|
||||
|
|
@ -17,8 +17,8 @@ impl<'a> Macros<'a> {
|
|||
let name = until_while!(src, 3, |c| c == b'}' || c == b'(', valid_name);
|
||||
|
||||
if src.as_bytes()[name] == b'}' {
|
||||
expect!(src, name + 1, b'}');
|
||||
expect!(src, name + 2, b'}');
|
||||
expect!(src, name + 1, b'}')?;
|
||||
expect!(src, name + 2, b'}')?;
|
||||
Some((
|
||||
Macros {
|
||||
name: &src[3..name],
|
||||
|
|
@ -27,12 +27,12 @@ impl<'a> Macros<'a> {
|
|||
name + 3,
|
||||
))
|
||||
} else {
|
||||
let end = find!(src, name, "}}}");
|
||||
expect!(src, end - 1, b')');
|
||||
let end = &src[name..].find("}}}").map(|i| i + name)?;
|
||||
expect!(src, end - 1, b')')?;
|
||||
Some((
|
||||
Macros {
|
||||
name: &src[3..name],
|
||||
args: if name == end {
|
||||
args: if name == *end {
|
||||
None
|
||||
} else {
|
||||
Some(&src[name + 1..end - 1])
|
||||
|
|
@ -46,30 +46,12 @@ impl<'a> Macros<'a> {
|
|||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert_eq!(
|
||||
Macros::parse("{{{poem(red,blue)}}}").unwrap(),
|
||||
(
|
||||
Macros {
|
||||
name: "poem",
|
||||
args: Some("red,blue")
|
||||
},
|
||||
"{{{poem(red,blue)}}}".len()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
Macros::parse("{{{author}}}").unwrap(),
|
||||
(
|
||||
Macros {
|
||||
name: "author",
|
||||
args: None,
|
||||
},
|
||||
"{{{author}}}".len()
|
||||
)
|
||||
);
|
||||
assert!(Macros::parse("{{author}}}").is_none());
|
||||
assert!(Macros::parse("{{{0uthor}}}").is_none());
|
||||
assert!(Macros::parse("{{{author}}").is_none());
|
||||
assert!(Macros::parse("{{{poem(}}}").is_none());
|
||||
assert!(Macros::parse("{{{poem)}}}").is_none());
|
||||
// FIXME: assert_eq!(Macros::parse("{{{poem())}}}"), None);
|
||||
parse_succ!(Macros, "{{{poem(red,blue)}}}", name: "poem", args: Some("red,blue"));
|
||||
parse_succ!(Macros, "{{{poem())}}}", name: "poem", args: Some(")"));
|
||||
parse_succ!(Macros, "{{{author}}}", name: "author", args: None);
|
||||
parse_fail!(Macros, "{{author}}}");
|
||||
parse_fail!(Macros, "{{{0uthor}}}");
|
||||
parse_fail!(Macros, "{{{author}}");
|
||||
parse_fail!(Macros, "{{{poem(}}}");
|
||||
parse_fail!(Macros, "{{{poem)}}}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,20 +20,7 @@ pub use self::macros::Macros;
|
|||
pub use self::snippet::Snippet;
|
||||
pub use self::target::{RadioTarget, Target};
|
||||
|
||||
const ACTIVE_TAB: [u8; 6] = [b' ', b'"', b'(', b'{', b'\'', b'\n'];
|
||||
|
||||
#[cfg_attr(test, derive(PartialEq, Debug))]
|
||||
pub struct Objects<'a> {
|
||||
text: &'a str,
|
||||
off: usize,
|
||||
}
|
||||
|
||||
impl<'a> Objects<'a> {
|
||||
pub fn new(text: &'a str) -> Objects<'a> {
|
||||
Objects { text, off: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Object<'a> {
|
||||
Cookie(Cookie<'a>),
|
||||
FnRef(FnRef<'a>),
|
||||
|
|
@ -45,47 +32,163 @@ pub enum Object<'a> {
|
|||
Snippet(Snippet<'a>),
|
||||
Target(Target<'a>),
|
||||
|
||||
Bold(&'a str),
|
||||
Verbatim(&'a str),
|
||||
Italic(&'a str),
|
||||
Strike(&'a str),
|
||||
Underline(&'a str),
|
||||
Code(&'a str),
|
||||
Bold { end: usize },
|
||||
Italic { end: usize },
|
||||
Strike { end: usize },
|
||||
Underline { end: usize },
|
||||
|
||||
Verbatim(&'a str),
|
||||
Code(&'a str),
|
||||
Text(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> Object<'a> {
|
||||
pub fn parse(src: &'a str) -> (Object<'a>, usize) {
|
||||
macro_rules! parse {
|
||||
($ty:ident) => {
|
||||
$ty::parse(src).map(|(s, l)| (Object::$ty(s), l))
|
||||
};
|
||||
pub fn next_2(src: &'a str) -> (Object<'a>, usize, Option<(Object<'a>, usize)>) {
|
||||
let bytes = src.as_bytes();
|
||||
|
||||
if src.len() < 2 {
|
||||
return (Object::Text(src), src.len(), None);
|
||||
}
|
||||
|
||||
macro_rules! parse_emphasis {
|
||||
($mk:tt, $ty:ident) => {
|
||||
Emphasis::parse(src, $mk).map(|(s, l)| (Object::$ty(s), l))
|
||||
};
|
||||
// TODO: refactor with src[..].find(..)
|
||||
for pos in 0..src.len() - 2 {
|
||||
macro_rules! parse {
|
||||
($obj:ident) => {
|
||||
if let Some((obj, off)) = $obj::parse(&src[pos..]) {
|
||||
return if pos == 0 {
|
||||
(Object::$obj(obj), off, None)
|
||||
} else {
|
||||
(
|
||||
Object::Text(&src[0..pos]),
|
||||
pos,
|
||||
Some((Object::$obj(obj), off)),
|
||||
)
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let first = bytes[pos];
|
||||
let second = bytes[pos + 1];
|
||||
let third = bytes[pos + 2];
|
||||
|
||||
if first == b'@' && second == b'@' {
|
||||
parse!(Snippet);
|
||||
}
|
||||
|
||||
if first == b'[' {
|
||||
if second == b'f' && third == b'n' {
|
||||
parse!(FnRef);
|
||||
} else if second == b'[' {
|
||||
parse!(Link);
|
||||
} else {
|
||||
parse!(Cookie);
|
||||
// TODO: Timestamp
|
||||
}
|
||||
}
|
||||
|
||||
if first == b'{' && second == b'{' && third == b'{' {
|
||||
parse!(Macros);
|
||||
}
|
||||
|
||||
if first == b'<' && second == b'<' {
|
||||
if third == b'<' {
|
||||
parse!(RadioTarget);
|
||||
} else if third != b'<' && third != b'\n' {
|
||||
parse!(Target);
|
||||
}
|
||||
}
|
||||
|
||||
if pos == 0
|
||||
|| bytes[pos - 1] == b' '
|
||||
|| bytes[pos - 1] == b'"'
|
||||
|| bytes[pos - 1] == b'('
|
||||
|| bytes[pos - 1] == b','
|
||||
|| bytes[pos - 1] == b'\n'
|
||||
|| bytes[pos - 1] == b'{'
|
||||
{
|
||||
if (first == b'*'
|
||||
|| first == b'+'
|
||||
|| first == b'/'
|
||||
|| first == b'='
|
||||
|| first == b'_'
|
||||
|| first == b'~')
|
||||
&& !second.is_ascii_whitespace()
|
||||
{
|
||||
if let Some(end) = Emphasis::parse(&src[pos..], first).map(|i| i + pos) {
|
||||
macro_rules! emph {
|
||||
($obj:ident) => {
|
||||
return if pos == 0 {
|
||||
(Object::$obj { end }, 1, None)
|
||||
} else {
|
||||
(
|
||||
Object::Text(&src[0..pos]),
|
||||
pos,
|
||||
Some((Object::$obj { end }, end)),
|
||||
)
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
match first {
|
||||
b'*' => emph!(Bold),
|
||||
b'+' => emph!(Strike),
|
||||
b'/' => emph!(Italic),
|
||||
b'_' => emph!(Underline),
|
||||
b'~' => {
|
||||
return if pos == 0 {
|
||||
(Object::Code(&src[1..end + 1]), end + 2, None)
|
||||
} else {
|
||||
(
|
||||
Object::Text(&src[0..pos]),
|
||||
pos,
|
||||
Some((Object::Code(&src[pos + 1..end + 1]), end - pos + 2)),
|
||||
)
|
||||
};
|
||||
}
|
||||
b'=' => {
|
||||
return if pos == 0 {
|
||||
(Object::Verbatim(&src[1..end + 1]), end + 2, None)
|
||||
} else {
|
||||
(
|
||||
Object::Text(&src[0..pos]),
|
||||
pos,
|
||||
Some((
|
||||
Object::Verbatim(&src[pos + 1..end + 1]),
|
||||
end - pos + 2,
|
||||
)),
|
||||
)
|
||||
};
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if first == b'c' && second == b'a' && third == b'l' {
|
||||
parse!(InlineCall);
|
||||
}
|
||||
|
||||
if first == b's' && second == b'r' && third == b'c' {
|
||||
parse!(InlineSrc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(match src.as_bytes()[0] {
|
||||
b'@' => parse!(Snippet),
|
||||
b'[' => parse!(FnRef)
|
||||
.or_else(|| parse!(Link))
|
||||
.or_else(|| parse!(Cookie)),
|
||||
b's' => parse!(InlineSrc),
|
||||
b'c' => parse!(InlineCall),
|
||||
b'{' => parse!(Macros),
|
||||
b'<' => parse!(RadioTarget).or_else(|| parse!(Target)),
|
||||
b'*' => parse_emphasis!(b'*', Bold),
|
||||
b'=' => parse_emphasis!(b'=', Verbatim),
|
||||
b'/' => parse_emphasis!(b'/', Italic),
|
||||
b'+' => parse_emphasis!(b'+', Strike),
|
||||
b'_' => parse_emphasis!(b'_', Underline),
|
||||
b'~' => parse_emphasis!(b'~', Code),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or((Object::Text(&src[0..1]), 1))
|
||||
(Object::Text(src), src.len(), None)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_2() {
|
||||
// TODO: more tests
|
||||
assert_eq!(Object::next_2("*bold*"), (Object::Bold { end: 4 }, 1, None));
|
||||
assert_eq!(
|
||||
Object::next_2("Normal =verbatim="),
|
||||
(
|
||||
Object::Text("Normal "),
|
||||
"Normal ".len(),
|
||||
Some((Object::Verbatim("verbatim"), "=verbatim=".len()))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ impl<'a> Snippet<'a> {
|
|||
return None;
|
||||
}
|
||||
|
||||
let end = find!(src, name + 1, "@@");
|
||||
let end = &src[name + 1..].find("@@").map(|i| i + name + 1)?;
|
||||
|
||||
Some((
|
||||
Snippet {
|
||||
name: &src[2..name],
|
||||
value: &src[name + 1..end],
|
||||
value: &src[name + 1..*end],
|
||||
},
|
||||
end + 2,
|
||||
))
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
use objects::Objects;
|
||||
|
||||
#[cfg_attr(test, derive(PartialEq, Debug))]
|
||||
// TODO: text-markup, entities, latex-fragments, subscript and superscript
|
||||
pub struct RadioTarget<'a>(Objects<'a>);
|
||||
pub struct RadioTarget<'a>(&'a str);
|
||||
|
||||
impl<'a> RadioTarget<'a> {
|
||||
pub fn parse(src: &'a str) -> Option<(RadioTarget<'a>, usize)> {
|
||||
|
|
@ -12,10 +10,10 @@ impl<'a> RadioTarget<'a> {
|
|||
let end = until_while!(src, 3, b'>', |c| c != b'<' && c != b'\n');
|
||||
|
||||
expect!(src, end - 1, |c| c != b' ');
|
||||
expect!(src, end + 1, b'>');
|
||||
expect!(src, end + 2, b'>');
|
||||
expect!(src, end + 1, b'>')?;
|
||||
expect!(src, end + 2, b'>')?;
|
||||
|
||||
Some((RadioTarget(Objects::new(&src[3..end])), end + 3))
|
||||
Some((RadioTarget(&src[3..end]), end + 3))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -30,7 +28,7 @@ impl<'a> Target<'a> {
|
|||
let end = until_while!(src, 2, b'>', |c| c != b'<' && c != b'\n');
|
||||
|
||||
expect!(src, end - 1, |c| c != b' ');
|
||||
expect!(src, end + 1, b'>');
|
||||
expect!(src, end + 1, b'>')?;
|
||||
|
||||
Some((Target(&src[2..end]), end + 2))
|
||||
}
|
||||
|
|
@ -40,19 +38,19 @@ impl<'a> Target<'a> {
|
|||
fn parse() {
|
||||
assert_eq!(
|
||||
RadioTarget::parse("<<<target>>>").unwrap(),
|
||||
(RadioTarget(Objects::new("target")), "<<<target>>>".len())
|
||||
(RadioTarget("target"), "<<<target>>>".len())
|
||||
);
|
||||
assert_eq!(
|
||||
RadioTarget::parse("<<<tar get>>>").unwrap(),
|
||||
(RadioTarget(Objects::new("tar get")), "<<<tar get>>>".len())
|
||||
(RadioTarget("tar get"), "<<<tar get>>>".len())
|
||||
);
|
||||
assert!(RadioTarget::parse("<<<target >>>").is_none());
|
||||
assert!(RadioTarget::parse("<<< target>>>").is_none());
|
||||
assert!(RadioTarget::parse("<<<ta<get>>>").is_none());
|
||||
assert!(RadioTarget::parse("<<<ta>get>>>").is_none());
|
||||
assert!(RadioTarget::parse("<<<ta\nget>>>").is_none());
|
||||
assert!(RadioTarget::parse("<<target>>>").is_none());
|
||||
assert!(RadioTarget::parse("<<<target>>").is_none());
|
||||
parse_fail!(RadioTarget, "<<<target >>>");
|
||||
parse_fail!(RadioTarget, "<<< target>>>");
|
||||
parse_fail!(RadioTarget, "<<<ta<get>>>");
|
||||
parse_fail!(RadioTarget, "<<<ta>get>>>");
|
||||
parse_fail!(RadioTarget, "<<<ta\nget>>>");
|
||||
parse_fail!(RadioTarget, "<<target>>>");
|
||||
parse_fail!(RadioTarget, "<<<target>>");
|
||||
|
||||
assert_eq!(
|
||||
Target::parse("<<target>>").unwrap(),
|
||||
|
|
@ -62,11 +60,11 @@ fn parse() {
|
|||
Target::parse("<<tar get>>").unwrap(),
|
||||
(Target("tar get"), "<<tar get>>".len())
|
||||
);
|
||||
assert!(Target::parse("<<target >>").is_none());
|
||||
assert!(Target::parse("<< target>>").is_none());
|
||||
assert!(Target::parse("<<ta<get>>").is_none());
|
||||
assert!(Target::parse("<<ta>get>>").is_none());
|
||||
assert!(Target::parse("<<ta\nget>>").is_none());
|
||||
assert!(Target::parse("<target>>").is_none());
|
||||
assert!(Target::parse("<<target>").is_none());
|
||||
parse_fail!(Target, "<<target >>");
|
||||
parse_fail!(Target, "<< target>>");
|
||||
parse_fail!(Target, "<<ta<get>>");
|
||||
parse_fail!(Target, "<<ta>get>>");
|
||||
parse_fail!(Target, "<<ta\nget>>");
|
||||
parse_fail!(Target, "<target>>");
|
||||
parse_fail!(Target, "<<target>");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue