summaryrefslogtreecommitdiff
path: root/src/sheet/mod.rs
diff options
context:
space:
mode:
authorJSDurand <mmemmew@gmail.com>2022-08-24 23:54:13 +0800
committerJSDurand <mmemmew@gmail.com>2022-08-24 23:54:13 +0800
commite954df3f896bd18494cd27d77b26bbb2005de8a7 (patch)
tree7c81b759e41665ffd3fcd77f2f1036b59c03bc79 /src/sheet/mod.rs
First commit
Now the project is in a somewhat complete state, ready for future enhancements.
Diffstat (limited to 'src/sheet/mod.rs')
-rw-r--r--src/sheet/mod.rs1905
1 files changed, 1905 insertions, 0 deletions
diff --git a/src/sheet/mod.rs b/src/sheet/mod.rs
new file mode 100644
index 0000000..581301c
--- /dev/null
+++ b/src/sheet/mod.rs
@@ -0,0 +1,1905 @@
+#![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<Volume> for Instruction {
+ fn from(volume: Volume) -> Self {
+ Self::VolumeTo(volume)
+ }
+}
+
+impl From<BPM> for Instruction {
+ fn from(bpm: BPM) -> Self {
+ Self::BPMTo(bpm)
+ }
+}
+
+impl From<Instrument> 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<usize> for SheetOctave {
+ fn from(abs: usize) -> Self {
+ Self::Absolute(abs)
+ }
+}
+
+impl From<isize> 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<SheetOctave>,
+}
+
+impl SheetTone {
+ fn new(semitone: SheetSemitone, octave: Option<SheetOctave>) -> 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<T> From<T> for SheetTone
+where
+ T: Into<SheetSemitone>,
+{
+ 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<SheetTone>);
+
+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<T> From<T> for SheetChord
+where
+ T: Into<SheetTone>,
+{
+ fn from(arg: T) -> Self {
+ Self(vec![arg.into()])
+ }
+}
+
+impl<T> From<Vec<T>> for SheetChord
+where
+ T: Into<SheetTone>,
+{
+ fn from(arg: Vec<T>) -> 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<Beats>,
+ dots: usize,
+}
+
+impl SheetDuration {
+ /// Return an optional `Beats`.
+ pub fn beats(&self) -> Option<Beats> {
+ 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<usize> for SheetDuration {
+ fn from(dots: usize) -> Self {
+ let beats = Default::default();
+ Self { beats, dots }
+ }
+}
+
+impl<T: Into<Beats>> From<T> for SheetDuration {
+ fn from(arg: T) -> Self {
+ let beats = arg.into();
+ let beats = Some(beats);
+ let dots = Default::default();
+ Self { beats, dots }
+ }
+}
+
+impl<T: Into<Beats>> From<(Option<T>, usize)> for SheetDuration {
+ fn from((arg, dots): (Option<T>, 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<T: Into<Instruction>> From<T> for SheetUnit {
+ fn from(instruction: T) -> Self {
+ let instruction = instruction.into();
+
+ Self::Instruction(instruction)
+ }
+}
+
+impl From<SheetDuration> for SheetUnit {
+ fn from(duration: SheetDuration) -> Self {
+ Self::Silence(duration)
+ }
+}
+
+impl From<SheetChord> 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<SheetDuration>),
+ /// 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<SheetDuration>) {
+ 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<Beats>) {
+ 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<SheetGroupNode>,
+ edges: Vec<Vec<usize>>,
+}
+
+impl SheetGroup {
+ /// Construct a new group.
+ pub fn new(nodes: Vec<SheetGroupNode>, edges: Vec<Vec<usize>>) -> 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<Beats> = 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<SheetGroup>,
+}
+
+impl SheetBlock {
+ /// A plain constructor.
+ pub fn new(groups: Vec<SheetGroup>) -> 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<SheetBlock>,
+}
+
+impl Sheet {
+ /// A plain constructor.
+ pub fn new(blocks: Vec<SheetBlock>) -> 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<Option<usize>, 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<usize, SheetParseError> {
+ 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<SheetDuration>, Option<usize>);
+
+impl CramRepetition {
+ fn repetition(&self) -> usize {
+ self.1.unwrap_or(1usize)
+ }
+
+ fn cram(&self) -> Option<SheetDuration> {
+ self.0
+ }
+
+ fn set_cram(&mut self, cram: Option<SheetDuration>) {
+ self.0 = cram;
+ }
+
+ fn set_repetition(&mut self, repetition: Option<usize>) {
+ 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<Beats> = 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<SheetTone> = Vec::new();
+
+ let mut sheet_duration = SheetDuration::default();
+
+ loop {
+ let mut octave: Option<SheetOctave> = 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<Self, Self::Err> {
+ 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<dyn std::error::Error>> {
+ 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(())
+ }
+}