Move parse errors, add helper func for parse fail

This commit is contained in:
RumovZ 2021-01-13 13:23:25 +01:00
parent b89381ac95
commit 447ff6931c
2 changed files with 82 additions and 89 deletions

View File

@ -3,6 +3,7 @@
use crate::i18n::{tr_args, tr_strs, I18n, TR}; use crate::i18n::{tr_args, tr_strs, I18n, TR};
pub use failure::{Error, Fail}; pub use failure::{Error, Fail};
use nom::error::{ErrorKind as NomErrorKind, ParseError as NomParseError};
use reqwest::StatusCode; use reqwest::StatusCode;
use std::{io, str::Utf8Error}; use std::{io, str::Utf8Error};
@ -327,3 +328,46 @@ pub enum DBErrorKind {
Utf8, Utf8,
Other, Other,
} }
#[derive(Debug, PartialEq)]
pub enum ParseError<'a> {
Anki(&'a str, ParseErrorKind),
Nom(&'a str, NomErrorKind),
}
#[derive(Debug, PartialEq)]
pub enum ParseErrorKind {
MisplacedAnd,
MisplacedOr,
EmptyGroup,
EmptyQuote,
UnclosedQuote,
MissingKey,
UnknownEscape(String),
InvalidIdList,
InvalidState,
InvalidFlag,
InvalidAdded,
InvalidEdited,
InvalidRatedDays,
InvalidRatedEase,
InvalidDupesMid,
InvalidDupesText,
InvalidPropProperty,
InvalidPropOperator,
InvalidPropFloat,
InvalidPropInteger,
InvalidPropUnsigned,
InvalidDid,
InvalidMid,
}
impl<'a> NomParseError<&'a str> for ParseError<'a> {
fn from_error_kind(input: &'a str, kind: NomErrorKind) -> Self {
ParseError::Nom(input, kind)
}
fn append(_: &str, _: NomErrorKind, other: Self) -> Self {
other
}
}

View File

@ -3,7 +3,7 @@
use crate::{ use crate::{
decks::DeckID, decks::DeckID,
err::{AnkiError, Result}, err::{AnkiError, ParseError, ParseErrorKind, Result},
notetype::NoteTypeID, notetype::NoteTypeID,
}; };
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -12,58 +12,19 @@ use nom::{
bytes::complete::{escaped, is_not, tag}, bytes::complete::{escaped, is_not, tag},
character::complete::{anychar, char, none_of, one_of}, character::complete::{anychar, char, none_of, one_of},
combinator::{all_consuming, map, verify}, combinator::{all_consuming, map, verify},
error::{ErrorKind as NomErrorKind, ParseError as NomParseError}, error::ErrorKind as NomErrorKind,
multi::many0, multi::many0,
sequence::{delimited, preceded, separated_pair}, sequence::{delimited, preceded, separated_pair},
Err::{Error, Failure},
}; };
use regex::{Captures, Regex}; use regex::{Captures, Regex};
use std::borrow::Cow; use std::borrow::Cow;
use ParseErrorKind::*;
#[derive(Debug)]
enum ParseError<'a> {
Anki(&'a str, ErrorKind),
Nom(&'a str, NomErrorKind),
}
#[derive(Debug)]
enum ErrorKind {
MisplacedAnd,
MisplacedOr,
EmptyGroup,
EmptyQuote,
UnclosedQuote,
MissingKey,
UnknownEscape(String),
InvalidIdList,
InvalidState,
InvalidFlag,
InvalidAdded,
InvalidEdited,
InvalidRatedDays,
InvalidRatedEase,
InvalidDupesMid,
InvalidDupesText,
InvalidPropProperty,
InvalidPropOperator,
InvalidPropFloat,
InvalidPropInteger,
InvalidPropUnsigned,
InvalidDid,
InvalidMid,
}
type IResult<'a, O> = std::result::Result<(&'a str, O), nom::Err<ParseError<'a>>>; type IResult<'a, O> = std::result::Result<(&'a str, O), nom::Err<ParseError<'a>>>;
type ParseResult<'a, O> = std::result::Result<O, nom::Err<ParseError<'a>>>; type ParseResult<'a, O> = std::result::Result<O, nom::Err<ParseError<'a>>>;
impl<'a> NomParseError<&'a str> for ParseError<'a> { fn parse_failure(input: &str, kind: ParseErrorKind) -> nom::Err<ParseError<'_>> {
fn from_error_kind(input: &'a str, kind: NomErrorKind) -> Self { nom::Err::Failure(ParseError::Anki(input, kind))
ParseError::Nom(input, kind)
}
fn append(_: &str, _: NomErrorKind, other: Self) -> Self {
other
}
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -171,9 +132,9 @@ fn group_inner(input: &str) -> IResult<Vec<Node>> {
// before adding the node, if the length is even then the node // before adding the node, if the length is even then the node
// must not be a boolean // must not be a boolean
if node == Node::And { if node == Node::And {
return Err(Failure(ParseError::Anki(input, ErrorKind::MisplacedAnd))); return Err(parse_failure(input, MisplacedAnd));
} else if node == Node::Or { } else if node == Node::Or {
return Err(Failure(ParseError::Anki(input, ErrorKind::MisplacedOr))); return Err(parse_failure(input, MisplacedOr));
} }
} else { } else {
// if the length is odd, the next item must be a boolean. if it's // if the length is odd, the next item must be a boolean. if it's
@ -193,11 +154,11 @@ fn group_inner(input: &str) -> IResult<Vec<Node>> {
} }
if nodes.is_empty() { if nodes.is_empty() {
Err(Failure(ParseError::Anki(input, ErrorKind::EmptyGroup))) Err(parse_failure(input, EmptyGroup))
} else if nodes.last().unwrap() == &Node::And { } else if nodes.last().unwrap() == &Node::And {
Err(Failure(ParseError::Anki(input, ErrorKind::MisplacedAnd))) Err(parse_failure(input, MisplacedAnd))
} else if nodes.last().unwrap() == &Node::Or { } else if nodes.last().unwrap() == &Node::Or {
Err(Failure(ParseError::Anki(input, ErrorKind::MisplacedOr))) Err(parse_failure(input, MisplacedOr))
} else { } else {
// chomp any trailing whitespace // chomp any trailing whitespace
let (remaining, _) = whitespace0(remaining)?; let (remaining, _) = whitespace0(remaining)?;
@ -245,10 +206,7 @@ fn partially_quoted_term(s: &str) -> IResult<Node> {
quoted_term_str, quoted_term_str,
)(s)?; )(s)?;
if key.is_empty() { if key.is_empty() {
Err(nom::Err::Failure(ParseError::Anki( Err(parse_failure(s, MissingKey))
s,
ErrorKind::MissingKey,
)))
} else { } else {
Ok(( Ok((
remaining, remaining,
@ -266,10 +224,7 @@ fn unquoted_term(s: &str) -> IResult<Node> {
{ {
if tail.starts_with('\\') { if tail.starts_with('\\') {
let escaped = (if tail.len() > 1 { &tail[0..2] } else { "" }).to_string(); let escaped = (if tail.len() > 1 { &tail[0..2] } else { "" }).to_string();
Err(Failure(ParseError::Anki( Err(parse_failure(s, UnknownEscape(format!("\\{}", escaped))))
s,
ErrorKind::UnknownEscape(format!("\\{}", escaped)),
)))
} else if term.eq_ignore_ascii_case("and") { } else if term.eq_ignore_ascii_case("and") {
Ok((tail, Node::And)) Ok((tail, Node::And))
} else if term.eq_ignore_ascii_case("or") { } else if term.eq_ignore_ascii_case("or") {
@ -279,12 +234,9 @@ fn unquoted_term(s: &str) -> IResult<Node> {
} }
} else if s.starts_with('\\') { } else if s.starts_with('\\') {
let escaped = (if s.len() > 1 { &s[0..2] } else { "" }).to_string(); let escaped = (if s.len() > 1 { &s[0..2] } else { "" }).to_string();
Err(Failure(ParseError::Anki( Err(parse_failure(s, UnknownEscape(format!("\\{}", escaped))))
s,
ErrorKind::UnknownEscape(format!("\\{}", escaped)),
)))
} else { } else {
Err(Error(ParseError::Nom(s, NomErrorKind::Verify))) Err(nom::Err::Error(ParseError::Nom(s, NomErrorKind::Verify)))
} }
} }
@ -295,17 +247,17 @@ fn quoted_term_str(s: &str) -> IResult<&str> {
escaped::<_, ParseError, _, _, _, _>(is_not(r#""\"#), '\\', anychar)(opened) escaped::<_, ParseError, _, _, _, _>(is_not(r#""\"#), '\\', anychar)(opened)
{ {
if tail.is_empty() { if tail.is_empty() {
Err(Failure(ParseError::Anki(s, ErrorKind::UnclosedQuote))) Err(parse_failure(s, UnclosedQuote))
} else if let Ok((remaining, _)) = char::<_, ParseError>('"')(tail) { } else if let Ok((remaining, _)) = char::<_, ParseError>('"')(tail) {
Ok((remaining, inner)) Ok((remaining, inner))
} else { } else {
Err(Failure(ParseError::Anki(s, ErrorKind::UnclosedQuote))) Err(parse_failure(s, UnclosedQuote))
} }
} else { } else {
match opened.chars().next().unwrap() { match opened.chars().next().unwrap() {
'"' => Err(Failure(ParseError::Anki(s, ErrorKind::EmptyQuote))), '"' => Err(parse_failure(s, EmptyQuote)),
// '\' followed by nothing // '\' followed by nothing
'\\' => Err(Failure(ParseError::Anki(s, ErrorKind::UnclosedQuote))), '\\' => Err(parse_failure(s, UnclosedQuote)),
// everything else is accepted by escaped // everything else is accepted by escaped
_ => unreachable!(), _ => unreachable!(),
} }
@ -324,7 +276,7 @@ fn search_node_for_text(s: &str) -> ParseResult<SearchNode> {
} else { } else {
// escaped only fails on "\" and leading ':' // escaped only fails on "\" and leading ':'
// "\" cannot be passed as an argument by a calling parser // "\" cannot be passed as an argument by a calling parser
Err(Failure(ParseError::Anki(s, ErrorKind::MissingKey))) Err(parse_failure(s, MissingKey))
} }
} }
@ -368,12 +320,12 @@ fn parse_template(s: &str) -> ParseResult<SearchNode> {
fn parse_flag(s: &str) -> ParseResult<SearchNode> { fn parse_flag(s: &str) -> ParseResult<SearchNode> {
if let Ok(flag) = s.parse::<u8>() { if let Ok(flag) = s.parse::<u8>() {
if flag > 4 { if flag > 4 {
Err(Failure(ParseError::Anki(s, ErrorKind::InvalidFlag))) Err(parse_failure(s, InvalidFlag))
} else { } else {
Ok(SearchNode::Flag(flag)) Ok(SearchNode::Flag(flag))
} }
} else { } else {
Err(Failure(ParseError::Anki(s, ErrorKind::InvalidEdited))) Err(parse_failure(s, InvalidEdited))
} }
} }
@ -387,7 +339,7 @@ fn parse_prop(s: &str) -> ParseResult<SearchNode<'static>> {
tag("ease"), tag("ease"),
tag("pos"), tag("pos"),
))(s) ))(s)
.map_err(|_| Failure(ParseError::Anki(s, ErrorKind::InvalidPropProperty)))?; .map_err(|_| parse_failure(s, InvalidPropProperty))?;
let (num, operator) = alt::<&str, &str, ParseError, _>(( let (num, operator) = alt::<&str, &str, ParseError, _>((
tag("<="), tag("<="),
@ -397,19 +349,19 @@ fn parse_prop(s: &str) -> ParseResult<SearchNode<'static>> {
tag("<"), tag("<"),
tag(">"), tag(">"),
))(tail) ))(tail)
.map_err(|_| Failure(ParseError::Anki(s, ErrorKind::InvalidPropOperator)))?; .map_err(|_| parse_failure(s, InvalidPropOperator))?;
let kind = if prop == "ease" { let kind = if prop == "ease" {
if let Ok(f) = num.parse::<f32>() { if let Ok(f) = num.parse::<f32>() {
PropertyKind::Ease(f) PropertyKind::Ease(f)
} else { } else {
return Err(Failure(ParseError::Anki(s, ErrorKind::InvalidPropFloat))); return Err(parse_failure(s, InvalidPropFloat));
} }
} else if prop == "due" { } else if prop == "due" {
if let Ok(i) = num.parse::<i32>() { if let Ok(i) = num.parse::<i32>() {
PropertyKind::Due(i) PropertyKind::Due(i)
} else { } else {
return Err(Failure(ParseError::Anki(s, ErrorKind::InvalidPropInteger))); return Err(parse_failure(s, InvalidPropInteger));
} }
} else if let Ok(u) = num.parse::<u32>() { } else if let Ok(u) = num.parse::<u32>() {
match prop { match prop {
@ -420,7 +372,7 @@ fn parse_prop(s: &str) -> ParseResult<SearchNode<'static>> {
_ => unreachable!(), _ => unreachable!(),
} }
} else { } else {
return Err(Failure(ParseError::Anki(s, ErrorKind::InvalidPropUnsigned))); return Err(parse_failure(s, InvalidPropUnsigned));
}; };
Ok(SearchNode::Property { Ok(SearchNode::Property {
@ -434,7 +386,7 @@ fn parse_added(s: &str) -> ParseResult<SearchNode> {
if let Ok(days) = s.parse::<u32>() { if let Ok(days) = s.parse::<u32>() {
Ok(SearchNode::AddedInDays(days.max(1))) Ok(SearchNode::AddedInDays(days.max(1)))
} else { } else {
Err(Failure(ParseError::Anki(s, ErrorKind::InvalidAdded))) Err(parse_failure(s, InvalidAdded))
} }
} }
@ -443,7 +395,7 @@ fn parse_edited(s: &str) -> ParseResult<SearchNode> {
if let Ok(days) = s.parse::<u32>() { if let Ok(days) = s.parse::<u32>() {
Ok(SearchNode::EditedInDays(days.max(1))) Ok(SearchNode::EditedInDays(days.max(1)))
} else { } else {
Err(Failure(ParseError::Anki(s, ErrorKind::InvalidEdited))) Err(parse_failure(s, InvalidEdited))
} }
} }
@ -458,17 +410,17 @@ fn parse_rated(s: &str) -> ParseResult<SearchNode> {
if u < 5 { if u < 5 {
Some(u) Some(u)
} else { } else {
return Err(Failure(ParseError::Anki(s, ErrorKind::InvalidRatedEase))); return Err(parse_failure(s, InvalidRatedEase));
} }
} else { } else {
return Err(Failure(ParseError::Anki(s, ErrorKind::InvalidRatedEase))); return Err(parse_failure(s, InvalidRatedEase));
} }
} else { } else {
None None
}; };
Ok(SearchNode::Rated { days, ease }) Ok(SearchNode::Rated { days, ease })
} else { } else {
Err(Failure(ParseError::Anki(s, ErrorKind::InvalidRatedDays))) Err(parse_failure(s, InvalidRatedDays))
} }
} }
@ -484,7 +436,7 @@ fn parse_state(s: &str) -> ParseResult<SearchNode> {
"buried-manually" => UserBuried, "buried-manually" => UserBuried,
"buried-sibling" => SchedBuried, "buried-sibling" => SchedBuried,
"suspended" => Suspended, "suspended" => Suspended,
_ => return Err(Failure(ParseError::Anki(s, ErrorKind::InvalidState))), _ => return Err(parse_failure(s, InvalidState)),
})) }))
} }
@ -492,7 +444,7 @@ fn parse_did(s: &str) -> ParseResult<SearchNode> {
if let Ok(did) = s.parse() { if let Ok(did) = s.parse() {
Ok(SearchNode::DeckID(did)) Ok(SearchNode::DeckID(did))
} else { } else {
Err(Failure(ParseError::Anki(s, ErrorKind::InvalidDid))) Err(parse_failure(s, InvalidDid))
} }
} }
@ -500,7 +452,7 @@ fn parse_mid(s: &str) -> ParseResult<SearchNode> {
if let Ok(mid) = s.parse() { if let Ok(mid) = s.parse() {
Ok(SearchNode::NoteTypeID(mid)) Ok(SearchNode::NoteTypeID(mid))
} else { } else {
Err(Failure(ParseError::Anki(s, ErrorKind::InvalidMid))) Err(parse_failure(s, InvalidMid))
} }
} }
@ -513,7 +465,7 @@ fn check_id_list(s: &str) -> ParseResult<&str> {
if RE.is_match(s) { if RE.is_match(s) {
Ok(s) Ok(s)
} else { } else {
Err(Failure(ParseError::Anki(s, ErrorKind::InvalidIdList))) Err(parse_failure(s, InvalidIdList))
} }
} }
@ -527,10 +479,10 @@ fn parse_dupes(s: &str) -> ParseResult<SearchNode> {
text: unescape_quotes(text), text: unescape_quotes(text),
}) })
} else { } else {
Err(Failure(ParseError::Anki(s, ErrorKind::InvalidDupesText))) Err(parse_failure(s, InvalidDupesText))
} }
} else { } else {
Err(Failure(ParseError::Anki(s, ErrorKind::InvalidDupesMid))) Err(parse_failure(s, InvalidDupesMid))
} }
} }
@ -562,10 +514,7 @@ fn unescape_quotes(s: &str) -> Cow<str> {
/// Unescape chars with special meaning to the parser. /// Unescape chars with special meaning to the parser.
fn unescape(txt: &str) -> ParseResult<Cow<str>> { fn unescape(txt: &str) -> ParseResult<Cow<str>> {
if let Some(seq) = invalid_escape_sequence(txt) { if let Some(seq) = invalid_escape_sequence(txt) {
Err(Failure(ParseError::Anki( Err(parse_failure(txt, UnknownEscape(seq)))
txt,
ErrorKind::UnknownEscape(seq),
)))
} else { } else {
Ok(if is_parser_escape(txt) { Ok(if is_parser_escape(txt) {
lazy_static! { lazy_static! {