parser
This commit is contained in:
parent
2fef529f57
commit
a85efe2056
22 changed files with 1776 additions and 7 deletions
67
src/objects/cookie.rs
Normal file
67
src/objects/cookie.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#[cfg_attr(test, derive(PartialEq, Debug))]
|
||||
pub struct Cookie<'a> {
|
||||
value: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Cookie<'a> {
|
||||
pub fn parse(src: &'a str) -> Option<(Cookie<'a>, usize)> {
|
||||
starts_with!(src, '[');
|
||||
|
||||
let num1 = until_while!(src, 1, |c| c == b'%' || c == b'/', |c: u8| c
|
||||
.is_ascii_digit());
|
||||
|
||||
if src.len() > num1 && src.as_bytes()[num1 + 1] == b']' {
|
||||
Some((
|
||||
Cookie {
|
||||
value: &src[0..num1 + 2],
|
||||
},
|
||||
num1 + 2,
|
||||
))
|
||||
} else {
|
||||
let num2 = until_while!(src, num1 + 1, b']', |c: u8| c.is_ascii_digit());
|
||||
Some((
|
||||
Cookie {
|
||||
value: &src[0..=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())
|
||||
);
|
||||
|
||||
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(),);
|
||||
}
|
||||
49
src/objects/emphasis.rs
Normal file
49
src/objects/emphasis.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
pub struct Emphasis;
|
||||
|
||||
impl Emphasis {
|
||||
pub fn parse(src: &str, marker: u8) -> Option<(&'_ str, usize)> {
|
||||
expect!(src, 1, |c: u8| !c.is_ascii_whitespace());
|
||||
|
||||
let mut lines = 0;
|
||||
let end = until_while!(src, 1, marker, |c| {
|
||||
if c == b'\n' {
|
||||
lines += 1;
|
||||
}
|
||||
lines < 2
|
||||
});
|
||||
|
||||
expect!(src, end - 1, |c: u8| !c.is_ascii_whitespace());
|
||||
|
||||
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')'
|
||||
|| ch == b'}');
|
||||
}
|
||||
|
||||
Some((&src[1..end], 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!(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());
|
||||
}
|
||||
38
src/objects/entity.rs
Normal file
38
src/objects/entity.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
pub struct Entity<'a> {
|
||||
pub name: &'a str,
|
||||
pub contents: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> Entity<'a> {
|
||||
pub fn parse(src: &'a str) -> Option<(Entity<'a>, usize)> {
|
||||
expect!(src, 0, b'\\');
|
||||
|
||||
let name = position!(src, 1, |c| !c.is_ascii_alphabetic());
|
||||
|
||||
if src.as_bytes()[name] == b'[' {
|
||||
Some((
|
||||
Entity {
|
||||
name: &src[1..name],
|
||||
contents: None,
|
||||
},
|
||||
name,
|
||||
))
|
||||
} else if src.as_bytes()[name] == b'{' {
|
||||
Some((
|
||||
Entity {
|
||||
name: &src[1..name],
|
||||
contents: None,
|
||||
},
|
||||
name,
|
||||
))
|
||||
} else {
|
||||
Some((
|
||||
Entity {
|
||||
name: &src[1..name],
|
||||
contents: None,
|
||||
},
|
||||
name,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
98
src/objects/fn_ref.rs
Normal file
98
src/objects/fn_ref.rs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
#[cfg_attr(test, derive(PartialEq, Debug))]
|
||||
pub struct FnRef<'a> {
|
||||
label: Option<&'a str>,
|
||||
definition: Option<&'a str>,
|
||||
}
|
||||
|
||||
fn valid_label(ch: u8) -> bool {
|
||||
ch.is_ascii_alphanumeric() || ch == b'-' || ch == b'_'
|
||||
}
|
||||
|
||||
impl<'a> FnRef<'a> {
|
||||
pub fn parse(src: &'a str) -> Option<(FnRef<'a>, usize)> {
|
||||
starts_with!(src, "[fn:");
|
||||
|
||||
let label = until_while!(src, 4, |c| c == b']' || c == b':', valid_label);
|
||||
|
||||
if src.as_bytes()[label] == b':' {
|
||||
let mut pairs = 1;
|
||||
let def = until!(src[label..], |c| {
|
||||
if c == b'[' {
|
||||
pairs += 1;
|
||||
} else if c == b']' {
|
||||
pairs -= 1;
|
||||
}
|
||||
c == b']' && pairs == 0
|
||||
})? + label;
|
||||
|
||||
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,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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());
|
||||
}
|
||||
10
src/objects/fragment.rs
Normal file
10
src/objects/fragment.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#[cfg_attr(test, derive(PartialEq, Debug))]
|
||||
pub struct Fragment<'a> {
|
||||
value: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Fragment<'a> {
|
||||
pub fn parse(src: &'a str) -> Option<(Fragment<'a>, usize)> {
|
||||
None
|
||||
}
|
||||
}
|
||||
105
src/objects/inline_call.rs
Normal file
105
src/objects/inline_call.rs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#[cfg_attr(test, derive(PartialEq, 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>,
|
||||
}
|
||||
|
||||
impl<'a> InlineCall<'a> {
|
||||
pub fn parse(src: &'a str) -> Option<(InlineCall, usize)> {
|
||||
starts_with!(src, "call_");
|
||||
|
||||
let mut pos = until_while!(src, 5, |c| c == b'[' || c == b'(', |c: u8| c
|
||||
.is_ascii_graphic());
|
||||
let mut pos_;
|
||||
|
||||
let name = &src[5..pos];
|
||||
|
||||
let inside_header = if src.as_bytes()[pos] == b'[' {
|
||||
pos_ = pos;
|
||||
pos = until_while!(src, pos, b']', |c: u8| c != b'\n') + 1;
|
||||
expect!(src, pos, b'(');
|
||||
Some(&src[pos_ + 1..pos - 1])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
pos_ = pos;
|
||||
pos = until_while!(src, pos, b')', |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 = until_while!(src, pos_ + 1, |c| c == b']', |c: u8| c != b'\n'
|
||||
&& c != b')');
|
||||
Some(&src[pos_ + 2..pos])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Some((
|
||||
InlineCall {
|
||||
name,
|
||||
inside_header,
|
||||
args,
|
||||
end_header,
|
||||
},
|
||||
pos + 1,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[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()
|
||||
)
|
||||
);
|
||||
}
|
||||
73
src/objects/inline_src.rs
Normal file
73
src/objects/inline_src.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#[cfg_attr(test, derive(PartialEq, Debug))]
|
||||
pub struct InlineSrc<'a> {
|
||||
pub lang: &'a str,
|
||||
pub option: Option<&'a str>,
|
||||
pub body: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> InlineSrc<'a> {
|
||||
pub fn parse(src: &'a str) -> Option<(InlineSrc, usize)> {
|
||||
starts_with!(src, "src_");
|
||||
|
||||
let lang = until_while!(src, 4, |c| c == b'[' || c == b'{', |c: u8| !c
|
||||
.is_ascii_whitespace());
|
||||
|
||||
if lang == 4 {
|
||||
return None;
|
||||
}
|
||||
|
||||
if src.as_bytes()[lang] == b'[' {
|
||||
let option = until_while!(src, lang, b']', |c| c != b'\n');
|
||||
let body = until_while!(src, option, b'}', |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 = until_while!(src, lang, b'}', |c| c != b'\n');
|
||||
|
||||
Some((
|
||||
InlineSrc {
|
||||
lang: &src[4..lang],
|
||||
option: None,
|
||||
body: &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());
|
||||
}
|
||||
68
src/objects/link.rs
Normal file
68
src/objects/link.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#[cfg_attr(test, derive(PartialEq, Debug))]
|
||||
pub struct Link<'a> {
|
||||
pub path: &'a str,
|
||||
pub desc: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> Link<'a> {
|
||||
pub fn parse(src: &'a str) -> Option<(Link<'a>, usize)> {
|
||||
starts_with!(src, "[[");
|
||||
|
||||
let path = until_while!(src, 2, b']', |c| c != b']'
|
||||
&& c != b'<'
|
||||
&& c != b'>'
|
||||
&& c != b'\n');
|
||||
|
||||
if cond_eq!(src, path + 1, b']') {
|
||||
Some((
|
||||
Link {
|
||||
path: &src[2..path],
|
||||
desc: None,
|
||||
},
|
||||
path + 2,
|
||||
))
|
||||
} else if src.as_bytes()[path + 1] == b'[' {
|
||||
let desc = until_while!(src, path + 2, b']', |c| c != b']'
|
||||
&& c != b'['
|
||||
&& c != b'\n');
|
||||
|
||||
expect!(src, desc + 1, b']');
|
||||
|
||||
Some((
|
||||
Link {
|
||||
path: &src[2..path],
|
||||
desc: 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());
|
||||
assert!(Link::parse("[#id][desc]]").is_none());
|
||||
}
|
||||
75
src/objects/macros.rs
Normal file
75
src/objects/macros.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#[cfg_attr(test, derive(PartialEq, Debug))]
|
||||
pub struct Macros<'a> {
|
||||
pub name: &'a str,
|
||||
pub args: Option<&'a str>,
|
||||
}
|
||||
|
||||
fn valid_name(ch: u8) -> bool {
|
||||
ch.is_ascii_alphanumeric() || ch == b'-' && ch == b'_'
|
||||
}
|
||||
|
||||
impl<'a> Macros<'a> {
|
||||
pub fn parse(src: &'a str) -> Option<(Macros<'a>, usize)> {
|
||||
starts_with!(src, "{{{");
|
||||
|
||||
expect!(src, 3, |c: u8| c.is_ascii_alphabetic());
|
||||
|
||||
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'}');
|
||||
Some((
|
||||
Macros {
|
||||
name: &src[3..name],
|
||||
args: None,
|
||||
},
|
||||
name + 3,
|
||||
))
|
||||
} else {
|
||||
let end = find!(src, name, "}}}");
|
||||
expect!(src, end - 1, b')');
|
||||
Some((
|
||||
Macros {
|
||||
name: &src[3..name],
|
||||
args: if name == end {
|
||||
None
|
||||
} else {
|
||||
Some(&src[name + 1..end - 1])
|
||||
},
|
||||
},
|
||||
end + 3,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
91
src/objects/mod.rs
Normal file
91
src/objects/mod.rs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
mod cookie;
|
||||
mod emphasis;
|
||||
mod entity;
|
||||
mod fn_ref;
|
||||
mod fragment;
|
||||
mod inline_call;
|
||||
mod inline_src;
|
||||
mod link;
|
||||
mod macros;
|
||||
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};
|
||||
|
||||
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>),
|
||||
InlineCall(InlineCall<'a>),
|
||||
InlineSrc(InlineSrc<'a>),
|
||||
Link(Link<'a>),
|
||||
Macros(Macros<'a>),
|
||||
RadioTarget(RadioTarget<'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),
|
||||
|
||||
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))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! parse_emphasis {
|
||||
($mk:tt, $ty:ident) => {
|
||||
Emphasis::parse(src, $mk).map(|(s, l)| (Object::$ty(s), l))
|
||||
};
|
||||
}
|
||||
|
||||
(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))
|
||||
}
|
||||
}
|
||||
65
src/objects/snippet.rs
Normal file
65
src/objects/snippet.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#[cfg_attr(test, derive(PartialEq, Debug))]
|
||||
pub struct Snippet<'a> {
|
||||
pub name: &'a str,
|
||||
pub value: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Snippet<'a> {
|
||||
pub fn parse(src: &'a str) -> Option<(Snippet<'a>, usize)> {
|
||||
starts_with!(src, "@@");
|
||||
|
||||
let name = until_while!(src, 2, b':', |c: u8| c.is_ascii_alphanumeric() || c == b'-');
|
||||
|
||||
if name == 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let end = find!(src, name + 1, "@@");
|
||||
|
||||
Some((
|
||||
Snippet {
|
||||
name: &src[2..name],
|
||||
value: &src[name + 1..end],
|
||||
},
|
||||
end + 2,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[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("@@html<b>@@").is_none());
|
||||
assert!(Snippet::parse("@@:<b>@@").is_none());
|
||||
}
|
||||
72
src/objects/target.rs
Normal file
72
src/objects/target.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
use objects::Objects;
|
||||
|
||||
#[cfg_attr(test, derive(PartialEq, Debug))]
|
||||
// TODO: text-markup, entities, latex-fragments, subscript and superscript
|
||||
pub struct RadioTarget<'a>(Objects<'a>);
|
||||
|
||||
impl<'a> RadioTarget<'a> {
|
||||
pub fn parse(src: &'a str) -> Option<(RadioTarget<'a>, usize)> {
|
||||
starts_with!(src, "<<<");
|
||||
expect!(src, 3, |c| c != b' ');
|
||||
|
||||
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'>');
|
||||
|
||||
Some((RadioTarget(Objects::new(&src[3..end])), end + 3))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(PartialEq, Debug))]
|
||||
pub struct Target<'a>(&'a str);
|
||||
|
||||
impl<'a> Target<'a> {
|
||||
pub fn parse(src: &'a str) -> Option<(Target<'a>, usize)> {
|
||||
starts_with!(src, "<<");
|
||||
expect!(src, 2, |c| c != b' ');
|
||||
|
||||
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'>');
|
||||
|
||||
Some((Target(&src[2..end]), end + 2))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert_eq!(
|
||||
RadioTarget::parse("<<<target>>>").unwrap(),
|
||||
(RadioTarget(Objects::new("target")), "<<<target>>>".len())
|
||||
);
|
||||
assert_eq!(
|
||||
RadioTarget::parse("<<<tar get>>>").unwrap(),
|
||||
(RadioTarget(Objects::new("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());
|
||||
|
||||
assert_eq!(
|
||||
Target::parse("<<target>>").unwrap(),
|
||||
(Target("target"), "<<target>>".len())
|
||||
);
|
||||
assert_eq!(
|
||||
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());
|
||||
}
|
||||
7
src/objects/timestamp.rs
Normal file
7
src/objects/timestamp.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
pub struct Time<'a> {
|
||||
pub date: &'a str,
|
||||
}
|
||||
|
||||
pub enum Timestamp<'a> {
|
||||
ActiveRange,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue