From b985906d6d835ef15e8934b7b2c3511a500d6638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Wed, 11 Jan 2023 23:22:31 +0100 Subject: [PATCH] Add nom-locate, ground for more detailed errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- Cargo.toml | 1 + src/lib.rs | 222 +++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 155 insertions(+), 68 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2c6d04e..9ee7373 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/lib.rs b/src/lib.rs index 88c0513..cac2aa2 100644 --- a/src/lib.rs +++ b/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>>> for Token<'a> { + fn from(err: NomErr>>) -> 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, } -fn allspaces(i: &str) -> IResult<&str, &str> { - let (i, (_, comments)) = tuple((multispace0, opt(comment)))(i)?; +fn allspaces(s: Span) -> IResult { + 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 { + 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, List>(list: List) -> impl Fn(I) -> IResult @@ -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 { + 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 = 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> { - let (i, accounts) = many0(parse_account)(i)?; +fn parse_accounts(s: Span) -> IResult> { + let (s, accounts) = many0(parse_account)(s)?; let mut map: HashMap = 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 { + 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 { + 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 { + 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, 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> { - let (i, actions) = many1(delimited(allspaces, parse_action, allspaces))(i)?; - Ok((i, actions)) +fn parse_actions(s: Span) -> IResult> { + let (s, actions) = many1(delimited(allspaces, parse_action, allspaces))(s)?; + Ok((s, actions)) } -pub fn parse_spec(i: &str) -> Result>> { - let (i, accounts) = parse_accounts(i)?; - let (i, _) = parse_sep(i)?; - let (_i, actions) = parse_actions(i)?; +pub fn parse_spec(i: &str) -> Result { + 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", + (), + ) + } + }) + ); + } }