abc-parser 0.2.0

An ABC music notation parser. Turns ABC text into Rust data structures and back.
Documentation
use datatypes::*;

number -> u32
    = n:$([1-9][0-9]*) { n.parse().unwrap() }

space
    = [ \t]+

pub tune_book -> TuneBook
    = h:file_header? ts:tune* { TuneBook::new(h, ts) } /
      !. { TuneBook::new(None, vec![]) }

pub file_header -> FileHeader
    = fields:info_field* [\n] { FileHeader::new(fields) }

pub tune -> Tune
    = h:tune_header b:tune_body? { Tune::new(h, b) }

pub tune_header -> TuneHeader
    = "X:" n:number [\n]
      t:info_field<'T'>
      extra:info_field_non_special*
      k:info_field<'K'> {
        let mut info = vec![InfoField::new('X', n.to_string()), t];
        for field in extra.into_iter() { info.push(field); }
        info.push(k);
        TuneHeader::new(info)
    }

info_field<N> = name:$(N) ":" t:$((![\n].)*) [\n] {
    InfoField::new(
        name.chars().next().unwrap(),
        t.to_string()
    )
}

info_field -> InfoField
    = info_field<[A-Za-z]>

info_field_non_special -> InfoField
    = info_field<![KX][A-Za-z]>

pub tune_body -> TuneBody
    = m:music_line+ { TuneBody::new(m) } /
      [\n] { TuneBody::new(vec![]) }

pub music_line -> MusicLine
    = s:music_symbol+ music_line_end { MusicLine::new(s) }

pub music_symbol -> MusicSymbol
    = n:note "`"* { n } /
      r:rest { r } /
      c:chord { c } /
      b:bar { b } /
      e:ending { e } /
      g:grace_notes { g } /
      t:tuplet { t } /
      space { MusicSymbol::VisualBreak() }

pub note -> MusicSymbol
    = d:decoration? a:accidental? n:$([a-gA-G]) o:octave? l:length? {
        MusicSymbol::new_note(
            d, a, n.chars().next().unwrap(), o.unwrap_or(1), l.unwrap_or(1.0)
        )
    }

pub bar -> MusicSymbol
    = b:$([:|[\]]+) { MusicSymbol::Bar(b.to_string()) }

music_line_end
    = [\n] / !.

decoration -> Decoration
    = d:decoration_symbol space? { d }

decoration_symbol -> Decoration
    = "." { Decoration::Staccato() } /
      "~" { Decoration::Roll() } /
      "H" { Decoration::Fermata() } /
      "L" { Decoration::Accent() } /
      "M" { Decoration::LowerMordent() } /
      "O" { Decoration::Coda() } /
      "P" { Decoration::UpperMordent() } /
      "S" { Decoration::Segno() } /
      "T" { Decoration::Trill() } /
      "u" { Decoration::UpBow() } /
      "v" { Decoration::DownBow() } /
      "!" d:$((![\n!].)+) "!" { Decoration::Unresolved(d.to_string()) }

accidental -> Accidental
    = "^^" { Accidental::DoubleSharp() } /
      "__" { Accidental::DoubleFlat() } /
      "^" { Accidental::Sharp() } /
      "_" { Accidental::Flat() } /
      "=" { Accidental::Natural() }

octave -> i8
    = p:$([,']+) {
        let mut octave = 1;
        for c in p.chars() {
            match c {
                ',' => octave -= 1,
                '\'' => octave += 1,
                _ => panic!("Parser malfunctioned. Unexpected octave identifier")
            }
        }
        octave
    }

length -> f32
    = "/" n:number { 1.0 / (n as f32) } /
      s:$("/"+) { 1.0 / (s.len() as f32).exp2() } /
      n1:number "/" n2:number { (n1 as f32) / (n2 as f32) } /
      n:number { n as f32 }

pub rest -> MusicSymbol
    = "z" n:number? { MusicSymbol::Rest(Rest::Note(n.unwrap_or(1))) } /
      "Z" n:number? { MusicSymbol::Rest(Rest::Measure(n.unwrap_or(1))) } /
      "x" n:number? { MusicSymbol::Rest(Rest::NoteHidden(n.unwrap_or(1))) } /
      "X" n:number? { MusicSymbol::Rest(Rest::MeasureHidden(n.unwrap_or(1))) }

pub ending -> MusicSymbol
    = "[" n:number space? { MusicSymbol::Ending(n) }

pub chord -> MusicSymbol
    = d:decoration? "[" n:note+ "]" l:length? {
        MusicSymbol::Chord {
            decoration: d,
            notes: n,
            length: l.unwrap_or(1f32)
        }
    }

pub grace_notes -> MusicSymbol
    = "{" a:"/"? n:note* "}" {
        MusicSymbol::GraceNotes {
            acciaccatura: a,
            notes: n
        }
    }

pub tuplet -> MusicSymbol
    = "(" p:number space? n:note*<{ p as usize }> {?
          MusicSymbol::tuplet_with_defaults(p, None, None, n)
      } /
      "(" p:number ":" q:number? ":" r:number? space? n:note*<{ p as usize }> {?
          MusicSymbol::tuplet_with_defaults(p, q, r, n)
      } /
      "(" p:number ":" q:number space? n:note*<{ p as usize }> {?
          MusicSymbol::tuplet_with_defaults(p, Some(q), None, n)
      }