verbose error support

This commit is contained in:
Ponj 2024-11-09 17:18:25 -05:00
parent a2566f1be3
commit bdb42ac846
Signed by: p6nj
GPG key ID: 6FED68D87C479A59
4 changed files with 120 additions and 125 deletions

View file

@ -15,7 +15,7 @@ channels:
instr: sine instr: sine
score: score:
notes: cCdDefFgGaAb notes: cCdDefFgGaAb
sheet: sheet: |
aabc. aabc.
'ab°A 'ab°A
+d+d+d--- +d+d+d---
@ -28,24 +28,26 @@ channels:
[ffe] [ffe]
{l 1-cos((PI*x)/2),acced} {l 1-cos((PI*x)/2),acced}
abbc!o5cc!v15feed!l4fedd!t60Gdd abbc!o5cc!v15feed!l4fedd!t60Gdd
# rest: .
# pizz.: '° ; syntax :
# volume: +- rest: .
# length: /\ pizz.:
# octave: >< volume: +-
# comment?: ;, length: /\
# start here: ':' octave: ><
# slope: {MODIFIER EXPR, score} comment?: ;,
# note modifier prefix: n start here: ':'
# volume modifier prefix: v slope: {MODIFIER EXPR, score}
# octave modifier prefix: o note modifier prefix: n
# length modifier prefix: l volume modifier prefix: v
# tempo modifier prefix: t octave modifier prefix: o
# loop: () length modifier prefix: l
# loop with count: (COUNT, score) tempo modifier prefix: t
# tuple: [] loop: ()
# modifier: ! loop with count: (COUNT, score)
# volume modifier prefix: v tuple: []
# octave modifier prefix: o modifier: !
# length modifier prefix: l volume modifier prefix: v
# tempo modifier prefix: t octave modifier prefix: o
length modifier prefix: l
tempo modifier prefix: t,

View file

@ -1,10 +1,13 @@
use std::fmt::{Debug, Display};
use derive_new::new; use derive_new::new;
use nom::{ use nom::{
character::complete::one_of, character::complete::one_of,
combinator::all_consuming, combinator::all_consuming,
error::{context, convert_error, ContextError, ParseError, VerboseError},
multi::{many0, many1}, multi::{many0, many1},
sequence::{preceded, terminated}, sequence::{preceded, terminated},
Parser, Needed, Parser,
}; };
use serde::{ use serde::{
de::{self, Deserializer, Visitor}, de::{self, Deserializer, Visitor},
@ -21,29 +24,12 @@ use super::{
#[derive(Debug, Error)] #[derive(Debug, Error)]
enum AtomsSerializeError { enum AtomsSerializeError {
#[error("sheet parsing error: {0}")] #[error("sheet parsing error:\n{0}")]
Parsing(String), Parsing(String),
#[error("sheet semantics: {0}")] #[error("sheet semantics: {0}")]
Inflation(#[from] InflateError), Inflation(#[from] InflateError),
} }
fn nom_err_message(e: nom::Err<nom::error::Error<&str>>) -> 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 { impl<'de> Deserialize<'de> for Atoms {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
@ -64,37 +50,30 @@ impl<'de> Deserialize<'de> for Atoms {
if sheet.is_empty() { if sheet.is_empty() {
Ok(Default::default()) Ok(Default::default())
} else { } else {
atom_mapper::<D, _>(&sheet, atom(&notes)) all_consuming(terminated(
many1(preceded(maybe_yml_str_space(), atom(&notes))),
maybe_yml_str_space(),
))(&sheet)
.map_err(|e: nom::Err<VerboseError<&str>>| 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<char>, E> fn maybe_yml_str_space<'a, E>() -> impl Parser<&'a str, Vec<char>, E>
where where
E: nom::error::ParseError<&'a str>, E: nom::error::ParseError<&'a str> + ContextError<&'a str>,
{ {
many0(one_of(" \t\r")) context("yml white space", many0(one_of(" \t\r\n")))
}
fn atom_mapper<'a, 'de, D, P>(
input: &'a str,
parser: P,
) -> Result<Atoms, <D as Deserializer<'de>>::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)
} }

View file

@ -1,6 +1,6 @@
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
num::{NonZeroU16, NonZeroU8}, num::{NonZeroU16, NonZeroU8, TryFromIntError},
}; };
use clap::builder::TypedValueParser; use clap::builder::TypedValueParser;
@ -10,7 +10,7 @@ use nom::{
bytes::complete::{take_till, take_till1}, bytes::complete::{take_till, take_till1},
character::complete::{anychar, char, one_of, space1, u16, u8}, character::complete::{anychar, char, one_of, space1, u16, u8},
combinator::{map_opt, map_res, opt, value, verify}, combinator::{map_opt, map_res, opt, value, verify},
error::{context, ContextError, ParseError}, error::{context, ContextError, ErrorKind, FromExternalError, ParseError},
multi::many0, multi::many0,
sequence::{delimited, pair, preceded, separated_pair, terminated}, sequence::{delimited, pair, preceded, separated_pair, terminated},
Err, IResult, Parser, Err, IResult, Parser,
@ -25,50 +25,59 @@ use super::{
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
pub fn atom(notes: &str) -> impl Parser<&str, FlatAtom, nom::error::Error<&str>> { pub fn atom<'a, E>(notes: &'a str) -> impl Parser<&'a str, FlatAtom, E>
alt(( where
map_res(map_opt(one_of(notes), |c| notes.find(c)), u8::try_from).map(FlatAtom::Note), E: ParseError<&'a str>
value(FlatAtom::Rest, char(Atom::REST)), + ContextError<&'a str>
value(FlatAtom::StartHere, char(Atom::START_HERE)), + FromExternalError<&'a str, TryFromIntError>
preceded(char(Atom::MODIFIER), modifier).map(FlatAtom::Modifier), + FromExternalError<&'a str, E>,
quick_modifier.map(FlatAtom::QuickModifier), {
preceded( context(
char(Atom::LOOP.0), "atom",
map_opt(opt(u8), |n| { alt((
if let Some(n) = n { map_res(map_opt(one_of(notes), |c| notes.find(c)), u8::try_from).map(FlatAtom::Note),
NonZeroU8::new(n) value(FlatAtom::Rest, char(Atom::REST)),
} else { value(FlatAtom::StartHere, char(Atom::START_HERE)),
unsafe { Some(NonZeroU8::new_unchecked(2)) } preceded(char(Atom::MODIFIER), modifier).map(FlatAtom::Modifier),
} quick_modifier.map(FlatAtom::QuickModifier),
}),
)
.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( preceded(
char(Atom::SLOPE.0), char(Atom::LOOP.0),
separated_pair( map_opt(opt(u8), |n| {
slope_modifier, if let Some(n) = n {
char(' '), NonZeroU8::new(n)
map_res(take_till1(|c| c == ','), |s: &str| { } else {
s.parse() unsafe { Some(NonZeroU8::new_unchecked(2)) }
.map_err(|_| nom::error::Error::new(s, nom::error::ErrorKind::Verify)) }
}), }),
)
.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),
),
),
))
} }

View file

@ -35,7 +35,7 @@ mod flat_atom {
fn note() { fn note() {
assert_eq!( assert_eq!(
Ok((SAMPLE_STR, FlatAtom::Note(2))), Ok((SAMPLE_STR, FlatAtom::Note(2))),
atom("abcdefg").parse(concatcp!('c', SAMPLE_STR)) atom::<Error<&str>>("abcdefg").parse(concatcp!('c', SAMPLE_STR))
) )
} }
@ -43,7 +43,7 @@ mod flat_atom {
fn rest() { fn rest() {
assert_eq!( assert_eq!(
Ok((SAMPLE_STR, FlatAtom::Rest)), Ok((SAMPLE_STR, FlatAtom::Rest)),
atom("abcdefg").parse(concatcp!(Atom::REST, SAMPLE_STR)) atom::<Error<&str>>("abcdefg").parse(concatcp!(Atom::REST, SAMPLE_STR))
) )
} }
@ -51,7 +51,7 @@ mod flat_atom {
fn start_here() { fn start_here() {
assert_eq!( assert_eq!(
Ok((SAMPLE_STR, FlatAtom::StartHere)), Ok((SAMPLE_STR, FlatAtom::StartHere)),
atom("abcdefg").parse(concatcp!(Atom::START_HERE, SAMPLE_STR)) atom::<Error<&str>>("abcdefg").parse(concatcp!(Atom::START_HERE, SAMPLE_STR))
) )
} }
@ -62,7 +62,12 @@ mod flat_atom {
SAMPLE_STR, SAMPLE_STR,
FlatAtom::Modifier(Modifier::Length(unsafe { NonZeroU8::new_unchecked(2) })) FlatAtom::Modifier(Modifier::Length(unsafe { NonZeroU8::new_unchecked(2) }))
)), )),
atom("abcdefg").parse(concatcp!(Atom::MODIFIER, Modifier::LENGTH, 2u8, SAMPLE_STR)) atom::<Error<&str>>("abcdefg").parse(concatcp!(
Atom::MODIFIER,
Modifier::LENGTH,
2u8,
SAMPLE_STR
))
) )
} }
@ -73,7 +78,7 @@ mod flat_atom {
SAMPLE_STR, SAMPLE_STR,
FlatAtom::QuickModifier(QuickModifier::Length(UP)) FlatAtom::QuickModifier(QuickModifier::Length(UP))
)), )),
atom("abcdefg").parse(concatcp!(QuickModifier::LENGTH.0, SAMPLE_STR)) atom::<Error<&str>>("abcdefg").parse(concatcp!(QuickModifier::LENGTH.0, SAMPLE_STR))
) )
} }
@ -84,14 +89,14 @@ mod flat_atom {
SAMPLE_STR, SAMPLE_STR,
FlatAtom::LoopStarts(unsafe { NonZeroU8::new_unchecked(3) }) FlatAtom::LoopStarts(unsafe { NonZeroU8::new_unchecked(3) })
)), )),
atom("abcdefg").parse(concatcp!(Atom::LOOP.0, 3u8, SAMPLE_STR)) atom::<Error<&str>>("abcdefg").parse(concatcp!(Atom::LOOP.0, 3u8, SAMPLE_STR))
); );
assert_eq!( assert_eq!(
Ok(( Ok((
SAMPLE_STR, SAMPLE_STR,
FlatAtom::LoopStarts(unsafe { NonZeroU8::new_unchecked(2) }) FlatAtom::LoopStarts(unsafe { NonZeroU8::new_unchecked(2) })
)), )),
atom("abcdefg").parse(concatcp!(Atom::LOOP.0, SAMPLE_STR)) atom::<Error<&str>>("abcdefg").parse(concatcp!(Atom::LOOP.0, SAMPLE_STR))
); );
assert_eq!( assert_eq!(
Err(nom::Err::Error(Error::new( Err(nom::Err::Error(Error::new(
@ -106,7 +111,7 @@ mod flat_atom {
fn loop_ends() { fn loop_ends() {
assert_eq!( assert_eq!(
Ok((SAMPLE_STR, FlatAtom::LoopEnds)), Ok((SAMPLE_STR, FlatAtom::LoopEnds)),
atom("abcdefg").parse(concatcp!(Atom::LOOP.1, SAMPLE_STR)) atom::<Error<&str>>("abcdefg").parse(concatcp!(Atom::LOOP.1, SAMPLE_STR))
) )
} }
@ -114,7 +119,7 @@ mod flat_atom {
fn tuple_starts() { fn tuple_starts() {
assert_eq!( assert_eq!(
Ok((SAMPLE_STR, FlatAtom::TupleStarts)), Ok((SAMPLE_STR, FlatAtom::TupleStarts)),
atom("abcdefg").parse(concatcp!(Atom::TUPLE.0, SAMPLE_STR)) atom::<Error<&str>>("abcdefg").parse(concatcp!(Atom::TUPLE.0, SAMPLE_STR))
) )
} }
@ -122,7 +127,7 @@ mod flat_atom {
fn tuple_ends() { fn tuple_ends() {
assert_eq!( assert_eq!(
Ok((SAMPLE_STR, FlatAtom::TupleEnds)), Ok((SAMPLE_STR, FlatAtom::TupleEnds)),
atom("abcdefg").parse(concatcp!(Atom::TUPLE.1, SAMPLE_STR)) atom::<Error<&str>>("abcdefg").parse(concatcp!(Atom::TUPLE.1, SAMPLE_STR))
) )
} }
@ -133,7 +138,7 @@ mod flat_atom {
SAMPLE_STR, SAMPLE_STR,
FlatAtom::SlopeStarts(SlopeModifier::Note, FASTEVAL_INSTRUCTION.parse().unwrap()) FlatAtom::SlopeStarts(SlopeModifier::Note, FASTEVAL_INSTRUCTION.parse().unwrap())
)), )),
atom("abcdefg").parse(concatcp!( atom::<Error<&str>>("abcdefg").parse(concatcp!(
Atom::SLOPE.0, Atom::SLOPE.0,
SlopeModifier::NOTE, SlopeModifier::NOTE,
' ', ' ',
@ -148,7 +153,7 @@ mod flat_atom {
fn slope_ends() { fn slope_ends() {
assert_eq!( assert_eq!(
Ok((SAMPLE_STR, FlatAtom::SlopeEnds)), Ok((SAMPLE_STR, FlatAtom::SlopeEnds)),
atom("abcdefg").parse(concatcp!(Atom::SLOPE.1, SAMPLE_STR)) atom::<Error<&str>>("abcdefg").parse(concatcp!(Atom::SLOPE.1, SAMPLE_STR))
) )
} }
@ -156,7 +161,7 @@ mod flat_atom {
fn comment() { fn comment() {
assert_eq!( assert_eq!(
Ok((SAMPLE_STR, FlatAtom::Comment)), Ok((SAMPLE_STR, FlatAtom::Comment)),
atom("abcdefg").parse(concatcp!( atom::<Error<&str>>("abcdefg").parse(concatcp!(
Atom::COMMENT.0, Atom::COMMENT.0,
"hi I'm a little pony", "hi I'm a little pony",
SAMPLE_STR, SAMPLE_STR,