Add nom-locate, ground for more detailed errors
Some checks are pending
ci/woodpecker/push/woodpecker Pipeline is pending
Some checks are pending
ci/woodpecker/push/woodpecker Pipeline is pending
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
parent
e626352e87
commit
b985906d6d
2 changed files with 155 additions and 68 deletions
|
@ -10,6 +10,7 @@ description = "Parser for the Scansion DSL"
|
|||
nom = "7.1"
|
||||
jid = "0.9"
|
||||
minidom = "0.15"
|
||||
nom_locate = "4.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.3"
|
||||
|
|
222
src/lib.rs
222
src/lib.rs
|
@ -18,8 +18,27 @@ use nom::{
|
|||
error::{ErrorKind, ParseError},
|
||||
multi::{many0, many1},
|
||||
sequence::{delimited, tuple},
|
||||
FindSubstring, IResult, InputLength, InputTake,
|
||||
Err as NomErr, FindSubstring, IResult, InputLength, InputTake,
|
||||
};
|
||||
use nom_locate::{position, LocatedSpan};
|
||||
|
||||
pub type Span<'a> = LocatedSpan<&'a str>;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Token<'a> {
|
||||
pub position: Span<'a>,
|
||||
}
|
||||
|
||||
impl<'a> From<NomErr<nom::error::Error<LocatedSpan<&'a str>>>> for Token<'a> {
|
||||
fn from(err: NomErr<nom::error::Error<LocatedSpan<&'a str>>>) -> Token<'a> {
|
||||
let span = match err {
|
||||
NomErr::Incomplete(_) => unreachable!(),
|
||||
NomErr::Error(error) | NomErr::Failure(error) => error.input,
|
||||
};
|
||||
|
||||
Token { position: span }
|
||||
}
|
||||
}
|
||||
|
||||
pub static DEFAULT_NS: &'static str = "jabber:client";
|
||||
|
||||
|
@ -46,25 +65,29 @@ pub struct Spec {
|
|||
pub actions: Vec<Action>,
|
||||
}
|
||||
|
||||
fn allspaces(i: &str) -> IResult<&str, &str> {
|
||||
let (i, (_, comments)) = tuple((multispace0, opt(comment)))(i)?;
|
||||
fn allspaces(s: Span) -> IResult<Span, Token> {
|
||||
let (s, (pos, _, comments)) = tuple((position, multispace0, opt(comment)))(s)?;
|
||||
|
||||
let mut i = i;
|
||||
let mut s = s;
|
||||
if let Some(_) = comments {
|
||||
let (j, _) = multispace0(i)?;
|
||||
i = j;
|
||||
let (j, _) = multispace0(s)?;
|
||||
s = j;
|
||||
}
|
||||
|
||||
Ok((i, ""))
|
||||
Ok((s, Token { position: pos }))
|
||||
}
|
||||
|
||||
fn comment(i: &str) -> IResult<&str, &str> {
|
||||
let (i, _) = many1(delimited(
|
||||
alt((tag("#"), tag("//"))),
|
||||
take_until("\n"),
|
||||
tag("\n"),
|
||||
))(i)?;
|
||||
Ok((i, ""))
|
||||
fn comment(s: Span) -> IResult<Span, Token> {
|
||||
let (s, (pos, _)) = tuple((
|
||||
position,
|
||||
many1(delimited(
|
||||
alt((tag("#"), tag("//"))),
|
||||
take_until("\n"),
|
||||
tag("\n"),
|
||||
)),
|
||||
))(s)?;
|
||||
|
||||
Ok((s, Token { position: pos }))
|
||||
}
|
||||
|
||||
fn take_until_tags<T, I, E: ParseError<I>, List>(list: List) -> impl Fn(I) -> IResult<I, I, E>
|
||||
|
@ -93,15 +116,15 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_account(i: &str) -> IResult<&str, (AccountName, Account)> {
|
||||
let (i, (_, _, _, name, _)) =
|
||||
tuple((allspaces, tag("[Client]"), space0, take_until("\n"), space0))(i)?;
|
||||
fn parse_account(s: Span) -> IResult<Span, (AccountName, Account)> {
|
||||
let (s, (_, _, _, name, _)) =
|
||||
tuple((allspaces, tag("[Client]"), space0, take_until("\n"), space0))(s)?;
|
||||
|
||||
let (i, lines) = many1(tuple((
|
||||
let (s, lines) = many1(tuple((
|
||||
tag("\n"),
|
||||
take_while1(|c| c == '\t'),
|
||||
take_until("\n"),
|
||||
)))(i)?;
|
||||
)))(s)?;
|
||||
|
||||
let name = name.trim();
|
||||
let mut jid: Option<BareJid> = None;
|
||||
|
@ -125,40 +148,40 @@ fn parse_account(i: &str) -> IResult<&str, (AccountName, Account)> {
|
|||
|
||||
if jid.is_none() || password.is_none() {
|
||||
return Err(nom::Err::Error(nom::error::Error::from_error_kind(
|
||||
i,
|
||||
s,
|
||||
ErrorKind::Tag,
|
||||
)));
|
||||
}
|
||||
|
||||
// Skip comments and empty newlines
|
||||
let (i, _) = allspaces(i)?;
|
||||
let (s, _) = allspaces(s)?;
|
||||
|
||||
let account = Account {
|
||||
jid: jid.unwrap(),
|
||||
password: password.unwrap(),
|
||||
resource,
|
||||
};
|
||||
Ok((i, (String::from(name), account)))
|
||||
Ok((s, (String::from(name), account)))
|
||||
}
|
||||
|
||||
fn parse_accounts(i: &str) -> IResult<&str, HashMap<AccountName, Account>> {
|
||||
let (i, accounts) = many0(parse_account)(i)?;
|
||||
fn parse_accounts(s: Span) -> IResult<Span, HashMap<AccountName, Account>> {
|
||||
let (s, accounts) = many0(parse_account)(s)?;
|
||||
let mut map: HashMap<AccountName, Account> = HashMap::new();
|
||||
for (name, account) in accounts {
|
||||
map.insert(name, account);
|
||||
}
|
||||
Ok((i, map))
|
||||
Ok((s, map))
|
||||
}
|
||||
|
||||
fn parse_sep(i: &str) -> IResult<&str, &str> {
|
||||
let (i, _) = delimited(allspaces, many1(tag("-")), allspaces)(i)?;
|
||||
Ok((i, ""))
|
||||
fn parse_sep(s: Span) -> IResult<Span, Token> {
|
||||
let (s, (pos, _)) = delimited(allspaces, tuple((position, many1(tag("-")))), allspaces)(s)?;
|
||||
Ok((s, Token { position: pos }))
|
||||
}
|
||||
|
||||
fn parse_action(i: &str) -> IResult<&str, Action> {
|
||||
let (i, name) =
|
||||
take_until_tags(vec!["disconnects", "connects", "sends:", "receives:"].into_iter())(i)?;
|
||||
let (i, (tagname, _, _)) = tuple((
|
||||
fn parse_action(s: Span) -> IResult<Span, Action> {
|
||||
let (s, name) =
|
||||
take_until_tags(vec!["disconnects", "connects", "sends:", "receives:"].into_iter())(s)?;
|
||||
let (s, (tagname, _, _)) = tuple((
|
||||
alt((
|
||||
tag("connects"),
|
||||
tag("disconnects"),
|
||||
|
@ -167,49 +190,50 @@ fn parse_action(i: &str) -> IResult<&str, Action> {
|
|||
)),
|
||||
space0,
|
||||
tag("\n"),
|
||||
))(i)?;
|
||||
))(s)?;
|
||||
|
||||
let name = String::from(name.trim());
|
||||
let (i, action) = match tagname {
|
||||
"connects" => (i, Action::Connect(name)),
|
||||
"disconnects" => (i, Action::Disconnect(name)),
|
||||
tagname @ "sends:" | tagname @ "receives:" => parse_send_receive(tagname, name, i)?,
|
||||
let (s, action) = match *tagname.fragment() {
|
||||
"connects" => (s, Action::Connect(name)),
|
||||
"disconnects" => (s, Action::Disconnect(name)),
|
||||
tagname @ "sends:" | tagname @ "receives:" => parse_send_receive(tagname, name, s)?,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Ok((i, action))
|
||||
Ok((s, action))
|
||||
}
|
||||
|
||||
fn parse_action_subline(i: &str) -> IResult<&str, &str> {
|
||||
let (i, (_, line, _)) = tuple((many1(tag("\t")), take_until1("\n"), tag("\n")))(i)?;
|
||||
Ok((i, line))
|
||||
fn parse_action_subline(s: Span) -> IResult<Span, &str> {
|
||||
let (s, (_, line, _)) = tuple((many1(tag("\t")), take_until1("\n"), tag("\n")))(s)?;
|
||||
Ok((s, line.fragment()))
|
||||
}
|
||||
|
||||
fn parse_send_receive<'a>(tagname: &str, name: String, i: &'a str) -> IResult<&'a str, Action> {
|
||||
let (i, (_, lines)) = tuple((
|
||||
fn parse_send_receive<'a>(tagname: &str, name: String, s: Span<'a>) -> IResult<Span<'a>, Action> {
|
||||
let (s, (_, lines)) = tuple((
|
||||
take_while(|c| c == ' ' || c == '\r' || c == '\n'), // Spaces but \t
|
||||
recognize(many1(parse_action_subline)),
|
||||
))(i)?;
|
||||
))(s)?;
|
||||
let lines = lines.trim();
|
||||
|
||||
let elem: Element =
|
||||
Element::from_reader_with_prefixes(&lines.as_bytes()[..], String::from(DEFAULT_NS))
|
||||
.unwrap();
|
||||
Ok(match tagname {
|
||||
"sends:" => (i, Action::Send(name, elem)),
|
||||
"receives:" => (i, Action::Receive(name, elem)),
|
||||
"sends:" => (s, Action::Send(name, elem)),
|
||||
"receives:" => (s, Action::Receive(name, elem)),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_actions(i: &str) -> IResult<&str, Vec<Action>> {
|
||||
let (i, actions) = many1(delimited(allspaces, parse_action, allspaces))(i)?;
|
||||
Ok((i, actions))
|
||||
fn parse_actions(s: Span) -> IResult<Span, Vec<Action>> {
|
||||
let (s, actions) = many1(delimited(allspaces, parse_action, allspaces))(s)?;
|
||||
Ok((s, actions))
|
||||
}
|
||||
|
||||
pub fn parse_spec(i: &str) -> Result<Spec, nom::Err<nom::error::Error<&str>>> {
|
||||
let (i, accounts) = parse_accounts(i)?;
|
||||
let (i, _) = parse_sep(i)?;
|
||||
let (_i, actions) = parse_actions(i)?;
|
||||
pub fn parse_spec(i: &str) -> Result<Spec, Token> {
|
||||
let s: Span = i.into();
|
||||
let (s, accounts) = parse_accounts(s)?;
|
||||
let (s, _) = parse_sep(s)?;
|
||||
let (_, actions) = parse_actions(s)?;
|
||||
Ok(Spec { accounts, actions })
|
||||
}
|
||||
|
||||
|
@ -230,17 +254,41 @@ mod tests {
|
|||
fn test_comment() {
|
||||
let buf1 = "#\n";
|
||||
let buf2 = "# Foo\n";
|
||||
assert_eq!(comment(buf1), Ok(("", "")));
|
||||
assert_eq!(comment(buf2), Ok(("", "")));
|
||||
assert_eq!(
|
||||
comment(buf1.into()),
|
||||
Ok((
|
||||
unsafe { LocatedSpan::new_from_raw_offset(2, 2, "", ()) },
|
||||
Token {
|
||||
position: LocatedSpan::new("")
|
||||
}
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
comment(buf2.into()),
|
||||
Ok((
|
||||
unsafe { LocatedSpan::new_from_raw_offset(6, 2, "", ()) },
|
||||
Token {
|
||||
position: LocatedSpan::new("")
|
||||
}
|
||||
))
|
||||
);
|
||||
|
||||
let buf3 = " # Foo\n";
|
||||
match comment(buf3) {
|
||||
match comment(buf3.into()) {
|
||||
Err(_) => (),
|
||||
err => panic!("Unexpected result: {:?}", err),
|
||||
}
|
||||
|
||||
let buf4 = "// Foo\n";
|
||||
assert_eq!(comment(buf4), Ok(("", "")));
|
||||
assert_eq!(
|
||||
comment(buf4.into()),
|
||||
Ok((
|
||||
unsafe { LocatedSpan::new_from_raw_offset(7, 2, "", ()) },
|
||||
Token {
|
||||
position: LocatedSpan::new("")
|
||||
}
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -256,8 +304,11 @@ mod tests {
|
|||
resource: None,
|
||||
};
|
||||
assert_eq!(
|
||||
parse_account(buf1),
|
||||
Ok(("", (name.clone(), account.clone())))
|
||||
parse_account(buf1.into()),
|
||||
Ok((
|
||||
unsafe { LocatedSpan::new_from_raw_offset(59, 4, "", ()) },
|
||||
(name.clone(), account.clone())
|
||||
))
|
||||
);
|
||||
|
||||
let name = String::from("louise's phone");
|
||||
|
@ -267,12 +318,15 @@ mod tests {
|
|||
resource: Some(String::from("resource1")),
|
||||
};
|
||||
assert_eq!(
|
||||
parse_account(buf2),
|
||||
Ok(("", (name.clone(), account.clone())))
|
||||
parse_account(buf2.into()),
|
||||
Ok((
|
||||
unsafe { LocatedSpan::new_from_raw_offset(90, 5, "", ()) },
|
||||
(name.clone(), account.clone())
|
||||
))
|
||||
);
|
||||
|
||||
// Missing tab
|
||||
match parse_account(buf3) {
|
||||
match parse_account(buf3.into()) {
|
||||
Err(_) => (),
|
||||
err => panic!("Unexpected result: {:?}", err),
|
||||
}
|
||||
|
@ -308,7 +362,13 @@ mod tests {
|
|||
resource: Some(String::from("resource1")),
|
||||
},
|
||||
);
|
||||
assert_eq!(parse_accounts(buf1), Ok(("", accounts)));
|
||||
assert_eq!(
|
||||
parse_accounts(buf1.into()),
|
||||
Ok((
|
||||
unsafe { LocatedSpan::new_from_raw_offset(144, 10, "", ()) },
|
||||
accounts
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -316,12 +376,12 @@ mod tests {
|
|||
let buf1 = "louise connects\n";
|
||||
|
||||
let action1 = Action::Connect(String::from("louise"));
|
||||
assert_eq!(parse_action(buf1).unwrap().1, action1);
|
||||
assert_eq!(parse_action(buf1.into()).unwrap().1, action1);
|
||||
|
||||
let buf2 = "louise's phone connects\n";
|
||||
|
||||
let action2 = Action::Connect(String::from("louise's phone"));
|
||||
assert_eq!(parse_action(buf2).unwrap().1, action2);
|
||||
assert_eq!(parse_action(buf2.into()).unwrap().1, action2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -329,7 +389,7 @@ mod tests {
|
|||
let buf1 = "louise disconnects\n";
|
||||
|
||||
let action = Action::Disconnect(String::from("louise"));
|
||||
assert_eq!(parse_action(buf1).unwrap().1, action);
|
||||
assert_eq!(parse_action(buf1.into()).unwrap().1, action);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -347,7 +407,7 @@ mod tests {
|
|||
String::from("rosa"),
|
||||
Element::from_reader_with_prefixes(&xml[..], String::from(DEFAULT_NS)).unwrap(),
|
||||
);
|
||||
assert_eq!(parse_action(buf).unwrap().1, send);
|
||||
assert_eq!(parse_action(buf.into()).unwrap().1, send);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -365,7 +425,7 @@ mod tests {
|
|||
String::from("rosa"),
|
||||
Element::from_reader_with_prefixes(&xml[..], String::from(DEFAULT_NS)).unwrap(),
|
||||
);
|
||||
assert_eq!(parse_action(buf).unwrap().1, receive);
|
||||
assert_eq!(parse_action(buf.into()).unwrap().1, receive);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -410,4 +470,30 @@ louise receives:
|
|||
|
||||
assert_eq!(parse_spec(buf), Ok(spec));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_errors() {
|
||||
let buf1: Span = "[Foo] bar\n".into();
|
||||
assert_eq!(
|
||||
parse_spec(&buf1),
|
||||
Err(Token {
|
||||
position: LocatedSpan::new("[Foo] bar\n")
|
||||
})
|
||||
);
|
||||
|
||||
let buf2: Span = "[Client] louise\n\tjid: jid@localhost\npassword: password\n".into();
|
||||
assert_eq!(
|
||||
parse_spec(&buf2),
|
||||
Err(Token {
|
||||
position: unsafe {
|
||||
LocatedSpan::new_from_raw_offset(
|
||||
0,
|
||||
1,
|
||||
"[Client] louise\n\tjid: jid@localhost\npassword: password\n",
|
||||
(),
|
||||
)
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue