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
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,

View file

@ -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<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 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
@ -64,37 +50,30 @@ impl<'de> Deserialize<'de> for Atoms {
if sheet.is_empty() {
Ok(Default::default())
} 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>
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<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)
context("yml white space", many0(one_of(" \t\r\n")))
}

View file

@ -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),
),
),
))
)),
)
}

View file

@ -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::<Error<&str>>("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::<Error<&str>>("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::<Error<&str>>("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::<Error<&str>>("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::<Error<&str>>("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::<Error<&str>>("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::<Error<&str>>("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::<Error<&str>>("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::<Error<&str>>("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::<Error<&str>>("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::<Error<&str>>("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::<Error<&str>>("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::<Error<&str>>("abcdefg").parse(concatcp!(
Atom::COMMENT.0,
"hi I'm a little pony",
SAMPLE_STR,