diff --git a/src/bng/score.rs b/src/bng/score.rs index 127cf2d..bbed848 100644 --- a/src/bng/score.rs +++ b/src/bng/score.rs @@ -1,13 +1,16 @@ use std::num::{NonZeroU16, NonZeroU8}; +use anyhow::Context; use bng_macros::{QuickModifierParser, SlopeModifierParser}; use fasteval::Instruction; +use strum::EnumDiscriminants; mod lex; +mod utils; #[cfg_attr(test, derive(Debug, PartialEq))] pub(super) enum Atom { - Note(char), + Note(u8), Rest, StartHere, Modifier(Modifier), @@ -49,6 +52,7 @@ impl Clone for FlatAtom { } } +#[derive(Clone)] #[cfg_attr(test, derive(Debug, PartialEq))] pub(super) enum Modifier { Volume(u8), @@ -63,7 +67,7 @@ impl Default for Modifier { } } -#[derive(QuickModifierParser)] +#[derive(QuickModifierParser, Clone)] #[cfg_attr(test, derive(Debug, PartialEq))] pub(super) enum QuickModifier { Volume(bool), diff --git a/src/bng/score/lex/lexer.rs b/src/bng/score/lex/lexer.rs index 41bfe95..5846f75 100644 --- a/src/bng/score/lex/lexer.rs +++ b/src/bng/score/lex/lexer.rs @@ -9,7 +9,7 @@ use nom::{ branch::alt, bytes::complete::{take_till, take_till1}, character::complete::{anychar, char, one_of, u16, u8}, - combinator::{map_opt, map_res, opt, value}, + combinator::{map_opt, map_res, opt, value, verify}, multi::many0, sequence::{delimited, pair, preceded, separated_pair, terminated}, Err, IResult, Parser, diff --git a/src/bng/score/utils.rs b/src/bng/score/utils.rs new file mode 100644 index 0000000..b89e997 --- /dev/null +++ b/src/bng/score/utils.rs @@ -0,0 +1,195 @@ +use super::*; + +#[cfg(test)] +mod tests; + +// TODO: replace panics with custom error type +fn inflate(mut flat_atoms: Vec) -> Vec { + #[derive(EnumDiscriminants)] + enum CurrentStack { + Loop(NonZeroU8), + Tuple, + Slope(SlopeModifier, Instruction), + } + let mut result = Vec::with_capacity(flat_atoms.len()); + let mut loop_stack: Vec> = Vec::new(); + let mut tuple_stack: Vec> = Vec::new(); + let mut slope_stack: Vec> = Vec::new(); + let mut stack_history: Vec = Vec::new(); + for mut atom in flat_atoms.into_iter() { + if let Some(stack) = stack_history.last() { + match CurrentStackDiscriminants::from(stack) { + CurrentStackDiscriminants::Loop => match atom { + FlatAtom::Note(n) => loop_stack.last_mut().unwrap().push(Atom::Note(n)), + FlatAtom::Rest => loop_stack.last_mut().unwrap().push(Atom::Rest), + FlatAtom::StartHere => loop_stack.last_mut().unwrap().push(Atom::StartHere), + FlatAtom::Modifier(m) => loop_stack.last_mut().unwrap().push(Atom::Modifier(m)), + FlatAtom::QuickModifier(q) => { + loop_stack.last_mut().unwrap().push(Atom::QuickModifier(q)) + } + FlatAtom::LoopStarts(n) => { + loop_stack.push(Vec::new()); + stack_history.push(CurrentStack::Loop(n)); + } + FlatAtom::LoopEnds => { + let popped = loop_stack.pop().unwrap(); + if stack_history.len() > 1 { + match CurrentStackDiscriminants::from( + stack_history.get(stack_history.len() - 2).unwrap(), + ) { + CurrentStackDiscriminants::Loop => &mut loop_stack, + CurrentStackDiscriminants::Tuple => &mut tuple_stack, + CurrentStackDiscriminants::Slope => &mut slope_stack, + } + .last_mut() + .unwrap() + .push(Atom::Loop( + match stack_history.pop().unwrap() { + CurrentStack::Loop(n) => n, + _ => unreachable!("this one is proven to be a loop"), + }, + popped, + )) + } else { + result.push(Atom::Loop( + match stack_history.pop().unwrap() { + CurrentStack::Loop(n) => n, + _ => unreachable!("this one is proven to be a loop"), + }, + popped, + )) + } + } + FlatAtom::TupleStarts => { + tuple_stack.push(Vec::new()); + stack_history.push(CurrentStack::Tuple); + } + FlatAtom::TupleEnds => panic!("unmatched end tuple in a loop"), + FlatAtom::SlopeStarts(s, i) => { + slope_stack.push(Vec::new()); + stack_history.push(CurrentStack::Slope(s, i)); + } + FlatAtom::SlopeEnds => panic!("unmatched end slope in a loop"), + FlatAtom::Comment => loop_stack.last_mut().unwrap().push(Atom::Comment), + }, + CurrentStackDiscriminants::Tuple => match atom { + FlatAtom::Note(n) => tuple_stack.last_mut().unwrap().push(Atom::Note(n)), + FlatAtom::Rest => tuple_stack.last_mut().unwrap().push(Atom::Rest), + FlatAtom::StartHere => tuple_stack.last_mut().unwrap().push(Atom::StartHere), + FlatAtom::Modifier(m) => { + tuple_stack.last_mut().unwrap().push(Atom::Modifier(m)) + } + FlatAtom::QuickModifier(q) => { + tuple_stack.last_mut().unwrap().push(Atom::QuickModifier(q)) + } + FlatAtom::LoopStarts(n) => { + tuple_stack.push(Vec::new()); + stack_history.push(CurrentStack::Loop(n)); + } + FlatAtom::LoopEnds => panic!("unmatched end loop in a tuple"), + FlatAtom::TupleStarts => { + tuple_stack.push(Vec::new()); + stack_history.push(CurrentStack::Tuple); + } + FlatAtom::TupleEnds => { + let popped = loop_stack.pop().unwrap(); + if stack_history.len() > 1 { + match CurrentStackDiscriminants::from( + stack_history.get(stack_history.len() - 2).unwrap(), + ) { + CurrentStackDiscriminants::Loop => &mut loop_stack, + CurrentStackDiscriminants::Tuple => &mut tuple_stack, + CurrentStackDiscriminants::Slope => &mut slope_stack, + } + .last_mut() + .unwrap() + .push(Atom::Tuple(popped)) + } else { + result.push(Atom::Tuple(popped)) + } + } + FlatAtom::SlopeStarts(s, i) => { + slope_stack.push(Vec::new()); + stack_history.push(CurrentStack::Slope(s, i)); + } + FlatAtom::SlopeEnds => panic!("unmatched end slope in a tuple"), + FlatAtom::Comment => tuple_stack.last_mut().unwrap().push(Atom::Comment), + }, + CurrentStackDiscriminants::Slope => match atom { + FlatAtom::Note(n) => slope_stack.last_mut().unwrap().push(Atom::Note(n)), + FlatAtom::Rest => slope_stack.last_mut().unwrap().push(Atom::Rest), + FlatAtom::StartHere => slope_stack.last_mut().unwrap().push(Atom::StartHere), + FlatAtom::Modifier(m) => { + slope_stack.last_mut().unwrap().push(Atom::Modifier(m)) + } + FlatAtom::QuickModifier(q) => { + slope_stack.last_mut().unwrap().push(Atom::QuickModifier(q)) + } + FlatAtom::LoopStarts(n) => { + slope_stack.push(Vec::new()); + stack_history.push(CurrentStack::Loop(n)); + } + FlatAtom::LoopEnds => panic!("unmatched end loop"), + FlatAtom::TupleStarts => { + tuple_stack.push(Vec::new()); + stack_history.push(CurrentStack::Tuple); + } + FlatAtom::TupleEnds => panic!("unmatched end tuple"), + FlatAtom::SlopeStarts(s, i) => { + slope_stack.push(Vec::new()); + stack_history.push(CurrentStack::Slope(s, i)); + } + FlatAtom::SlopeEnds => { + let popped = loop_stack.pop().unwrap(); + if stack_history.len() > 1 { + match CurrentStackDiscriminants::from( + stack_history.get(stack_history.len() - 2).unwrap(), + ) { + CurrentStackDiscriminants::Loop => &mut loop_stack, + CurrentStackDiscriminants::Tuple => &mut tuple_stack, + CurrentStackDiscriminants::Slope => &mut slope_stack, + } + .last_mut() + .unwrap() + .push(match stack_history.pop().unwrap() { + CurrentStack::Slope(m, i) => Atom::Slope(m, i, popped), + _ => unreachable!("this one is proven to be a slope"), + }) + } else { + result.push(match stack_history.pop().unwrap() { + CurrentStack::Slope(m, i) => Atom::Slope(m, i, popped), + _ => unreachable!("this one is proven to be a slope"), + }) + } + } + FlatAtom::Comment => slope_stack.last_mut().unwrap().push(Atom::Comment), + }, + } + } else { + match atom { + FlatAtom::Note(n) => result.push(Atom::Note(n)), + FlatAtom::Rest => result.push(Atom::Rest), + FlatAtom::StartHere => result.push(Atom::StartHere), + FlatAtom::Modifier(m) => result.push(Atom::Modifier(m)), + FlatAtom::QuickModifier(q) => result.push(Atom::QuickModifier(q)), + FlatAtom::LoopStarts(n) => { + loop_stack.push(Vec::new()); + stack_history.push(CurrentStack::Loop(n)); + } + FlatAtom::LoopEnds => panic!("unmatched end loop"), + FlatAtom::TupleStarts => { + tuple_stack.push(Vec::new()); + stack_history.push(CurrentStack::Tuple); + } + FlatAtom::TupleEnds => panic!("unmatched end tuple"), + FlatAtom::SlopeStarts(s, i) => { + slope_stack.push(Vec::new()); + stack_history.push(CurrentStack::Slope(s, i)); + } + FlatAtom::SlopeEnds => panic!("unmatched end slope"), + FlatAtom::Comment => result.push(Atom::Comment), + } + } + } + result +} diff --git a/src/bng/score/utils/tests.rs b/src/bng/score/utils/tests.rs new file mode 100644 index 0000000..88739d1 --- /dev/null +++ b/src/bng/score/utils/tests.rs @@ -0,0 +1,86 @@ +#[cfg(test)] +mod inflate { + use fasteval::Compiler; + use lex::UP; + + use super::{super::*, inflate}; + + const FASTEVAL_INSTRUCTION: &str = "1-cos((PI*x)/2)"; + + #[test] + fn inflate_flat() { + assert_eq!( + vec![ + Atom::Note(2), + Atom::Rest, + Atom::StartHere, + Atom::Modifier(Modifier::Volume(2)), + Atom::QuickModifier(QuickModifier::Volume(UP)), + Atom::Comment + ], + inflate(vec![ + FlatAtom::Note(2), + FlatAtom::Rest, + FlatAtom::StartHere, + FlatAtom::Modifier(Modifier::Volume(2)), + FlatAtom::QuickModifier(QuickModifier::Volume(UP)), + FlatAtom::Comment + ]) + ) + } + + #[test] + fn inflate_loop_l1() { + assert_eq!( + vec![Atom::Loop( + unsafe { NonZeroU8::new_unchecked(3) }, + vec![Atom::Note(2), Atom::Note(3)] + )], + inflate(vec![ + FlatAtom::LoopStarts(unsafe { NonZeroU8::new_unchecked(3) }), + FlatAtom::Note(2), + FlatAtom::Note(3), + FlatAtom::LoopEnds + ]) + ) + } + + #[test] + fn inflate_tuple_l1() { + assert_eq!( + vec![Atom::Tuple(vec![Atom::Note(2), Atom::Note(3)])], + inflate(vec![ + FlatAtom::TupleStarts, + FlatAtom::Note(2), + FlatAtom::Note(3), + FlatAtom::TupleEnds + ]) + ) + } + + #[test] + fn inflate_slope_l1() { + let instruction = || { + let parser = fasteval::Parser::new(); + let mut slab = fasteval::Slab::new(); + parser + .parse(FASTEVAL_INSTRUCTION, &mut slab.ps) + .unwrap() + .from(&slab.ps) + .compile(&slab.ps, &mut slab.cs) + }; + assert_eq!( + vec![Atom::Slope( + SlopeModifier::Note, + instruction(), + vec![Atom::Note(2), Atom::Note(3)] + )], + inflate(vec![ + FlatAtom::SlopeStarts(SlopeModifier::Note, instruction()), + FlatAtom::Note(2), + FlatAtom::Note(3), + FlatAtom::SlopeEnds + ]) + ) + } +}