#![deny(missing_docs)] //! This file implements a simple recursive-descent parser for parsing //! plain text music sheets into some intermediate data structure that //! can later generate the waves to save to an audio file. //! //! The format of the sheet can be described as follows. //! //! # 1. Assignments //! //! Read a string "\"name\"" followed by a block //! //! (assign the block to the name) //! //! # 2. Blocks //! //! Read block+ enclosed in "{}", nor not, if there is only one block //! //! ## 2.1. Groups //! //! Read group+ enclosed in "()" or not //! //! ### 2.1.1. Unit //! //! Read unit* //! //! #### 2.1.1.1. Instruction //! //! Read an instruction starting with an "i" //! //! ##### 2.1.1.1.1. Volume //! //! Read a volume "vDIGITS(\.DIGITS)?" //! //! ##### 2.1.1.1.2. BPM //! //! Read a BPM "bDIGITS" //! //! ##### 2.1.1.1.3. Instrument //! //! Read an instrument "ipiano"/"iviolin". //! //! #### 2.1.1.2. Silence //! //! Read a silence "rDIGITS\.*" //! //! #### 2.1.1.3. Chord //! //! Read a chord = tone (/ tone)* //! //! ##### 2.1.1.3.1. Octave //! //! Read an octave "(oDIGITS)?|\[><\]*" //! //! ##### 2.1.1.3.2. Semitone //! //! Read a semitone "\[a-g\](\+|\-)*" //! //! ##### 2.1.1.3.3. duration //! //! Read a duration "DIGITS\.*|pDIGITS(\.DIGITS)?" //! //! ### 2.1.2 Repetition specification //! //! Read either a [duration](#21133-duration), to specify a cram //! expression, and/or "*DIGITS" to specify a repetition of DIGITS times. //! //! # 3. name //! //! Read a name "\[name\]" that has an assigned value use super::*; use std::error::Error; pub mod play; /// Possibilities of an instrument. /// /// Right now only the piano is usable, whereas the violin is but an /// experiment. #[derive(Debug, Clone, Copy, Default, Eq, PartialEq)] pub enum Instrument { #[default] /// A piano instrument Piano, /// An experimental violin instrument Violin, /// Pure Sinus instrument Sinus, /// Pure Tangent instrument Tangent, } impl Display for Instrument { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Instrument::Piano => write!(f, "piano"), Instrument::Violin => write!(f, "violin"), Instrument::Sinus => write!(f, "sinus"), Instrument::Tangent => write!(f, "tangent"), } } } /// An instruction changing some setting. /// /// It changes one of the following: /// /// 1. the volume /// /// 2. the bpm /// /// 3. the instrument #[derive(Debug, Copy, Clone)] pub enum Instruction { /// Change volume VolumeTo(Volume), /// Change beats per minute BPMTo(BPM), /// Change instrument InstrumentTo(Instrument), } impl From for Instruction { fn from(volume: Volume) -> Self { Self::VolumeTo(volume) } } impl From for Instruction { fn from(bpm: BPM) -> Self { Self::BPMTo(bpm) } } impl From for Instruction { fn from(instrument: Instrument) -> Self { Self::InstrumentTo(instrument) } } impl Display for Instruction { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Instruction::VolumeTo(v) => write!(f, "volume => {}", **v), Instruction::BPMTo(bpm) => write!(f, "BPM => {}", **bpm), Instruction::InstrumentTo(instrument) => write!(f, "Instrument => {instrument}"), } } } /// Represents a semitone #[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] pub struct SheetSemitone(isize); impl SheetSemitone { fn shift(&mut self, shift: isize) { self.0 += shift; } } impl Display for SheetSemitone { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } /// Represents an octave that is either absolute or relative. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum SheetOctave { /// Absolute octave specification Absolute(usize), /// Relative octave specification Relative(isize), } impl From for SheetOctave { fn from(abs: usize) -> Self { Self::Absolute(abs) } } impl From for SheetOctave { fn from(rel: isize) -> Self { Self::Relative(rel) } } impl Display for SheetOctave { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Self::Absolute(val) => write!(f, "o{val}"), Self::Relative(shift) => { let string = if shift.is_positive() { str::repeat(">", *shift as usize) } else { str::repeat("<", (-*shift) as usize) }; write!(f, "{string}") } } } } impl From<(usize, SheetSemitone)> for Semitones { fn from((octave, semitone): (usize, SheetSemitone)) -> Self { let octave_translation = 12 * (octave as isize - 4isize); // println!("octave = {octave_translation}"); // println!("semitone = {}", semitone.0); ((semitone.0 + octave_translation - 9) as f64).into() } } /// Represents a tone and an optional octave. #[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] struct SheetTone { semitone: SheetSemitone, octave: Option, } impl SheetTone { fn new(semitone: SheetSemitone, octave: Option) -> Self { Self { semitone, octave } } } impl Display for SheetTone { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let semitone = self.semitone; if let Some(octave) = self.octave { write!(f, "{octave} {semitone}") } else { write!(f, "{semitone}") } } } impl From for SheetTone where T: Into, { fn from(semitone: T) -> Self { let semitone = semitone.into(); let octave = None; Self { semitone, octave } } } /// A chord is a vector of tones. #[derive(Debug, Clone, Default)] pub struct SheetChord(Vec); impl Display for SheetChord { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self.0.len() { 0 => write!(f, ""), _ => { let mut iter = self.0.iter(); let first_tone = *iter.by_ref().take(1).next().unwrap(); write!(f, "{first_tone}")?; for tone in iter { write!(f, " / {tone}")?; } Ok(()) } } } } impl From for SheetChord where T: Into, { fn from(arg: T) -> Self { Self(vec![arg.into()]) } } impl From> for SheetChord where T: Into, { fn from(arg: Vec) -> Self { Self(arg.into_iter().map(Into::into).collect()) } } /// A duration for a group. /// /// This is essentially a float number, except that it also accepts /// dots. #[derive(Debug, Copy, Clone, Default)] pub struct SheetDuration { beats: Option, dots: usize, } impl SheetDuration { /// Return an optional `Beats`. pub fn beats(&self) -> Option { self.beats } /// Return the number of dots. pub fn dots(&self) -> usize { self.dots } } impl Display for SheetDuration { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if let Some(b) = self.beats() { write!(f, "{}", *b)?; } let dots_str = str::repeat(".", self.dots); write!(f, "{dots_str}") } } impl From for SheetDuration { fn from(dots: usize) -> Self { let beats = Default::default(); Self { beats, dots } } } impl> From for SheetDuration { fn from(arg: T) -> Self { let beats = arg.into(); let beats = Some(beats); let dots = Default::default(); Self { beats, dots } } } impl> From<(Option, usize)> for SheetDuration { fn from((arg, dots): (Option, usize)) -> Self { let beats = arg.map(|b| b.into()); Self { beats, dots } } } /// A unit of the sheet. /// /// It is one of the following: /// /// 1. an ordinary chord. /// /// 2. a silence. /// /// 3. an instruction. #[derive(Debug, Clone)] pub enum SheetUnit { /// A combination of a chord and the number of beats. Tone(SheetChord, SheetDuration), /// A silence that lasts a number of beats. Silence(SheetDuration), /// An [`Instruction`]. Instruction(Instruction), } impl Default for SheetUnit { fn default() -> Self { // The default is silence let duration: SheetDuration = Default::default(); duration.into() } } impl> From for SheetUnit { fn from(instruction: T) -> Self { let instruction = instruction.into(); Self::Instruction(instruction) } } impl From for SheetUnit { fn from(duration: SheetDuration) -> Self { Self::Silence(duration) } } impl From for SheetUnit { fn from(chord: SheetChord) -> Self { Self::Tone(chord, Default::default()) } } impl SheetUnit { /// Adjusting the duration of the chord. /// /// # Warning /// /// This function does nothing if called on a unit that is not a /// tone unit. pub fn set_duration(&mut self, duration: Beats) { match self { Self::Tone(chord, _) => { *self = Self::Tone(chord.clone(), duration.into()); } Self::Silence(_) => { *self = Self::Silence(duration.into()); } _ => (), } } } impl Display for SheetUnit { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Self::Tone(chord, duration) => write!(f, "{chord},{duration}"), Self::Silence(duration) => write!(f, "r{duration}"), Self::Instruction(instruction) => write!(f, "{instruction}"), } } } /// A sheet group node either specifies the number of repetitions or is /// a unit. #[derive(Debug, Clone)] pub enum SheetGroupNode { /// The number of repetition and optionally the crammed duration. /// /// Note that the number of repetition can be zero. /// /// The crammed duration, if present, will be used to adjust the /// durations in the notes. Intermediate(usize, Option), /// A unit Unit(SheetUnit), } impl SheetGroupNode { /// Set the repetition number of an intermediate node. /// /// # Panic /// /// This function panics if called on a node that is not an /// intermediate node. pub fn set_repetition(&mut self, repetition: usize) { match self { Self::Intermediate(_, duration) => { *self = Self::Intermediate(repetition, *duration); } _ => { panic!("setting intermediate on a unit: {self:?}"); } } } /// Set the crammed duration of an intermediate node. /// /// # Panic /// /// This function panics if called on a node that is not an /// intermediate node. pub fn set_cram(&mut self, duration: Option) { match self { Self::Intermediate(repetition, _) => { *self = Self::Intermediate(*repetition, duration); } _ => { panic!("setting intermediate on a unit: {self:?}"); } } } /// Adjusting the durations of a node to fit within a crammed /// duration. pub fn set_duration(&mut self, duration: Option) { match self { Self::Unit(unit) => { if let Some(duration) = duration { unit.set_duration(duration); } } SheetGroupNode::Intermediate(repetition, _) => { *self = SheetGroupNode::Intermediate(*repetition, duration.map(Into::into)); } } } } /// A sheet group is a tree whose leafs are units and whose /// intermediate nodes specify the number of repetitions. #[derive(Debug, Clone, Default)] pub struct SheetGroup { nodes: Vec, edges: Vec>, } impl SheetGroup { /// Construct a new group. pub fn new(nodes: Vec, edges: Vec>) -> Self { Self { nodes, edges } } /// Return the node at position N. pub fn get_node(&self, n: usize) -> &SheetGroupNode { self.nodes.get(n).unwrap() } /// Return the children of the node at position N. pub fn get_children(&self, n: usize) -> &[usize] { self.edges.get(n).unwrap() } /// Adjust durations for each chord in the group. fn do_cram(&mut self, start_duration: &mut Beats) { if self.nodes.is_empty() { return; } // First calculate the durations of each node, so that we can // find out their respective proportions later on. let mut durations_array: Vec = vec![0f64.into(); self.nodes.len()]; let mut seen = vec![false; self.nodes.len()]; let current_duration = start_duration; let mut stack = vec![0]; let mut cram_result: Vec<(usize, Beats)> = vec![]; while let Some(top) = stack.pop() { match self.nodes[top] { SheetGroupNode::Intermediate(repetition, Some(cram)) => { match repetition { 0 => { continue; } 1 => { durations_array[top] = (&cram, *current_duration).into(); } _ => { durations_array[top] = (&cram, *current_duration).into(); durations_array[top] = (*durations_array[top] * repetition as f64).into(); } } for child in self.edges[top].iter().rev() { stack.push(*child); } } SheetGroupNode::Intermediate(repetition, _) => { if repetition == 0 { continue; } if !seen[top] { stack.push(top); for child in self.edges[top].iter().rev().copied() { stack.push(child); } } else { let summation: f64 = self.edges[top] .iter() .map(|node| *durations_array[*node]) .sum(); match repetition { 0 => unreachable!(), 1 => { durations_array[top] = summation.into(); } _ => { durations_array[top] = summation.into(); durations_array[top] = (*durations_array[top] * repetition as f64).into(); } } } } SheetGroupNode::Unit(SheetUnit::Tone(_, duration)) => { let final_duration: Beats = (&duration, *current_duration).into(); durations_array[top] = final_duration; *current_duration = final_duration; cram_result.push((top, final_duration)); } SheetGroupNode::Unit(SheetUnit::Silence(duration)) => { let final_duration: Beats = (&duration, *current_duration).into(); durations_array[top] = final_duration; *current_duration = final_duration; cram_result.push((top, final_duration)); } // Instructions have no duration. _ => {} } seen[top] = true; } for (child, duration) in cram_result { self.nodes[child].set_duration(Some(duration)); } // Then do a depth first traversal to "cram" the durations, // based on their respective proportions. let mut stack = vec![( 0, matches!(self.nodes[0], SheetGroupNode::Intermediate(_, Some(_))), )]; while let Some((top, top_cram_p)) = stack.pop() { if let SheetGroupNode::Intermediate(repetition, cram) = self.nodes[top] { if repetition == 0 { continue; } let new_cram_p = top_cram_p || cram.is_some(); let top_duration = *durations_array[top]; let total_duration: f64 = self.edges[top] .iter() .copied() .map(|child| *durations_array[child]) .sum(); if new_cram_p { let mut cram_result: Vec<(usize, Beats)> = vec![]; // println!("top = {top}"); for child in self.edges[top].iter().copied() { if matches!( self.nodes[child], SheetGroupNode::Unit(SheetUnit::Instruction(_)) ) { continue; } // println!("child = {child}"); let child_duration = *durations_array[child]; let mut crammed_duration: Beats = ((child_duration / total_duration) * top_duration).into(); if let SheetGroupNode::Intermediate(repetition, _) = self.nodes[child] { match repetition { 0 => { continue; } 1 => { // nothing to do } _ => { crammed_duration = (*crammed_duration / repetition as f64).into(); } } } cram_result.push((child, crammed_duration)); } for (child, crammed_duration) in cram_result { self.nodes[child].set_duration(Some(crammed_duration)); // durations_array[child] = crammed_duration; } } stack.append( &mut self.edges[top] .iter() .copied() .filter_map(|child| match self.nodes[child] { SheetGroupNode::Intermediate(repetition, _) if repetition != 0 => { Some((child, new_cram_p)) } _ => None, }) .collect(), ); } } } } /// A sheet block is a sequence of groups. /// /// A block is delimited by curly braces. #[derive(Debug, Clone, Default)] pub struct SheetBlock { groups: Vec, } impl SheetBlock { /// A plain constructor. pub fn new(groups: Vec) -> Self { Self { groups } } } impl Deref for SheetBlock { type Target = [SheetGroup]; fn deref(&self) -> &Self::Target { &self.groups } } /// A sheet consists of a vector of blocks. /// /// If a sheet does not have any blocks, a block is automatically /// inferred to contain the entire sheet. /// /// If a sheet has multiple blocks, every block will be played at the /// same time. #[derive(Debug, Clone, Default)] pub struct Sheet { blocks: Vec, } impl Sheet { /// A plain constructor. pub fn new(blocks: Vec) -> Self { Self { blocks } } } impl Deref for Sheet { type Target = [SheetBlock]; fn deref(&self) -> &Self::Target { &self.blocks } } /// A parsing error. #[derive(Debug, Clone, Eq, PartialEq)] pub enum SheetParseError { /// Empty sheet is not allowed. EmptySheet, /// Encounters an invalid character in a comment. InvalidComment(usize), /// Expecting a closing block. HangingBlock(usize), /// Invalid block. InvalidBlock(usize), /// Expecting a closing group. HangingGroup(usize), /// Invalid group. InvalidGroup(usize), /// Invalid character InvalidChar(usize), /// Invalid instruction InvalidInstruction(usize), /// Invalid instrument instruction InvalidInstrument(usize), /// Invalid volume instruction InvalidVolume(usize), /// Invalid bpm instruction InvalidBPM(usize), /// Invalid tone InvalidTone(usize), /// Invalid octave InvalidOctave(usize), /// Invalid duration InvalidDuration(usize), /// Invalid repetition InvalidRepetition(usize), } impl Display for SheetParseError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { SheetParseError::EmptySheet => write!(f, "found an empty sheet"), SheetParseError::InvalidComment(pos) => write!(f, "invalid comment at {pos}"), SheetParseError::HangingBlock(pos) => write!(f, "expecting closing block at {pos}"), SheetParseError::InvalidBlock(pos) => write!(f, "invalid block at {pos}"), SheetParseError::HangingGroup(pos) => write!(f, "expecting closing group at {pos}"), SheetParseError::InvalidGroup(pos) => write!(f, "invalid group at {pos}"), SheetParseError::InvalidChar(pos) => write!(f, "invalid character at {pos}"), SheetParseError::InvalidInstruction(pos) => write!(f, "invalid instruction at {pos}"), SheetParseError::InvalidInstrument(pos) => write!(f, "invalid instrument at {pos}"), SheetParseError::InvalidVolume(pos) => write!(f, "invalid volume at {pos}"), SheetParseError::InvalidBPM(pos) => write!(f, "invalid BPM at {pos}"), SheetParseError::InvalidTone(pos) => write!(f, "invalid tone at {pos}"), SheetParseError::InvalidOctave(pos) => write!(f, "invalid octave at {pos}"), SheetParseError::InvalidDuration(pos) => write!(f, "invalid duration at {pos}"), SheetParseError::InvalidRepetition(pos) => write!(f, "invalid repetition at {pos}"), } } } impl Error for SheetParseError {} impl SheetParseError { /// Translate the positions in the error by a certain amount. pub fn shift_pos(&self, shift_len: usize) -> Self { match self { SheetParseError::EmptySheet => self.clone(), SheetParseError::InvalidComment(pos) => Self::InvalidComment(pos + shift_len), SheetParseError::HangingBlock(pos) => Self::HangingBlock(pos + shift_len), SheetParseError::InvalidBlock(pos) => Self::InvalidBlock(pos + shift_len), SheetParseError::HangingGroup(pos) => Self::HangingGroup(pos + shift_len), SheetParseError::InvalidGroup(pos) => Self::InvalidGroup(pos + shift_len), SheetParseError::InvalidChar(pos) => Self::InvalidChar(pos + shift_len), SheetParseError::InvalidInstruction(pos) => Self::InvalidInstruction(pos + shift_len), SheetParseError::InvalidInstrument(pos) => Self::InvalidInstrument(pos + shift_len), SheetParseError::InvalidVolume(pos) => Self::InvalidVolume(pos + shift_len), SheetParseError::InvalidBPM(pos) => Self::InvalidBPM(pos + shift_len), SheetParseError::InvalidTone(pos) => Self::InvalidTone(pos + shift_len), SheetParseError::InvalidOctave(pos) => Self::InvalidOctave(pos + shift_len), SheetParseError::InvalidDuration(pos) => Self::InvalidDuration(pos + shift_len), SheetParseError::InvalidRepetition(pos) => Self::InvalidRepetition(pos + shift_len), } } } fn skip_by_fn(s: &str, f: fn(char) -> bool) -> usize { for (index, c) in s.chars().enumerate() { if !f(c) { return index; } } s.len() } fn is_wsp(c: char) -> bool { c == ' ' || c == '\t' } fn is_wsp_or_newline(c: char) -> bool { is_wsp(c) || c == '\n' || c == '\r' } fn is_v_char(c: char) -> bool { ('!'..='~').contains(&c) } fn is_wsp_or_v_char(c: char) -> bool { is_wsp(c) || is_v_char(c) } // fn is_alpha(c: char) -> bool { // ('A'..='Z').contains(&c) || ('a'..='z').contains(&c) // } fn is_decimal_digit(c: char) -> bool { ('0'..='9').contains(&c) } // fn is_binary_digit(c: char) -> bool { // c == '0' || c == '1' // } // fn is_hexadecimal_digit(c: char) -> bool { // is_decimal_digit(c) || ('A'..='F').contains(&c) || ('a'..='f').contains(&c) // } // fn is_alpha_or_digit_or_hyphen(c: char) -> bool { // is_alpha(c) || is_decimal_digit(c) || c == '-' // } // #[allow(unused)] // fn is_char_not_quote(c: char) -> bool { // (' '..='~').contains(&c) && c != '"' // } /// Skip comments and newlines. fn skip_c_nl(s: &str) -> Result, SheetParseError> { if s.is_empty() { return Ok(Some(0)); } let s_len = s.len(); match s.chars().next().unwrap() { ';' => { let after_wsp_and_v_char = skip_by_fn(&s[1..], is_wsp_or_v_char); if 1 + after_wsp_and_v_char >= s_len { Ok(Some(s_len)) } else { match s.chars().nth(1 + after_wsp_and_v_char).unwrap() { '\n' | '\r' => Ok(Some(after_wsp_and_v_char + 2)), _ => Err(SheetParseError::InvalidComment(1 + after_wsp_and_v_char)), } } } '\n' | '\r' => Ok(Some(1)), _ => Ok(None), } } /// Skip whitespaces, newlines, and comments. fn skip_wsp_and_newline_and_comments(s: &str) -> Result { let mut s = s; let mut continue_skipping = true; let mut index = 0; while continue_skipping { continue_skipping = false; let skip_wsp = skip_by_fn(s, is_wsp_or_newline); if skip_wsp > 0 { continue_skipping = true; index += skip_wsp; if skip_wsp >= s.len() { return Ok(index); } s = &s[skip_wsp..]; } // skip comments match skip_c_nl(s).map_err(|e| e.shift_pos(index))? { Some(skipped_size) if skipped_size > 0 => { continue_skipping = true; index += skipped_size; if skipped_size >= s.len() { return Ok(index); } s = &s[skipped_size..]; } _ => {} } } Ok(index) } /// Skip white spaces, tabs, newlines, and comments altogether. macro_rules! skip_ws { ($s:ident, $index:ident, $fallback:expr) => { let skip_result = skip_wsp_and_newline_and_comments($s).map_err(|e| e.shift_pos($index))?; $index += skip_result; if skip_result >= $s.len() { $fallback } else { $s = &$s[skip_result..]; } }; } #[derive(Debug, Copy, Clone, Default)] struct CramRepetition(Option, Option); impl CramRepetition { fn repetition(&self) -> usize { self.1.unwrap_or(1usize) } fn cram(&self) -> Option { self.0 } fn set_cram(&mut self, cram: Option) { self.0 = cram; } fn set_repetition(&mut self, repetition: Option) { self.1 = repetition; } } fn parse_cram_repetition(s: &str) -> Result<(usize, CramRepetition), SheetParseError> { if s.is_empty() { return Ok((0, Default::default())); } let mut s = s; let mut index = 0; let mut result: CramRepetition = Default::default(); // The index is zero, so we do not need to translate the error. let (duration_len, duration) = parse_one_duration(s)?; if duration_len != 0 { index += duration_len; result.set_cram(Some(duration)); if duration_len >= s.len() { return Ok((index, result)); } s = &s[duration_len..]; skip_ws!(s, index, { return Ok((index, result)); }); } if s.starts_with('*') { index += 1; s = &s[1..]; skip_ws!(s, index, { return Err(SheetParseError::InvalidRepetition(index)); }); let digits_len = skip_by_fn(s, is_decimal_digit); if digits_len != 0 { index += digits_len; let num: usize = s[..digits_len].parse().unwrap(); result.set_repetition(Some(num)); } } Ok((index, result)) } fn parse_one_instruction(s: &str) -> Result<(usize, SheetUnit), SheetParseError> { if s.is_empty() { return Err(SheetParseError::InvalidInstruction(0)); } let mut s = s; let mut index = 0; if s.starts_with('i') { index += 1; s = &s[1..]; if s.starts_with("piano") { index += 5; Ok((index, Instrument::Piano.into())) } else if s.starts_with("violin") { index += 6; Ok((index, Instrument::Violin.into())) } else if s.starts_with("sinus") { index += 5; Ok((index, Instrument::Sinus.into())) } else if s.starts_with("tangent") { index += 7; Ok((index, Instrument::Tangent.into())) } else { Err(SheetParseError::InvalidInstrument(index)) } } else if s.starts_with('b') { index += 1; s = &s[1..]; let digits_len = skip_by_fn(s, is_decimal_digit); if digits_len == 0 { return Err(SheetParseError::InvalidBPM(index)); } // This should never fail because the string is supposed to // contain only decimal digits. let num: usize = (s[..digits_len]).parse().unwrap(); if num == 0 { return Err(SheetParseError::InvalidBPM(index)); } index += digits_len; let bpm: BPM = (num as f64).into(); Ok((index, bpm.into())) } else if s.starts_with('v') { index += 1; s = &s[1..]; let digits_len = skip_by_fn(s, is_decimal_digit); let num: usize = if digits_len == 0 { 0 } else { // This should never fail because the string is supposed // to contain only decimal digits. (s[..digits_len]).parse().unwrap() }; let mut frac: usize = 0; index += digits_len; s = &s[digits_len..]; if s.starts_with('.') { index += 1; s = &s[1..]; let frac_digits_len = skip_by_fn(s, is_decimal_digit); if frac_digits_len != 0 { index += frac_digits_len; frac = (s[..frac_digits_len]).parse().unwrap(); } else if digits_len == 0 { return Err(SheetParseError::InvalidVolume(index)); } } // This should never fail let volume: f64 = format!("{num}.{frac}").parse().unwrap(); let volume: Volume = volume.into(); Ok((index, volume.into())) } else { Err(SheetParseError::InvalidInstruction(index)) } } // This never fails, since a silence "r" cannot determine that the // following is invalid. fn parse_one_silence(s: &str) -> (usize, SheetUnit) { if s.is_empty() { return (0, Default::default()); } let mut s = s; let mut index = 0; let mut beats: Option = None; let digits_len = skip_by_fn(s, is_decimal_digit); if digits_len != 0 { // this will not fail let num: usize = s[..digits_len].parse().unwrap(); let beats_beats: Beats = (1f64 / num as f64).into(); beats = Some(beats_beats); if digits_len >= s.len() { let duration: SheetDuration = beats_beats.into(); return (index + digits_len, duration.into()); } index += digits_len; s = &s[digits_len..]; } let dots_len = skip_by_fn(s, |c| c == '.'); index += dots_len; let duration: SheetDuration = (beats, dots_len).into(); (index, duration.into()) } fn parse_one_duration(s: &str) -> Result<(usize, SheetDuration), SheetParseError> { if s.is_empty() { return Ok((0, Default::default())); } let mut s = s; let mut index = 0; if s.starts_with('p') { // a precise specification index += 1; s = &s[1..]; let digits_len = skip_by_fn(s, is_decimal_digit); if digits_len == 0 { return Err(SheetParseError::InvalidDuration(index)); } index += digits_len; let num: usize = s[..digits_len].parse().unwrap(); if digits_len >= s.len() { return Ok((index, (num as f64).into())); } s = &s[digits_len..]; let mut frac: usize = 0; if s.starts_with('.') { index += 1; s = &s[1..]; let frac_len = skip_by_fn(s, is_decimal_digit); index += frac_len; if frac_len != 0 { frac = s[..frac_len].parse().unwrap(); } } let duration_f64: f64 = format!("{num}.{frac}").parse().unwrap(); Ok((index, duration_f64.into())) } else { let digits_len = skip_by_fn(s, is_decimal_digit); let mut duration_f64 = None; let mut dots_len = 0; if digits_len != 0 { index += digits_len; let num: usize = s[..digits_len].parse().unwrap(); duration_f64 = Some(1f64 / num as f64); if digits_len < s.len() { s = &s[digits_len..]; } } if s.starts_with('.') { dots_len = skip_by_fn(s, |c| c == '.'); index += dots_len; } Ok((index, (duration_f64, dots_len).into())) } } /// Parse a chord. /// /// # Panic /// /// It assumes that the string starts with a valid letter. If the /// assumption is violated, this function panics. fn parse_one_chord(s: &str) -> Result<(usize, SheetUnit), SheetParseError> { if s.is_empty() { return Err(SheetParseError::InvalidTone(0)); } let mut s = s; let mut index = 0; let mut tones: Vec = Vec::new(); let mut sheet_duration = SheetDuration::default(); loop { let mut octave: Option = None; if s.starts_with('o') { index += 1; s = &s[1..]; let digits_len = skip_by_fn(s, is_decimal_digit); if digits_len == 0 { return Err(SheetParseError::InvalidOctave(index)); } else if digits_len >= s.len() { return Err(SheetParseError::InvalidTone(index + digits_len)); } let num: usize = s[..digits_len].parse().unwrap(); octave = Some(num.into()); index += digits_len; s = &s[digits_len..]; } else if s.starts_with('<') { let less_len = skip_by_fn(s, |c| c == '<'); if less_len >= s.len() { return Err(SheetParseError::InvalidTone(index + less_len)); } octave = Some((-(less_len as isize)).into()); index += less_len; s = &s[less_len..]; } else if s.starts_with('>') { let greater_len = skip_by_fn(s, |c| c == '>'); if greater_len >= s.len() { return Err(SheetParseError::InvalidTone(index + greater_len)); } octave = Some((greater_len as isize).into()); index += greater_len; s = &s[greater_len..]; } skip_ws!(s, index, { return Err(SheetParseError::InvalidTone(index)); }); let first_char = s.chars().next().unwrap(); let mut tone = match first_char { 'c' | 'C' => SheetSemitone(0isize), 'd' | 'D' => SheetSemitone(2isize), 'e' | 'E' => SheetSemitone(4isize), 'f' | 'F' => SheetSemitone(5isize), 'g' | 'G' => SheetSemitone(7isize), 'a' | 'A' => SheetSemitone(9isize), 'b' | 'B' => SheetSemitone(11isize), _ => { return Err(SheetParseError::InvalidTone(index)); } }; index += 1; if s.len() == 1 { tones.push(SheetTone::new(tone, octave)); break; } s = &s[1..]; if s.starts_with('+') { let plus_len = skip_by_fn(s, |c| c == '+'); tone.shift(plus_len as isize); index += plus_len; if plus_len >= s.len() { tones.push(SheetTone::new(tone, octave)); let chord: SheetChord = tones.into(); return Ok((index, chord.into())); } s = &s[plus_len..]; } else if s.starts_with('-') { let minus_len = skip_by_fn(s, |c| c == '-'); tone.shift(-(minus_len as isize)); index += minus_len; if minus_len >= s.len() { tones.push(SheetTone::new(tone, octave)); let chord: SheetChord = tones.into(); return Ok((index, chord.into())); } s = &s[minus_len..]; } tones.push(SheetTone::new(tone, octave)); skip_ws!(s, index, { let chord: SheetChord = tones.into(); return Ok((index, chord.into())); }); let (duration_len, duration) = parse_one_duration(s).map_err(|e| e.shift_pos(index))?; if duration_len != 0 { index += duration_len; sheet_duration = duration; break; } // If there is a forward slash then continue else just break if !s.starts_with('/') { break; } index += 1; s = &s[1..]; skip_ws!(s, index, { return Err(SheetParseError::InvalidTone(index)); }); } let chord: SheetChord = tones.into(); let unit = SheetUnit::Tone(chord, sheet_duration); Ok((index, unit)) } fn parse_one_unit(s: &str) -> Result<(usize, SheetUnit), SheetParseError> { if s.is_empty() { return Ok((0, Default::default())); } let mut s = s; let mut index = 0; let mut peek_iter = s.chars().peekable(); let first_char = peek_iter.peek().unwrap(); match first_char { 'i' => { s = &s[1..]; index += 1; parse_one_instruction(s) .map(|(len, unit)| (len + index, unit)) .map_err(|e| e.shift_pos(index)) } 'r' => { s = &s[1..]; index += 1; let (len, unit) = parse_one_silence(s); Ok((index + len, unit)) } 'o' | '<' | '>' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' => parse_one_chord(s), _ => Ok((0, Default::default())), } } fn parse_one_group(s: &str) -> Result<(usize, SheetGroup), SheetParseError> { // read units let mut s = s; let mut explicit_group = true; let mut index = 0; let mut result = SheetGroup::default(); result.nodes.push(SheetGroupNode::Intermediate(1, None)); result.edges.push(Vec::new()); let mut peek_iter = s.chars().peekable(); let first_char = peek_iter.peek(); match first_char { Some('(') => { // an explicit block let mut stack = vec![0]; let mut current_parent = 0; index += 1; s = &s[1..]; skip_ws!(s, index, { dbg!(); return Err(SheetParseError::HangingGroup(index)); }); loop { let (parsed_length, parsed_unit) = parse_one_unit(s).map_err(|e| e.shift_pos(index))?; if parsed_length == 0 { let mut peekable = s.chars().peekable(); let first_char = peekable.peek().unwrap(); match first_char { '(' => { result.nodes.push(SheetGroupNode::Intermediate(1, None)); result.edges.push(Vec::new()); let result_len = result.nodes.len(); result.edges[current_parent].push(result_len - 1); stack.push(current_parent); current_parent = result_len - 1; index += 1; s = &s[1..]; skip_ws!(s, index, { return Err(SheetParseError::HangingGroup(index)); }); continue; } ')' => { if stack.len() <= 1 { break; } index += 1; s = &s[1..]; skip_ws!(s, index, { return Err(SheetParseError::HangingGroup(index)); }); let (parsed_length, parsed_cram_repetition) = parse_cram_repetition(s).map_err(|e| e.shift_pos(index))?; if parsed_length != 0 { index += parsed_length; s = &s[parsed_length..]; result.nodes[current_parent] .set_repetition(parsed_cram_repetition.repetition()); result.nodes[current_parent] .set_cram(parsed_cram_repetition.cram()); skip_ws!(s, index, { return Err(SheetParseError::HangingGroup(index)); }); } current_parent = stack.pop().unwrap(); continue; } _ => { break; } } } index += parsed_length; s = &s[parsed_length..]; let leaf_node = SheetGroupNode::Unit(parsed_unit); let nodes_len = result.nodes.len(); result.nodes.push(leaf_node); result.edges.push(Vec::new()); result.edges[current_parent].push(nodes_len); skip_ws!(s, index, { dbg!(); return Err(SheetParseError::HangingGroup(index)); }); } if s.starts_with(')') { index += 1; s = &s[1..]; skip_ws!(s, index, { return Ok((index, result)); }); } else { dbg!(); return Err(SheetParseError::HangingGroup(index)); } } Some(')') => { dbg!(); return Err(SheetParseError::HangingGroup(index)); } Some(_) => { // an implicit group: we read a unit only explicit_group = false; let (parsed_length, parsed_unit) = parse_one_unit(s).map_err(|e| e.shift_pos(index))?; if parsed_length != 0 { index += parsed_length; let leaf_node = SheetGroupNode::Unit(parsed_unit); let nodes_len = result.nodes.len(); result.nodes.push(leaf_node); result.edges.push(Vec::new()); result.edges[0].push(nodes_len); if parsed_length >= s.len() { return Ok((index, result)); } s = &s[parsed_length..]; skip_ws!(s, index, { return Ok((index, result)); }); } } None => { // empty group } } if explicit_group && index == 1 { dbg!(); return Err(SheetParseError::HangingGroup(index)); } // else if !explicit_group && index == 0 { // dbg!(); // return Err(SheetParseError::InvalidGroup(index)); // } let (parsed_length, parsed_cram_repetition) = parse_cram_repetition(s).map_err(|e| e.shift_pos(index))?; if parsed_length != 0 { index += parsed_length; s = &s[parsed_length..]; result.nodes[0].set_repetition(parsed_cram_repetition.repetition()); result.nodes[0].set_cram(parsed_cram_repetition.cram()); } let skip_result = skip_wsp_and_newline_and_comments(s).map_err(|e| e.shift_pos(index))?; index += skip_result; // let mut peek_iter = s.chars().peekable(); // let first_char = peek_iter.peek(); // if explicit_group && first_char.copied() != Some(')') { // return Err(SheetParseError::HangingGroup(index)); // } // if explicit_group { // index += 1; // } Ok((index, result)) } fn parse_one_block(s: &str) -> Result<(usize, SheetBlock), SheetParseError> { // read groups let mut s = s; // let mut explicit_block = true; let mut index = 0; let mut result = SheetBlock::default(); let mut peek_iter = s.chars().peekable(); let first_char = peek_iter.peek(); match first_char { Some('{') => { // an explicit block index += 1; s = &s[1..]; skip_ws!(s, index, { return Err(SheetParseError::HangingBlock(index)); }); loop { let (parsed_length, parsed_group) = parse_one_group(s).map_err(|e| e.shift_pos(index))?; if parsed_length == 0 { break; } index += parsed_length; s = &s[parsed_length..]; result.groups.push(parsed_group); } skip_ws!(s, index, { return Err(SheetParseError::HangingBlock(index)); }); if s.starts_with('}') { index += 1; } else { return Err(SheetParseError::HangingBlock(index)); } } Some('}') => { dbg!(); return Err(SheetParseError::HangingBlock(index)); } Some(_) => { // an implicit block: we read till the end // explicit_block = false; loop { let (parsed_length, parsed_group) = parse_one_group(s).map_err(|e| e.shift_pos(index))?; if parsed_length == 0 { break; } index += parsed_length; s = &s[parsed_length..]; result.groups.push(parsed_group); } } None => { // empty block } } // let mut peek_iter = s.chars().peekable(); // let first_char = peek_iter.peek(); // if explicit_block && first_char.copied() != Some('}') { // return Err(SheetParseError::HangingBlock(index)); // } else if !explicit_block && !s.is_empty() { // return Err(SheetParseError::InvalidBlock(index)); // } // if explicit_block { // index += 1; // } Ok((index, result)) } impl std::str::FromStr for Sheet { type Err = SheetParseError; fn from_str(s: &str) -> Result { if s.is_empty() { return Err(SheetParseError::EmptySheet); } let mut s = s; let mut index: usize = 0; let mut result = Sheet::default(); loop { skip_ws!(s, index, break); // s is guaranteed to be non-empty at this point // look ahead one character to determine the situation if s.starts_with('\"') { // an assignment expression todo!(); // first skip whitespaces again } else { // a block let (block_len, block) = parse_one_block(s).map_err(|e| e.shift_pos(index))?; index += block_len; result.blocks.push(block); if block_len >= s.len() { break; } s = &s[block_len..]; } } Ok(result) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_sheet() -> Result<(), Box> { let s: &str = &String::from("{ (o4 a+ / < b- 4... o3 c+ ( < e-- 4 r8 ) 1 * 2 ) 4... * 12 > d. }"); println!("input = {s}"); let sheet: Sheet = s.parse().map_err(|e: SheetParseError| e.to_string())?; println!("sheet = {sheet:?}"); Ok(()) } #[test] fn test_group_do_cram() -> Result<(), String> { let s: &str = &String::from("(o4 a+ / < b- 4... o3 c+) 4... * 12"); println!("input = {s}"); let (index, mut group) = parse_one_group(s).map_err(|e| e.to_string())?; println!("{group:?}"); assert_eq!(index, s.len()); group.do_cram(&mut Beats::default()); println!("{group:?}"); match group.nodes[1] { SheetGroupNode::Unit(SheetUnit::Tone(_, duration)) => { assert_eq!(*duration.beats.unwrap(), 0.234375f64); } _ => { panic!("wrong node"); } } match group.nodes[2] { SheetGroupNode::Unit(SheetUnit::Tone(_, duration)) => { assert_eq!(*duration.beats.unwrap(), 0.234375f64); } _ => { panic!("wrong node"); } } Ok(()) } #[test] fn test_parse_group() -> Result<(), String> { let s: &str = &String::from("(o4 a+ / < b- 4... o3 c+) 4... * 12"); println!("input = {s}"); let (index, unit) = parse_one_group(s).map_err(|e| e.to_string())?; println!("{unit:?}"); assert_eq!(index, s.len()); Ok(()) } #[test] fn test_parse_chord() -> Result<(), SheetParseError> { let s: &str = &String::from("o4 a+ / < b- 4..."); let (index, unit) = parse_one_chord(s)?; assert_eq!(index, s.len()); assert_eq!(format!("{unit}"), "o4 10 / < 10,0.25..."); Ok(()) } #[test] fn test_skip_wsp_nl_c() -> Result<(), SheetParseError> { let mut s: &str = &String::from("\n\n\r\r \t; haha this is a comment\nthis is not a comment"); let s_len = s.len(); let mut index = 0; skip_ws!(s, index, ()); assert_eq!(index, 33); assert_eq!(s_len, 54); assert_eq!(s, "this is not a comment"); Ok(()) } }