From 0b83efb63ed10ef0035ebfe4a704ed0086d9f06b Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 18 Jan 2021 10:41:03 +1000 Subject: [PATCH] simplify write_props() and associated translations This is a work in progress; see associated PR discussion to follow. --- ftl/core/search.ftl | 1 + rslib/src/err.rs | 4 + rslib/src/search/parser.rs | 169 ++++++++++++++++--------------------- 3 files changed, 78 insertions(+), 96 deletions(-) diff --git a/ftl/core/search.ftl b/ftl/core/search.ftl index fcae80877..8dc845d72 100644 --- a/ftl/core/search.ftl +++ b/ftl/core/search.ftl @@ -22,6 +22,7 @@ search-invalid-prop-operator = `prop:{ $val }` must be followed by one of the co search-invalid-prop-float = `prop:{ $val }` must be followed by a decimal number. search-invalid-prop-integer = `prop:{ $val }` must be followed by a whole number. search-invalid-prop-unsigned = `prop:{ $val }` must be followed by a non-negative whole number. +search-invalid-number = `{ $val }`: invalid number provided search-invalid-did = `did:` must be followed by a valid deck id. search-invalid-mid = `mid:` must be followed by a note type id. search-invalid-other = please check for typing mistakes. diff --git a/rslib/src/err.rs b/rslib/src/err.rs index 3f321fbbe..90f595e2b 100644 --- a/rslib/src/err.rs +++ b/rslib/src/err.rs @@ -163,6 +163,9 @@ impl AnkiError { SearchErrorKind::InvalidRatedEase(ctx) => i18n .trn(TR::SearchInvalidRatedEase, tr_strs!["val"=>(ctx)]) .into(), + SearchErrorKind::InvalidNumber(ctx) => i18n + .trn(TR::SearchInvalidNumber, tr_strs!["val"=>(ctx)]) + .into(), SearchErrorKind::InvalidResched => i18n .trn( TR::SearchInvalidFollowedByPositiveDays, @@ -454,6 +457,7 @@ pub enum SearchErrorKind { InvalidPropFloat(String), InvalidPropInteger(String), InvalidPropUnsigned(String), + InvalidNumber(String), InvalidDid, InvalidMid, Regex(String), diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index 6fdff4d8b..03f932a23 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -17,7 +17,11 @@ use nom::{ sequence::{preceded, separated_pair}, }; use regex::{Captures, Regex}; -use std::borrow::Cow; +use std::{ + borrow::Cow, + num::{ParseFloatError, ParseIntError}, + str::FromStr, +}; type IResult<'a, O> = std::result::Result<(&'a str, O), nom::Err>>; type ParseResult<'a, O> = std::result::Result>>; @@ -362,7 +366,7 @@ fn parse_resched(s: &str) -> ParseResult { } /// eg prop:ivl>3, prop:ease!=2.5 -fn parse_prop(s: &str) -> ParseResult { +fn parse_prop(prop_clause: &str) -> ParseResult { let (tail, prop) = alt::<_, _, ParseError, _>(( tag("ivl"), tag("due"), @@ -372,8 +376,13 @@ fn parse_prop(s: &str) -> ParseResult { tag("pos"), tag("rated"), tag("resched"), - ))(s) - .map_err(|_| parse_failure(s, FailKind::InvalidPropProperty(s.into())))?; + ))(prop_clause) + .map_err(|_| { + parse_failure( + prop_clause, + FailKind::InvalidPropProperty(prop_clause.into()), + ) + })?; let (num, operator) = alt::<_, _, ParseError, _>(( tag("<="), @@ -383,87 +392,21 @@ fn parse_prop(s: &str) -> ParseResult { tag("<"), tag(">"), ))(tail) - .map_err(|_| parse_failure(s, FailKind::InvalidPropOperator(prop.to_string())))?; + .map_err(|_| parse_failure(prop_clause, FailKind::InvalidPropOperator(prop.to_string())))?; - let kind = if prop == "ease" { - if let Ok(f) = num.parse::() { - PropertyKind::Ease(f) - } else { - return Err(parse_failure( - s, - FailKind::InvalidPropFloat(format!("{}{}", prop, operator)), - )); - } - } else if prop == "due" { - if let Ok(i) = num.parse::() { - PropertyKind::Due(i) - } else { - return Err(parse_failure( - s, - FailKind::InvalidPropInteger(format!("{}{}", prop, operator)), - )); - } - } else if prop == "rated" { - let mut it = num.splitn(2, ':'); - - let days: i32 = if let Ok(i) = it.next().unwrap().parse::() { - i.min(0) - } else { - return Err(parse_failure( - s, - FailKind::InvalidPropInteger(format!("{}{}", prop, operator)), - )); - }; - - let ease = match it.next() { - Some(v) => { - if let Ok(u) = v.parse::() { - if (1..5).contains(&u) { - EaseKind::AnswerButton(u) - } else { - return Err(parse_failure( - s, - FailKind::InvalidRatedEase(format!( - "prop:{}{}{}", - prop, - operator, - days.to_string() - )), - )); - } - } else { - return Err(parse_failure( - s, - FailKind::InvalidPropInteger(format!("{}{}", prop, operator)), - )); - } - } - None => EaseKind::AnyAnswerButton, - }; - - PropertyKind::Rated(days, ease) - } else if prop == "resched" { - if let Ok(days) = num.parse::() { - PropertyKind::Rated(days.min(0), EaseKind::ManualReschedule) - } else { - return Err(parse_failure( - s, - FailKind::InvalidPropInteger(format!("{}{}", prop, operator)), - )); - } - } else if let Ok(u) = num.parse::() { - match prop { - "ivl" => PropertyKind::Interval(u), - "reps" => PropertyKind::Reps(u), - "lapses" => PropertyKind::Lapses(u), - "pos" => PropertyKind::Position(u), - _ => unreachable!(), - } - } else { - return Err(parse_failure( - s, - FailKind::InvalidPropUnsigned(format!("{}{}", prop, operator)), - )); + let kind = match prop { + "ease" => PropertyKind::Ease(parse_prop_float(num, prop_clause)?), + "due" => PropertyKind::Due(parse_prop_integer(num, prop_clause)?), + "rated" => parse_prop_rated(num, prop_clause)?, + "resched" => PropertyKind::Rated( + parse_prop_integer::(num, prop_clause)?.min(0), + EaseKind::ManualReschedule, + ), + "ivl" => PropertyKind::Interval(parse_prop_integer(num, prop_clause)?), + "reps" => PropertyKind::Reps(parse_prop_integer(num, prop_clause)?), + "lapses" => PropertyKind::Lapses(parse_prop_integer(num, prop_clause)?), + "pos" => PropertyKind::Position(parse_prop_integer(num, prop_clause)?), + _ => unreachable!(), }; Ok(SearchNode::Property { @@ -472,6 +415,43 @@ fn parse_prop(s: &str) -> ParseResult { }) } +fn parse_prop_float<'a, N>(num: &str, prop_clause: &'a str) -> ParseResult<'a, N> +where + N: FromStr, + ::Err: PartialEq, +{ + num.parse() + .map_err(|_e| parse_failure(prop_clause, FailKind::InvalidNumber(prop_clause.to_owned()))) +} + +fn parse_prop_integer<'a, N>(num: &str, prop_clause: &'a str) -> ParseResult<'a, N> +where + N: FromStr, + ::Err: PartialEq, +{ + num.parse() + .map_err(|_e| parse_failure(prop_clause, FailKind::InvalidNumber(prop_clause.to_owned()))) +} + +fn parse_prop_rated<'a>(num: &str, prop_clause: &'a str) -> ParseResult<'a, PropertyKind> { + let mut it = num.splitn(2, ':'); + let days = parse_prop_integer::(it.next().unwrap(), prop_clause)?.min(0); + let ease = match it.next() { + Some(v) => match parse_prop_integer(v, prop_clause)? { + u @ 1..=5 => EaseKind::AnswerButton(u), + _ => { + return Err(parse_failure( + prop_clause, + FailKind::InvalidRatedEase(prop_clause.to_owned()), + )) + } + }, + None => EaseKind::AnyAnswerButton, + }; + + Ok(PropertyKind::Rated(days, ease)) +} + /// eg added:1 fn parse_added(s: &str) -> ParseResult { if let Ok(days) = s.parse::() { @@ -947,20 +927,17 @@ mod test { assert_err_kind("prop:pos~1", InvalidPropOperator("pos".to_string())); assert_err_kind("prop:reps10", InvalidPropOperator("reps".to_string())); - assert_err_kind("prop:ease>", InvalidPropFloat("ease>".to_string())); - assert_err_kind("prop:ease!=one", InvalidPropFloat("ease!=".to_string())); - assert_err_kind("prop:ease<1,3", InvalidPropFloat("ease<".to_string())); + assert_err_kind("prop:ease>", InvalidNumber("ease>".to_string())); + assert_err_kind("prop:ease!=one", InvalidNumber("ease!=one".to_string())); + assert_err_kind("prop:ease<1,3", InvalidNumber("ease<1,3".to_string())); - assert_err_kind("prop:due>", InvalidPropInteger("due>".to_string())); - assert_err_kind("prop:due=0.5", InvalidPropInteger("due=".to_string())); - assert_err_kind("prop:due", InvalidNumber("due>".to_string())); + assert_err_kind("prop:due=0.5", InvalidNumber("due=0.5".to_string())); + assert_err_kind("prop:due", InvalidPropUnsigned("ivl>".to_string())); - assert_err_kind("prop:reps=1.1", InvalidPropUnsigned("reps=".to_string())); - assert_err_kind( - "prop:lapses!=-1", - InvalidPropUnsigned("lapses!=".to_string()), - ); + assert_err_kind("prop:ivl>", InvalidNumber("ivl>".to_string())); + assert_err_kind("prop:reps=1.1", InvalidNumber("reps=1.1".to_string())); + assert_err_kind("prop:lapses!=-1", InvalidNumber("lapses!=-1".to_string())); Ok(()) }