From bdb42ac8466b6f4f78248d27e85ccb1782c5a722 Mon Sep 17 00:00:00 2001 From: p6nj Date: Sat, 9 Nov 2024 17:18:25 -0500 Subject: [PATCH] verbose error support --- poc/poc.yml | 46 ++++++++------- src/bng/score/de.rs | 69 ++++++++-------------- src/bng/score/lex/lexer.rs | 99 +++++++++++++++++--------------- src/bng/score/lex/lexer/tests.rs | 31 +++++----- 4 files changed, 120 insertions(+), 125 deletions(-) diff --git a/poc/poc.yml b/poc/poc.yml index 4e0f25d..f4c5e2f 100644 --- a/poc/poc.yml +++ b/poc/poc.yml @@ -15,7 +15,7 @@ channels: instr: sine score: notes: cCdDefFgGaAb - sheet: + sheet: | aabc. 'ab°A +d+d+d--- @@ -28,24 +28,26 @@ channels: [ffe] {l 1-cos((PI*x)/2),acced} abbc!o5cc!v15feed!l4fedd!t60Gdd - # rest: . - # pizz.: '° - # volume: +- - # length: /\ - # octave: >< - # comment?: ;, - # start here: ':' - # slope: {MODIFIER EXPR, score} - # note modifier prefix: n - # volume modifier prefix: v - # octave modifier prefix: o - # length modifier prefix: l - # tempo modifier prefix: t - # loop: () - # loop with count: (COUNT, score) - # tuple: [] - # modifier: ! - # volume modifier prefix: v - # octave modifier prefix: o - # length modifier prefix: l - # tempo modifier prefix: t + + ; syntax : + rest: . + pizz.: '° + volume: +- + length: /\ + octave: >< + comment?: ;, + start here: ':' + slope: {MODIFIER EXPR, score} + note modifier prefix: n + volume modifier prefix: v + octave modifier prefix: o + length modifier prefix: l + tempo modifier prefix: t + loop: () + loop with count: (COUNT, score) + tuple: [] + modifier: ! + volume modifier prefix: v + octave modifier prefix: o + length modifier prefix: l + tempo modifier prefix: t, diff --git a/src/bng/score/de.rs b/src/bng/score/de.rs index 0ee1b57..bb09e3e 100644 --- a/src/bng/score/de.rs +++ b/src/bng/score/de.rs @@ -1,10 +1,13 @@ +use std::fmt::{Debug, Display}; + use derive_new::new; use nom::{ character::complete::one_of, combinator::all_consuming, + error::{context, convert_error, ContextError, ParseError, VerboseError}, multi::{many0, many1}, sequence::{preceded, terminated}, - Parser, + Needed, Parser, }; use serde::{ de::{self, Deserializer, Visitor}, @@ -21,29 +24,12 @@ use super::{ #[derive(Debug, Error)] enum AtomsSerializeError { - #[error("sheet parsing error: {0}")] + #[error("sheet parsing error:\n{0}")] Parsing(String), #[error("sheet semantics: {0}")] Inflation(#[from] InflateError), } -fn nom_err_message(e: nom::Err>) -> String { - match e { - nom::Err::Incomplete(needed) => format!( - "input is incomplete, needed {} byte(s) more", - match needed { - nom::Needed::Unknown => "?".to_string(), - nom::Needed::Size(s) => s.to_string(), - } - ), - nom::Err::Error(e) | nom::Err::Failure(e) => format!( - "got error code {code:#?} at \"{input}\"", - code = e.code, - input = e.input - ), - } -} - impl<'de> Deserialize<'de> for Atoms { fn deserialize(deserializer: D) -> Result where @@ -64,37 +50,30 @@ impl<'de> Deserialize<'de> for Atoms { if sheet.is_empty() { Ok(Default::default()) } else { - atom_mapper::(&sheet, atom(¬es)) + all_consuming(terminated( + many1(preceded(maybe_yml_str_space(), atom(¬es))), + maybe_yml_str_space(), + ))(&sheet) + .map_err(|e: nom::Err>| match e { + nom::Err::Incomplete(Needed::Unknown) => "needed some more bytes".to_string(), + nom::Err::Incomplete(Needed::Size(n)) => format!("needed {} more bytes", n), + nom::Err::Error(e) | nom::Err::Failure(e) => convert_error(sheet.as_str(), e), + }) + .map_err(AtomsSerializeError::Parsing) + .map_err(de::Error::custom) + .and_then(|(_, v)| { + inflate(v) + .map_err(AtomsSerializeError::from) + .map_err(de::Error::custom) + }) + .map(Atoms) } } } fn maybe_yml_str_space<'a, E>() -> impl Parser<&'a str, Vec, E> where - E: nom::error::ParseError<&'a str>, + E: nom::error::ParseError<&'a str> + ContextError<&'a str>, { - many0(one_of(" \t\r")) -} - -fn atom_mapper<'a, 'de, D, P>( - input: &'a str, - parser: P, -) -> Result>::Error> -where - D: serde::Deserializer<'de>, - P: Parser<&'a str, FlatAtom, nom::error::Error<&'a str>>, -{ - all_consuming(terminated( - many1(preceded(maybe_yml_str_space(), parser)), - maybe_yml_str_space(), - ))(input) - .map_err(nom_err_message) - .map_err(AtomsSerializeError::Parsing) - .map_err(de::Error::custom) - .and_then(|(_, v)| { - inflate(v) - .map_err(AtomsSerializeError::from) - .map_err(de::Error::custom) - }) - .map(Atoms) + context("yml white space", many0(one_of(" \t\r\n"))) } diff --git a/src/bng/score/lex/lexer.rs b/src/bng/score/lex/lexer.rs index 68ce93c..aa98244 100644 --- a/src/bng/score/lex/lexer.rs +++ b/src/bng/score/lex/lexer.rs @@ -1,6 +1,6 @@ use std::{ collections::BTreeMap, - num::{NonZeroU16, NonZeroU8}, + num::{NonZeroU16, NonZeroU8, TryFromIntError}, }; use clap::builder::TypedValueParser; @@ -10,7 +10,7 @@ use nom::{ bytes::complete::{take_till, take_till1}, character::complete::{anychar, char, one_of, space1, u16, u8}, combinator::{map_opt, map_res, opt, value, verify}, - error::{context, ContextError, ParseError}, + error::{context, ContextError, ErrorKind, FromExternalError, ParseError}, multi::many0, sequence::{delimited, pair, preceded, separated_pair, terminated}, Err, IResult, Parser, @@ -25,50 +25,59 @@ use super::{ #[cfg(test)] mod tests; -pub fn atom(notes: &str) -> impl Parser<&str, FlatAtom, nom::error::Error<&str>> { - alt(( - map_res(map_opt(one_of(notes), |c| notes.find(c)), u8::try_from).map(FlatAtom::Note), - value(FlatAtom::Rest, char(Atom::REST)), - value(FlatAtom::StartHere, char(Atom::START_HERE)), - preceded(char(Atom::MODIFIER), modifier).map(FlatAtom::Modifier), - quick_modifier.map(FlatAtom::QuickModifier), - preceded( - char(Atom::LOOP.0), - map_opt(opt(u8), |n| { - if let Some(n) = n { - NonZeroU8::new(n) - } else { - unsafe { Some(NonZeroU8::new_unchecked(2)) } - } - }), - ) - .map(FlatAtom::LoopStarts), - value(FlatAtom::LoopEnds, char(Atom::LOOP.1)), - value(FlatAtom::TupleStarts, char(Atom::TUPLE.0)), - value(FlatAtom::TupleEnds, char(Atom::TUPLE.1)), - terminated( +pub fn atom<'a, E>(notes: &'a str) -> impl Parser<&'a str, FlatAtom, E> +where + E: ParseError<&'a str> + + ContextError<&'a str> + + FromExternalError<&'a str, TryFromIntError> + + FromExternalError<&'a str, E>, +{ + context( + "atom", + alt(( + map_res(map_opt(one_of(notes), |c| notes.find(c)), u8::try_from).map(FlatAtom::Note), + value(FlatAtom::Rest, char(Atom::REST)), + value(FlatAtom::StartHere, char(Atom::START_HERE)), + preceded(char(Atom::MODIFIER), modifier).map(FlatAtom::Modifier), + quick_modifier.map(FlatAtom::QuickModifier), preceded( - char(Atom::SLOPE.0), - separated_pair( - slope_modifier, - char(' '), - map_res(take_till1(|c| c == ','), |s: &str| { - s.parse() - .map_err(|_| nom::error::Error::new(s, nom::error::ErrorKind::Verify)) - }), + char(Atom::LOOP.0), + map_opt(opt(u8), |n| { + if let Some(n) = n { + NonZeroU8::new(n) + } else { + unsafe { Some(NonZeroU8::new_unchecked(2)) } + } + }), + ) + .map(FlatAtom::LoopStarts), + value(FlatAtom::LoopEnds, char(Atom::LOOP.1)), + value(FlatAtom::TupleStarts, char(Atom::TUPLE.0)), + value(FlatAtom::TupleEnds, char(Atom::TUPLE.1)), + terminated( + preceded( + char(Atom::SLOPE.0), + separated_pair( + slope_modifier, + char(' '), + map_res(take_till1(|c| c == ','), |s: &str| { + s.parse() + .map_err(|_| E::from_error_kind(s, ErrorKind::Verify)) + }), + ), + ), + char(','), + ) + .map(|(sm, i)| FlatAtom::SlopeStarts(sm, i)), + value(FlatAtom::SlopeEnds, char(Atom::SLOPE.1)), + value( + FlatAtom::Comment, + delimited( + char(Atom::COMMENT.0), + take_till(|c| c == Atom::COMMENT.1), + char(Atom::COMMENT.1), ), ), - char(','), - ) - .map(|(sm, i)| FlatAtom::SlopeStarts(sm, i)), - value(FlatAtom::SlopeEnds, char(Atom::SLOPE.1)), - value( - FlatAtom::Comment, - delimited( - char(Atom::COMMENT.0), - take_till(|c| c == Atom::COMMENT.1), - char(Atom::COMMENT.1), - ), - ), - )) + )), + ) } diff --git a/src/bng/score/lex/lexer/tests.rs b/src/bng/score/lex/lexer/tests.rs index a343048..f7a8bcf 100644 --- a/src/bng/score/lex/lexer/tests.rs +++ b/src/bng/score/lex/lexer/tests.rs @@ -35,7 +35,7 @@ mod flat_atom { fn note() { assert_eq!( Ok((SAMPLE_STR, FlatAtom::Note(2))), - atom("abcdefg").parse(concatcp!('c', SAMPLE_STR)) + atom::>("abcdefg").parse(concatcp!('c', SAMPLE_STR)) ) } @@ -43,7 +43,7 @@ mod flat_atom { fn rest() { assert_eq!( Ok((SAMPLE_STR, FlatAtom::Rest)), - atom("abcdefg").parse(concatcp!(Atom::REST, SAMPLE_STR)) + atom::>("abcdefg").parse(concatcp!(Atom::REST, SAMPLE_STR)) ) } @@ -51,7 +51,7 @@ mod flat_atom { fn start_here() { assert_eq!( Ok((SAMPLE_STR, FlatAtom::StartHere)), - atom("abcdefg").parse(concatcp!(Atom::START_HERE, SAMPLE_STR)) + atom::>("abcdefg").parse(concatcp!(Atom::START_HERE, SAMPLE_STR)) ) } @@ -62,7 +62,12 @@ mod flat_atom { SAMPLE_STR, FlatAtom::Modifier(Modifier::Length(unsafe { NonZeroU8::new_unchecked(2) })) )), - atom("abcdefg").parse(concatcp!(Atom::MODIFIER, Modifier::LENGTH, 2u8, SAMPLE_STR)) + atom::>("abcdefg").parse(concatcp!( + Atom::MODIFIER, + Modifier::LENGTH, + 2u8, + SAMPLE_STR + )) ) } @@ -73,7 +78,7 @@ mod flat_atom { SAMPLE_STR, FlatAtom::QuickModifier(QuickModifier::Length(UP)) )), - atom("abcdefg").parse(concatcp!(QuickModifier::LENGTH.0, SAMPLE_STR)) + atom::>("abcdefg").parse(concatcp!(QuickModifier::LENGTH.0, SAMPLE_STR)) ) } @@ -84,14 +89,14 @@ mod flat_atom { SAMPLE_STR, FlatAtom::LoopStarts(unsafe { NonZeroU8::new_unchecked(3) }) )), - atom("abcdefg").parse(concatcp!(Atom::LOOP.0, 3u8, SAMPLE_STR)) + atom::>("abcdefg").parse(concatcp!(Atom::LOOP.0, 3u8, SAMPLE_STR)) ); assert_eq!( Ok(( SAMPLE_STR, FlatAtom::LoopStarts(unsafe { NonZeroU8::new_unchecked(2) }) )), - atom("abcdefg").parse(concatcp!(Atom::LOOP.0, SAMPLE_STR)) + atom::>("abcdefg").parse(concatcp!(Atom::LOOP.0, SAMPLE_STR)) ); assert_eq!( Err(nom::Err::Error(Error::new( @@ -106,7 +111,7 @@ mod flat_atom { fn loop_ends() { assert_eq!( Ok((SAMPLE_STR, FlatAtom::LoopEnds)), - atom("abcdefg").parse(concatcp!(Atom::LOOP.1, SAMPLE_STR)) + atom::>("abcdefg").parse(concatcp!(Atom::LOOP.1, SAMPLE_STR)) ) } @@ -114,7 +119,7 @@ mod flat_atom { fn tuple_starts() { assert_eq!( Ok((SAMPLE_STR, FlatAtom::TupleStarts)), - atom("abcdefg").parse(concatcp!(Atom::TUPLE.0, SAMPLE_STR)) + atom::>("abcdefg").parse(concatcp!(Atom::TUPLE.0, SAMPLE_STR)) ) } @@ -122,7 +127,7 @@ mod flat_atom { fn tuple_ends() { assert_eq!( Ok((SAMPLE_STR, FlatAtom::TupleEnds)), - atom("abcdefg").parse(concatcp!(Atom::TUPLE.1, SAMPLE_STR)) + atom::>("abcdefg").parse(concatcp!(Atom::TUPLE.1, SAMPLE_STR)) ) } @@ -133,7 +138,7 @@ mod flat_atom { SAMPLE_STR, FlatAtom::SlopeStarts(SlopeModifier::Note, FASTEVAL_INSTRUCTION.parse().unwrap()) )), - atom("abcdefg").parse(concatcp!( + atom::>("abcdefg").parse(concatcp!( Atom::SLOPE.0, SlopeModifier::NOTE, ' ', @@ -148,7 +153,7 @@ mod flat_atom { fn slope_ends() { assert_eq!( Ok((SAMPLE_STR, FlatAtom::SlopeEnds)), - atom("abcdefg").parse(concatcp!(Atom::SLOPE.1, SAMPLE_STR)) + atom::>("abcdefg").parse(concatcp!(Atom::SLOPE.1, SAMPLE_STR)) ) } @@ -156,7 +161,7 @@ mod flat_atom { fn comment() { assert_eq!( Ok((SAMPLE_STR, FlatAtom::Comment)), - atom("abcdefg").parse(concatcp!( + atom::>("abcdefg").parse(concatcp!( Atom::COMMENT.0, "hi I'm a little pony", SAMPLE_STR,