This commit is contained in:
PoiScript 2019-01-10 20:58:13 +08:00
parent a85efe2056
commit 6f7fa9c920
16 changed files with 622 additions and 229 deletions

View file

@ -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());

View file

@ -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());

View file

@ -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 {

View file

@ -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)}}}");
}

View file

@ -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()))
)
);
}

View file

@ -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,
))

View file

@ -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>");
}