Better parser errors

This commit is contained in:
Jeremy Wall 2021-12-04 09:51:39 -05:00
parent 78921d973c
commit b46e5746b9
2 changed files with 52 additions and 7 deletions

View File

@ -16,7 +16,7 @@ use std::time::Duration;
use abortable_parser::{ use abortable_parser::{
ascii_digit, consume_all, discard, do_each, either, eoi, make_fn, must, not, optional, peek, ascii_digit, consume_all, discard, do_each, either, eoi, make_fn, must, not, optional, peek,
repeat, separated, text_token, trap, until, Result, StrIter, repeat, separated, text_token, trap, until, with_err, Result, StrIter,
}; };
use inflector::Inflector; use inflector::Inflector;
use num_rational::Ratio; use num_rational::Ratio;
@ -37,7 +37,7 @@ pub fn as_recipe(i: &str) -> std::result::Result<Recipe, String> {
make_fn!( make_fn!(
pub recipe<StrIter, Recipe>, pub recipe<StrIter, Recipe>,
do_each!( do_each!(
title => title, title => must!(title),
_ => optional!(para_separator), _ => optional!(para_separator),
desc => optional!(do_each!( desc => optional!(do_each!(
_ => peek!(not!(step_prefix)), _ => peek!(not!(step_prefix)),
@ -127,7 +127,7 @@ make_fn!(
pub step<StrIter, Step>, pub step<StrIter, Step>,
do_each!( do_each!(
dur => step_prefix, dur => step_prefix,
ingredients => must!(ingredient_list), ingredients => with_err!(must!(ingredient_list), "Missing ingredient list"),
_ => para_separator, _ => para_separator,
desc => description, desc => description,
_ => either!(discard!(para_separator), eoi), _ => either!(discard!(para_separator), eoi),
@ -138,7 +138,7 @@ make_fn!(
make_fn!( make_fn!(
pub step_list<StrIter, Vec<Step>>, pub step_list<StrIter, Vec<Step>>,
do_each!( do_each!(
first_step => must!(step), first_step => with_err!(must!(step), "Missing recipe steps"),
rest => repeat!(step), rest => repeat!(step),
({ ({
let mut steps = vec![first_step]; let mut steps = vec![first_step];
@ -303,6 +303,21 @@ pub fn measure(i: StrIter) -> abortable_parser::Result<StrIter, Measure> {
} }
} }
pub fn normalize_name(name: &str) -> String {
let parts: Vec<&str> = name.split_whitespace().collect();
if parts.len() >= 2 {
let mut prefix = parts[0..parts.len() - 1].join(" ");
// NOTE(jwall): The below unwrap is safe because of the length
// check above.
let last = parts.last().unwrap();
let normalized = last.to_singular();
prefix.push(' ');
prefix.push_str(&normalized);
return prefix;
}
return name.trim().to_owned();
}
make_fn!( make_fn!(
pub ingredient_name<StrIter, String>, pub ingredient_name<StrIter, String>,
do_each!( do_each!(
@ -310,7 +325,7 @@ make_fn!(
discard!(text_token!("\n")), discard!(text_token!("\n")),
eoi, eoi,
discard!(text_token!("(")))), discard!(text_token!("(")))),
(name.trim().to_singular()) (normalize_name(name))
) )
); );
@ -318,8 +333,8 @@ make_fn!(
ingredient_modifier<StrIter, &str>, ingredient_modifier<StrIter, &str>,
do_each!( do_each!(
_ => text_token!("("), _ => text_token!("("),
modifier => until!(text_token!(")")), modifier => must!(until!(text_token!(")"))),
_ => text_token!(")"), _ => must!(text_token!(")")),
(modifier) (modifier)
) )
); );

View File

@ -466,3 +466,33 @@ until thickened. Set aside to cool.
err => assert!(false, "{:?}", err), err => assert!(false, "{:?}", err),
} }
} }
#[test]
fn test_recipe_missing_steps_parse_failure() {
let recipe = "title: gooey apple bake
A simple gooey apple bake recipe.
";
match parse::recipe(StrIter::new(recipe)) {
ParseResult::Abort(e) => {
assert_eq!(e.get_msg(), "Missing recipe steps");
}
other => assert!(false, "{:?}", other),
}
}
#[test]
fn test_step_no_ingredients_parse_failure() {
let step = "step:
step: ";
match parse::step(StrIter::new(step)) {
ParseResult::Abort(e) => {
eprintln!("err: {:?}", e);
assert_eq!(e.get_msg(), "Missing ingredient list");
}
other => {
assert!(false, "{:?}", other);
}
}
}