use std::{
any::type_name,
borrow::{Borrow, Cow},
collections::BTreeMap,
marker::PhantomData,
};
use anyhow::anyhow;
use derive_new::new;
use fasteval::Evaler;
use log::{debug, trace, warn};
use nom::{
AsChar, Compare, Input, Parser as _,
branch::alt,
bytes::complete::{tag, take, take_till},
character::{
complete::{char, space1, usize},
streaming::one_of,
},
combinator::{all_consuming, cut, opt, value},
error::{ErrorKind, FromExternalError, ParseError},
multi::many0,
sequence::{delimited, preceded},
};
use nom_locate::LocatedSpan;
use crate::compiler::{
Expression, Loop, LoopCount, Marker, Note, Silence, Slope, Token, TokenVec, Tuplet,
VariableChange,
};
pub type IResult<I, O, E = LocatedVerboseError<I>> = nom::IResult<I, O, E>;
#[derive(new)]
pub struct VerboseParser<P: nom::Parser<I, Error = LocatedVerboseError<I>>, I> {
parser: P,
context: Cow<'static, str>,
#[new(default)]
phantom: PhantomData<I>,
}
impl<I> ParseError<I> for LocatedVerboseError<I> {
fn from_error_kind(input: I, _kind: ErrorKind) -> Self {
Self {
location: input,
error: None,
}
}
fn append(_input: I, _kind: ErrorKind, other: Self) -> Self {
other
}
}
impl<I, P: nom::Parser<I, Error = LocatedVerboseError<I>>> nom::Parser<I> for VerboseParser<P, I> {
type Output = P::Output;
type Error = LocatedVerboseError<I>;
fn process<OM: nom::OutputMode>(
&mut self,
input: I,
) -> nom::PResult<OM, I, Self::Output, Self::Error> {
use nom::Err::*;
use nom::Mode;
let stack_verbose_error = |e: LocatedVerboseError<I>| -> LocatedVerboseError<I> {
LocatedVerboseError {
error: Some(if let Some(cause) = e.error {
cause.context(self.context.clone())
} else {
anyhow::Error::msg(self.context.clone())
}),
..e
}
};
match self.parser.process::<OM>(input) {
Ok(o) => Ok(o),
Err(Error(e)) => Err(Error(OM::Error::map(e, stack_verbose_error))),
Err(Failure(e)) => Err(Failure(stack_verbose_error(e))),
Err(Incomplete(e)) => Err(Incomplete(e)),
}
}
}
#[derive(Debug)]
pub struct LocatedVerboseError<I> {
pub location: I,
pub error: Option<anyhow::Error>,
}
pub fn expect<P, I>(
parser: P,
error_message: impl Into<Cow<'static, str>>,
) -> impl nom::Parser<I, Output = P::Output, Error = LocatedVerboseError<I>>
where
P: nom::Parser<I, Error = LocatedVerboseError<I>>,
{
VerboseParser::new(parser, error_message.into())
}
impl<I> FromExternalError<I, anyhow::Error> for LocatedVerboseError<I> {
fn from_external_error(input: I, _kind: ErrorKind, e: anyhow::Error) -> Self {
Self {
location: input,
error: Some(e),
}
}
}
#[derive(new)]
pub struct Parser<N, NS, S, SS, SV, V>
where
N: AsRef<[NS]>,
NS: AsRef<str>,
S: IntoIterator<Item = (SS, SV)>,
SS: AsRef<str>,
SV: Borrow<VariableChange>,
V: AsRef<[char]>,
{
notes: N,
slopes: S,
variables: V,
phantom: PhantomData<(NS, SS)>,
}
impl<'a, 'p, N, NS, S, SS, SV, V> Parser<N, NS, S, SS, SV, V>
where
N: AsRef<[NS]>,
NS: AsRef<str>,
S: IntoIterator<Item = (SS, SV)> + Clone,
SS: AsRef<str>,
SV: Borrow<VariableChange>,
V: AsRef<[char]>,
'p: 'a,
{
pub fn parse_all(
&'p self,
input: &'a str,
) -> Result<TokenVec, nom::Err<LocatedVerboseError<nom_locate::LocatedSpan<&'a str>>>> {
debug!("parsing input \"{input}\"");
all_consuming(token_parser(self))
.parse_complete(LocatedSpan::new(input))
.map(move |(_, o)| o)
}
}
fn token_parser<'a, 'p, N, NS, S, SS, SV, V>(
parser: &'p Parser<N, NS, S, SS, SV, V>,
) -> impl nom::Parser<
LocatedSpan<&'a str>,
Output = TokenVec,
Error = LocatedVerboseError<LocatedSpan<&'a str>>,
>
where
N: AsRef<[NS]>,
NS: AsRef<str>,
S: IntoIterator<Item = (SS, SV)> + Clone,
SS: AsRef<str>,
SV: Borrow<VariableChange>,
V: AsRef<[char]>,
'p: 'a,
{
trace!("making the TOKEN parser");
let space_or_comment = || {
value(
(),
many0(value((), char('#').and(take_till(|c| c == '\n'))).or(value((), space1))),
)
};
many0(delimited(
space_or_comment(),
alt((
expect(Silence::parser(), "expected a silence").map(into_box),
expect(Marker::parser(), "expected a marker").map(into_box),
expect(
VariableChange::parser(&parser.variables).map(into_box),
"variable assignment",
),
expect(Loop::parser(parser).map(into_box), "in the loop"),
expect(Tuplet::parser(parser).map(into_box), "in the tuplet"),
expect(Slope::parser(parser).map(into_box), "in the slope"),
expect(
Note::parser(parser.notes.as_ref()),
"expected a note as last appeal (input didn't match anything known)",
)
.map(into_box),
)),
space_or_comment(),
))
.map(Into::into)
}
fn into_box<'a>(token: impl Token + 'a) -> Box<dyn Token + 'a> {
Box::new(token)
}
impl Silence {
fn parser<I>() -> impl nom::Parser<I, Output = Self, Error = LocatedVerboseError<I>>
where
I: Input,
<I as Input>::Item: AsChar,
{
trace!("making the {} parser", type_name::<Self>());
value(Self, char('.'))
}
}
impl Marker {
fn parser<I>() -> impl nom::Parser<I, Output = Self, Error = LocatedVerboseError<I>>
where
I: Input,
<I as Input>::Item: AsChar,
{
trace!("making the {} parser", type_name::<Self>());
value(Marker, char('%'))
}
}
impl Note {
fn parser<'a, N, NS, I>(
notes: N,
) -> impl nom::Parser<I, Output = Self, Error = LocatedVerboseError<I>> + 'a
where
N: IntoIterator<Item = NS>,
NS: AsRef<str>,
I: Input + for<'z> Compare<&'z str>,
{
trace!("making the {} parser", type_name::<Self>());
let notes = {
let mut sorted = notes
.into_iter()
.map(|s| s.as_ref().to_string())
.enumerate()
.collect::<Vec<_>>();
debug!("got notes {sorted:?}");
sorted.sort_by_key(|(_, n)| n.len());
sorted.reverse();
debug!("sorted to {sorted:?}");
sorted
};
move |input: I| {
#[allow(clippy::type_complexity)]
let mut parsers: Vec<Box<dyn Fn(I) -> IResult<I, Self>>> = notes
.clone()
.drain(..)
.map(|(i, t)| {
Box::new(move |input: I| {
value(Note(i as u8), tag(t.clone().as_ref())).parse(input)
}) as Box<dyn Fn(I) -> IResult<I, Self>>
})
.collect();
alt(parsers.as_mut_slice()).parse(input)
}
}
}
impl VariableChange {
fn parser<I, V>(variables: V) -> impl Fn(I) -> IResult<I, Self>
where
I: Input + AsRef<str> + Copy,
<I as Input>::Item: AsChar,
V: AsRef<[char]>,
{
trace!("making the {} parser", type_name::<Self>());
let variables_string = variables.as_ref().iter().collect::<String>();
move |i: I| {
preceded(
char('$'),
cut(expect(
one_of(variables_string.as_str()),
format!(
"got unknown variable '{}', expected one of these instead: {:?}",
i.as_ref().chars().nth(1).unwrap_or('?'),
variables_string.chars().collect::<Vec<_>>()
),
)
.and(cut(expect(
expression_parser(variables.as_ref()),
format!(
"expected a valid expression to assign variable {} to",
i.as_ref().chars().nth(1).unwrap_or('?')
),
)))
.map(|(name, change)| VariableChange(name, change))),
)
.parse(i)
}
}
}
impl Loop {
fn parser<'a, 'p, N, NS, S, SS, SV, V>(
parser: &'p Parser<N, NS, S, SS, SV, V>,
) -> impl Fn(LocatedSpan<&'a str>) -> IResult<LocatedSpan<&'a str>, Self>
where
N: AsRef<[NS]>,
NS: AsRef<str>,
S: IntoIterator<Item = (SS, SV)> + Clone,
SS: AsRef<str>,
SV: Borrow<VariableChange>,
V: AsRef<[char]>,
'p: 'a,
{
trace!("making the {} parser", type_name::<Self>());
move |input| {
delimited(
char('('),
opt(alt((
usize.map(LoopCount::Litteral),
one_of(
parser
.variables
.as_ref()
.iter()
.collect::<String>()
.as_str(),
)
.map(LoopCount::Variable),
)))
.and(cut(take_till(|c| c == ')').and_then(cut(expect(all_consuming(token_parser(parser)), "input did not match any known grammar for inner tokens (typo?)"))))),
cut(
expect(
char(')'),
format!(
"the loop started at line {line} column {column} was not closed at this point",
line = input.location_line(),
column = input.get_utf8_column()
)
)
),
)
.map(|(c, v)| Self(c.unwrap_or_default(), v))
.parse(input)
}
}
}
impl Tuplet {
fn parser<'a, 'p, N, NS, S, SS, SV, V>(
parser: &'p Parser<N, NS, S, SS, SV, V>,
) -> impl Fn(LocatedSpan<&'a str>) -> IResult<LocatedSpan<&'a str>, Self>
where
N: AsRef<[NS]>,
NS: AsRef<str>,
S: IntoIterator<Item = (SS, SV)> + Clone,
SS: AsRef<str>,
SV: Borrow<VariableChange>,
V: AsRef<[char]>,
'p: 'a,
{
trace!("making the {} parser", type_name::<Self>());
|input| {
delimited(char('['),
cut(take_till(|c| c == ']')
.and_then(cut(expect(
all_consuming(token_parser(parser)),
"input did not match any known grammar for inner tokens (typo?)")))), cut(
expect(
char(']'),
format!(
"the tuplet started at line {line} column {column} was not closed at this point",
line = input.location_line(),
column = input.get_utf8_column()
)
)
))
.map(Self)
.parse(input)
}
}
}
impl Slope {
fn parser<'a, 'p, N, NS, S, SS, SV, V>(
parser: &'p Parser<N, NS, S, SS, SV, V>,
) -> impl Fn(LocatedSpan<&'a str>) -> IResult<LocatedSpan<&'a str>, Self>
where
N: AsRef<[NS]>,
NS: AsRef<str>,
S: IntoIterator<Item = (SS, SV)> + Clone,
SS: AsRef<str>,
SV: Borrow<VariableChange>,
V: AsRef<[char]>,
'p: 'a,
{
trace!("making the {} parser", type_name::<Self>());
move |input| {
let slopes = {
let mut vec = parser
.slopes
.clone()
.into_iter()
.map(|(s1, s2)| (s1.as_ref().to_string(), s2.borrow().clone()))
.collect::<Vec<(String, VariableChange)>>();
debug!("got slopes {vec:?}");
vec.sort_by_key(|(name, _)| name.len());
vec.reverse();
debug!("sorted to {vec:?}");
vec
};
let iter: std::vec::IntoIter<(String, VariableChange)> = slopes.into_iter();
delimited(
char('{'),
cut(expect(alt(iter
.map(|(k, v)| {
Box::new(move |input| value(v.clone(), tag(k.as_str())).parse(input))
as Box<
dyn Fn(
LocatedSpan<&'a str>,
)
-> IResult<LocatedSpan<&'a str>, VariableChange>,
>
})
.collect::<Vec<
Box<
dyn Fn(
LocatedSpan<&'a str>,
)
-> IResult<LocatedSpan<&'a str>, VariableChange>,
>,
>>()
.as_mut_slice()),
format!(
"expected a slope name from available slope names ({:?})",
parser.slopes.clone().into_iter().map(|(s1, _)| s1.as_ref().to_string()).collect::<Vec<_>>()
)))
.and(cut(take_till(|c| c == '}').and_then(cut(
expect(all_consuming(token_parser(parser)),
"input did not match any known grammar for inner tokens (typo?)"))))),
cut(
expect(
char('}'),
format!(
"the slope started at line {line} column {column} was not closed at this point",
line = input.location_line(),
column = input.get_utf8_column()
)
)
),
)
.map(|(i, v)| Self::new(i, v))
.parse(input)
}
}
}
fn expression_parser<'v, I, V, C>(variables: V) -> impl 'v + Fn(I) -> IResult<I, Expression>
where
I: Input + AsRef<str> + Copy,
V: IntoIterator<Item = C>,
C: Borrow<char>,
{
trace!("making the Expression parser");
let variables: Vec<(String, f64)> = variables
.into_iter()
.map(|v| (v.borrow().to_string(), 0.0))
.collect();
debug!("got variables {variables:?}");
move |input: I| {
take_while_map(|i: I| {
i.as_ref()
.parse::<Expression>()
.inspect_err(|e| {
trace!(
"failed parsing expression {expr} with {err}",
expr = i.as_ref(),
err = e
)
})
.ok()
.and_then(|e| {
e.instruction
.eval(
&e.slab,
&mut BTreeMap::from_iter(variables.clone().drain(..)),
)
.inspect_err(|e| {
trace!(
"failed expression evaluation {expr:?} with {err}",
expr = e,
err = e
)
})
.ok()
.is_some()
.then_some(e)
})
})
.parse(input)
}
}
pub fn take_while_map<F, I, O>(cond: F) -> impl FnMut(I) -> IResult<I, O, LocatedVerboseError<I>>
where
I: Input + Copy,
F: Fn(I) -> Option<O>,
{
trace!("making take_while_map parser");
move |input: I| {
let mut len = input.input_len();
debug!(
"take_while_map will now match biggest munch from the rest of the input ({len} elements)"
);
while len > 0 {
let result = take(len).map_opt(&cond).parse(input);
if result.is_ok() {
debug!("found a match using {len} elements");
return result;
} else {
len -= 1;
}
}
warn!("take_while_map found no match");
Err(nom::Err::Error(LocatedVerboseError {
location: input,
error: Some(anyhow!("invalid expression")),
}))
}
}
#[cfg(test)]
mod tests {
use super::{IResult, Parser, expression_parser};
use std::{borrow::Borrow, collections::HashMap};
use nom::Parser as _;
use nom_locate::LocatedSpan;
use crate::compiler::{
Loop, LoopCount, Marker, Note, Silence, Slope, TokenVec, Tuplet, VariableChange,
};
fn very_fancy_slope() -> VariableChange {
VariableChange('n', "1+1".parse().unwrap())
}
fn normal_slope() -> VariableChange {
VariableChange('n', "1".parse().unwrap())
}
type DefaultParser = Parser<
[&'static str; 3],
&'static str,
HashMap<String, VariableChange>,
String,
VariableChange,
[char; 1],
>;
fn parser_generator() -> DefaultParser {
Parser::new(
["do", "ré", "mi"],
HashMap::from([
("nor".to_string(), very_fancy_slope()),
("normal".to_string(), normal_slope()),
]),
['n'],
)
}
#[test]
fn expression_parser_test() {
let parser = expression_parser(&['x']);
let mut working_test_cases = vec![
("1", ("", "1")),
("1*x", ("", "1*x")),
("56coucou", ("coucou", "56")), (
"8*x + 1 heille salut ça va ou quoi 46 - 5*x",
("heille salut ça va ou quoi 46 - 5*x", "8*x + 1"),
),
];
let mut not_working_test_cases = vec![
"",
"(",
"y",
"abcdexx489",
" ", " h", ];
for (test, expected) in working_test_cases.drain(..) {
let output = parser(test);
if let Ok(result) = output {
assert_eq!(
(expected.0, expected.1.parse().unwrap()),
result,
"case \"{test}\""
)
} else {
panic!("result of \"{test}\" was not Ok: {output:?}");
}
}
for test in not_working_test_cases.drain(..) {
let output = parser(test);
assert!(
output.is_err(),
"result of \"{test}\" was not Err: {output:?}"
);
}
}
#[test]
fn silence() {
let parser = |input| Silence::parser().parse(input);
let mut working_cases = vec![
(".", ("", Silence)),
(".dd", ("dd", Silence)),
("..", (".", Silence)),
];
let mut not_working_cases = vec!["", ",", "d.", " "];
for (test, expected) in working_cases.drain(..) {
let output = parser(test);
if let Ok(result) = output {
assert_eq!(expected, result);
} else {
panic!("result of \"{test}\" was not Ok: {output:?}");
}
}
for test in not_working_cases.drain(..) {
let output = parser(test);
assert!(
output.is_err(),
"result of \"{test}\" was not Err: {output:?}"
);
}
}
#[test]
fn marker() {
let parser = |input| Marker::parser().parse(input);
let mut working_cases = vec![
("%", ("", Marker)),
("%dd", ("dd", Marker)),
("%%", ("%", Marker)),
];
let mut not_working_cases = vec!["", ",", "d%", " "];
for (test, expected) in working_cases.drain(..) {
let output = parser(test);
if let Ok(result) = output {
assert_eq!(expected, result);
} else {
panic!("result of \"{test}\" was not Ok: {output:?}");
}
}
for test in not_working_cases.drain(..) {
let output = parser(test);
assert!(
output.is_err(),
"result of \"{test}\" was not Err: {output:?}"
);
}
}
#[test]
fn note() {
let parser = |input| Note::parser(&["do", "r", "ré", "mi", "m"]).parse(input);
let mut working_cases = vec![
("do", ("", Note(0))),
("ré", ("", Note(2))),
("rér", ("r", Note(2))),
("r.", (".", Note(1))),
("mi", ("", Note(3))),
("mif", ("f", Note(3))),
("mi ", (" ", Note(3))),
("m ", (" ", Note(4))),
];
let mut not_working_cases = vec!["", ",", " mi", " ", "dré"];
for (test, expected) in working_cases.drain(..) {
let output = parser(test);
if let Ok(result) = output {
assert_eq!(expected, result, "{test}");
} else {
panic!("result of \"{test}\" was not Ok: {output:?}");
}
}
for test in not_working_cases.drain(..) {
let output = parser(test);
assert!(
output.is_err(),
"result of \"{test}\" was not Err: {output:?}"
);
}
}
#[test]
fn variable_change() {
let parser = |input| VariableChange::parser(&['a', 'b', 'ö']).parse(input);
let mut working_cases = vec![("$a5", ("", ('a', "5")))];
let mut not_working_cases = vec!["", "$", "$a", "$c8", "$d/1"];
for (test, expected) in working_cases.drain(..) {
let output = parser(test);
if let Ok(result) = output {
assert_eq!(
(
expected.0,
VariableChange(expected.1.0, expected.1.1.parse().unwrap())
),
result,
"case \"{test}\""
);
} else {
panic!("result of \"{test}\" was not Ok: {output:?}");
}
}
for test in not_working_cases.drain(..) {
let output = parser(test);
assert!(
output.is_err(),
"result of \"{test}\" was not Err: {output:?}"
);
}
}
#[test]
fn r#loop() {
let parser = Parser::new(
["do", "ré", "mi"],
HashMap::<String, VariableChange>::default(),
['n'],
);
fn parser_builder<'a, 'p, N, NS, S, SS, SV, V>(
parser: &'p Parser<N, NS, S, SS, SV, V>,
) -> impl Fn(LocatedSpan<&'a str>) -> IResult<LocatedSpan<&'a str>, Loop>
where
N: AsRef<[NS]>,
NS: AsRef<str>,
S: IntoIterator<Item = (SS, SV)> + Clone,
SS: AsRef<str>,
SV: Borrow<VariableChange>,
V: AsRef<[char]>,
'p: 'a,
{
move |input| Loop::parser(parser).parse(input)
}
let parser = parser_builder(&parser);
let mut working_cases = vec![
(
"(.%)",
(
"",
Loop(
LoopCount::Litteral(2),
TokenVec(vec![Box::new(Silence), Box::new(Marker)]),
),
),
),
("()", ("", Loop(LoopCount::Litteral(2), TokenVec(vec![])))),
("(4)", ("", Loop(LoopCount::Litteral(4), TokenVec(vec![])))),
(
"(n)",
("", Loop(LoopCount::Variable('n'), TokenVec(vec![]))),
),
(
"(ndo)",
(
"",
Loop(LoopCount::Variable('n'), TokenVec(vec![Box::new(Note(0))])),
),
),
];
let mut not_working_cases = vec!["", "(", ")", "(2", "(p)"];
for (test, expected) in working_cases.drain(..) {
let output = parser(test.into()).map(|(ls, o)| (*ls, o));
if let Ok(result) = output {
assert_eq!(expected, result, "case \"{test}\"");
} else {
panic!("result of \"{test}\" was not Ok: {output:?}");
}
}
for test in not_working_cases.drain(..) {
let output = parser(test.into()).map(|(ls, o)| (*ls, o));
assert!(
output.is_err(),
"result of \"{test}\" was not Err: {output:?}"
);
}
}
#[test]
fn tuplet() {
let parser = Parser::new(
["do", "ré", "mi"],
HashMap::<String, VariableChange>::default(),
['n'],
);
fn parser_builder<'a, 'p, N, NS, S, SS, SV, V>(
parser: &'p Parser<N, NS, S, SS, SV, V>,
) -> impl Fn(LocatedSpan<&'a str>) -> IResult<LocatedSpan<&'a str>, Tuplet>
where
N: AsRef<[NS]>,
NS: AsRef<str>,
S: IntoIterator<Item = (SS, SV)> + Clone,
SS: AsRef<str>,
SV: Borrow<VariableChange>,
V: AsRef<[char]>,
'p: 'a,
{
move |input| Tuplet::parser(parser).parse(input)
}
let parser = parser_builder(&parser);
let mut working_cases = vec![
(
"[.%]",
(
"",
Tuplet(TokenVec(vec![Box::new(Silence), Box::new(Marker)])),
),
),
("[]", ("", Tuplet(TokenVec(vec![])))),
("[do]f", ("f", Tuplet(TokenVec(vec![Box::new(Note(0))])))),
];
let mut not_working_cases = vec!["", "[", "]", "[2", "[p]"];
for (test, expected) in working_cases.drain(..) {
let output = parser(test.into()).map(|(ls, o)| (*ls, o));
if let Ok(result) = output {
assert_eq!(expected, result, "case \"{test}\"");
} else {
panic!("result of \"{test}\" was not Ok: {output:?}");
}
}
for test in not_working_cases.drain(..) {
let output = parser(test.into()).map(|(ls, o)| (*ls, o));
assert!(
output.is_err(),
"result of \"{test}\" was not Err: {output:?}"
);
}
}
#[test]
fn slope() {
let p = parser_generator();
let parser_builder = || Slope::parser(&p);
let parser = parser_builder();
let normal_value = normal_slope();
let very_fancy_value = very_fancy_slope();
let mut working_cases = vec![
(
"{normal.%}",
(
"",
Slope(
normal_value.clone(),
TokenVec(vec![Box::new(Silence), Box::new(Marker)]),
),
),
),
(
"{normal}",
("", Slope(normal_value.clone(), TokenVec(vec![]))),
),
("{nor}", ("", Slope(very_fancy_value, TokenVec(vec![])))),
(
"{normal do}f",
("f", Slope(normal_value, TokenVec(vec![Box::new(Note(0))]))),
),
];
let mut not_working_cases = vec![
"",
"{",
"}",
"{normal",
"{fancy}",
"{norma do}",
"{do}",
"{}",
];
for (test, expected) in working_cases.drain(..) {
let output = parser(test.into()).map(|(ls, o)| (*ls, o));
if let Ok(result) = output {
assert_eq!(expected, result, "case \"{test}\"");
} else {
panic!("result of \"{test}\" was not Ok: {output:?}");
}
}
for test in not_working_cases.drain(..) {
let output = parser(test.into()).map(|(ls, o)| (*ls, o));
assert!(
output.is_err(),
"result of \"{test}\" was not Err: {output:?}"
);
}
}
}