use crate::datatypes::*;
peg::parser! {
pub grammar abc() for str {
pub rule tune_book() -> TuneBook =
c:ignored_line()* h:file_header()? ts:tune()* { TuneBook::new(c, h, ts) } /
![_] { TuneBook::new(vec![], None, vec![]) }
pub rule ignored_line() -> IgnoredLine =
"\n" { IgnoredLine::EmptyLine } /
c:comment_line() { IgnoredLine::Comment(c) }
pub rule file_header() -> FileHeader =
l:header_line()* "\n" { FileHeader::new(l) }
pub rule tune() -> Tune =
h:tune_header() b:tune_body()? { Tune::new(h, b) }
pub rule tune_header() -> TuneHeader =
c1:comment_line()*
"X:" optional_space() n:number() optional_space() cx:inline_comment()? music_line_end()
c2:comment_line()*
t:info_field_header(<$("T")>) ct:inline_comment()? music_line_end()
extra:info_field_header_non_special()*
k:info_field_header(<$("K")>) ck:inline_comment()? music_line_end()
c3:comment_line()* {
let mut info = c1.into_iter().map(HeaderLine::Comment).collect::<Vec<_>>();
info.push(
HeaderLine::Field(
InfoField::new('X', n.to_string()),
cx.map(|s| Comment::Comment(s.to_string())),
)
);
info.extend(c2.into_iter().map(HeaderLine::Comment));
info.push(HeaderLine::Field(t, ct.map(|s| Comment::Comment(s.to_string()))));
info.extend(extra);
info.push(HeaderLine::Field(k, ck.map(|s| Comment::Comment(s.to_string()))));
info.extend(c3.into_iter().map(HeaderLine::Comment));
TuneHeader::new(info)
}
rule info_field_header<'a>(n: rule<&'a str>) -> InfoField =
name:n() ":" optional_space() t:$(([^ '\n' | '%'])*) {
InfoField::new(
name.chars().next().unwrap(),
t.to_string()
)
}
rule info_field_inline() -> InfoField =
name:$(['H'..='Y' | 'h'..='y' | '+']) ":" optional_space() t:$((!"]"!"\n"[_])*) {
InfoField::new(
name.chars().next().unwrap(),
t.to_string()
)
}
rule header_line() -> HeaderLine =
f:info_field_header(<$(['A'..='Z' | 'a'..='z' | '+'])>)
c:inline_comment()?
music_line_end() {
HeaderLine::Field(f, c.map(|s| Comment::Comment(s.to_string())))
} /
c:comment_line() { HeaderLine::Comment(c) }
rule info_field_header_non_special() -> HeaderLine =
f:info_field_header(<$(!['K' | 'X']['A'..='Z' | 'a'..='z' | '+'])>)
c:inline_comment()?
music_line_end() {
HeaderLine::Field(f, c.map(|s| Comment::Comment(s.to_string())))
} /
c:comment_line() { HeaderLine::Comment(c) }
pub rule tune_body() -> TuneBody =
l:tune_line()+ { TuneBody::new(l) } /
"\n" { TuneBody::new(vec![]) }
pub rule tune_line() -> TuneLine =
c:comment_line() { TuneLine::Comment(c) } /
m:music_line() { TuneLine::Music(m) } /
s:symbol_line() { TuneLine::Symbol(s) } /
l:lyric_line() { TuneLine::Lyric(l) } /
i:inline_field_line() { TuneLine::Music(i) }
pub rule music_line() -> MusicLine =
s:music_symbol()+ c:inline_comment()? music_line_end() {
let mut symbols = s;
if let Some(c) = c {
symbols.push(
MusicSymbol::Comment(Comment::Comment(c.to_string()))
);
}
MusicLine::new(symbols)
}
pub rule symbol_line() -> SymbolLine =
"s:" s:symbol_line_symbol()* music_line_end() {
SymbolLine::new(s)
}
pub rule lyric_line() -> LyricLine =
"w:" s:lyric_line_symbol()* music_line_end() {
LyricLine::new(s)
}
pub rule inline_field_line() -> MusicLine =
i:info_field_inline() music_line_end() {
MusicLine::new(vec![MusicSymbol::InlineField(i, false)])
}
pub rule music_symbol() -> MusicSymbol =
broken_rhythm() /
note() /
rest() /
spacer() /
chord() /
"[" i:info_field_inline() "]" { MusicSymbol::InlineField(i, true) } /
bar() /
beam() /
ending() /
grace_notes() /
tuplet() /
slur() /
d:decoration() { MusicSymbol::Decoration(d) } /
a:annotation() { MusicSymbol::Annotation(a) } /
music_space() /
reserved()
pub rule symbol_line_symbol() -> SymbolLineSymbol =
s:symbol_alignment() { SymbolLineSymbol::SymbolAlignment(s) } /
a:annotation() { SymbolLineSymbol::Annotation(a) } /
d:decoration() { SymbolLineSymbol::Decoration(d) } /
s:space() { SymbolLineSymbol::Space(s.to_string()) }
pub rule lyric_line_symbol() -> LyricSymbol =
s:symbol_alignment() { LyricSymbol::SymbolAlignment(s) } /
l:$([^ '-' | '_' | '*' | '~' | '\\' | '|' | ' ' | '\n']+) {
LyricSymbol::Syllable(l.to_string())
} /
s:space() { LyricSymbol::Space(s.to_string()) }
pub rule broken_rhythm() -> MusicSymbol =
b:(t:broken_rhythm_target() s:music_space()? {
match s {
Some(s) => vec![t, s],
None => vec![t],
}
})
r:$("<"*<1,3> / ">"*<1,3>)
a:(s:music_space()? t:(broken_rhythm() / broken_rhythm_target()) {
match s {
Some(s) => vec![s, t],
None => vec![t],
}
}) {
MusicSymbol::BrokenRhythm {
rhythm: r.to_string(),
before: b,
after: a,
}
}
pub rule broken_rhythm_target() -> MusicSymbol =
note() /
rest() /
chord() /
grace_notes()
pub rule note() -> MusicSymbol =
a:accidental()? n:note_uppercase() o:octave()? l:length()? t:tie()? {
MusicSymbol::new_note(
a, n, o.unwrap_or(1), l, t
)
} /
a:accidental()? n:note_lowercase() o:octave()? l:length()? t:tie()? {
MusicSymbol::new_note(
a, n, o.unwrap_or(1) + 1, l, t
)
}
pub rule bar() -> MusicSymbol =
b:$(
"."?
(
[':' | '|' | '\\' | ']'] /
"[" !['H'..='Y' | 'h'..='y' | '1'..='9']
)+
)
e:ending_number()?
{ MusicSymbol::Bar(b.to_string(), e) }
pub rule beam() -> MusicSymbol =
b:$("`"+) { MusicSymbol::Beam(b.to_string()) }
pub rule slur() -> MusicSymbol =
"(" { MusicSymbol::Slur(Slur::Begin) } /
".(" { MusicSymbol::Slur(Slur::BeginDotted) } /
")" { MusicSymbol::Slur(Slur::End) }
pub rule rest() -> MusicSymbol =
"z" l:length()? { MusicSymbol::Rest(Rest::Note(l)) } /
"Z" l:length()? { MusicSymbol::Rest(Rest::Measure(l)) } /
"x" l:length()? { MusicSymbol::Rest(Rest::NoteHidden(l)) } /
"X" l:length()? { MusicSymbol::Rest(Rest::MeasureHidden(l)) }
pub rule spacer() -> MusicSymbol =
"y" { MusicSymbol::Spacer }
pub rule ending() -> MusicSymbol =
"[" i:ending_identifier() { MusicSymbol::Ending(i.to_string()) }
pub rule ending_identifier() -> String =
n1:ending_number() n2:("," n:ending_number() { n })* {
let mut s = n1;
for n in n2 {
s.push(',');
s.push_str(&n);
}
s
}
pub rule ending_number() -> String =
d1:number_digits() d2:("-" d:number_digits() { d } )? {
let mut s = d1.to_string();
if let Some(d2) = d2 {
s.push('-');
s.push_str(d2);
}
s
}
pub rule chord() -> MusicSymbol =
"[" n:chord_inner_symbol()+ "]" l:length()? t:tie()? {
MusicSymbol::Chord {
notes: n,
length: l,
tie: t,
}
}
pub rule chord_inner_symbol() -> MusicSymbol =
note() /
d:decoration() { MusicSymbol::Decoration(d) }
pub rule grace_notes() -> MusicSymbol =
"{" a:"/"? n:grace_notes_inner_symbol()+ "}" {
MusicSymbol::GraceNotes {
acciaccatura: a,
notes: n
}
}
pub rule grace_notes_inner_symbol() -> MusicSymbol =
broken_rhythm() /
note() /
music_space()
pub rule tuplet() -> MusicSymbol =
"("
p:digit_at_least_2()
q:(":" x:digit_at_least_1()? { x })?
r:(":" x:digit_at_least_1()? { x })? {
MusicSymbol::new_tuplet(p, q.flatten(), r.flatten())
}
pub rule music_space() -> MusicSymbol =
s:space() { MusicSymbol::Space(s.to_string()) }
pub rule reserved() -> MusicSymbol =
r:['#' | '*' | ';' | '?' | '@'] {
MusicSymbol::Reserved(r.to_string())
}
rule accidental() -> Accidental =
"^^" { Accidental::DoubleSharp } /
"__" { Accidental::DoubleFlat } /
"^" { Accidental::Sharp } /
"_" { Accidental::Flat } /
"=" { Accidental::Natural }
pub rule note_uppercase() -> Note =
"A" { Note::A } /
"B" { Note::B } /
"C" { Note::C } /
"D" { Note::D } /
"E" { Note::E } /
"F" { Note::F } /
"G" { Note::G }
pub rule note_lowercase() -> Note =
"a" { Note::A } /
"b" { Note::B } /
"c" { Note::C } /
"d" { Note::D } /
"e" { Note::E } /
"f" { Note::F } /
"g" { Note::G }
rule 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
}
rule tie() -> Tie =
".-" { Tie::Dotted } /
"-" { Tie::Solid }
rule inline_comment() -> &'input str =
"%" c:$([^'\n']*) { c }
rule comment_line() -> Comment =
"%" c:inline_comment() music_line_end() {
Comment::StylesheetDirective(c.to_string())
} /
s:space() c:inline_comment() music_line_end() {
Comment::CommentLine(s.to_string(), c.to_string())
} /
c:inline_comment() music_line_end() {
Comment::CommentLine("".to_string(), c.to_string())
}
rule length() -> Length =
"/" n:number() {
Length::new(1.0 / (n as f32))
} /
s:$("/"+) {
Length::new(1.0 / (s.len() as f32).exp2())
} /
n1:number() "/" n2:number() {
Length::new((n1 as f32) / (n2 as f32))
} /
n:number() { Length::new(n as f32) }
pub rule annotation() -> Annotation =
"\"" p:placement()? s:$((!"\""[_])*) "\"" {
Annotation::new(p, s.to_string())
}
pub rule placement() -> Placement =
"^" { Placement::Above } /
"_" { Placement::Below } /
"<" { Placement::Left } /
">" { Placement::Right } /
"@" { Placement::Auto }
rule decoration() -> 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())
}
pub rule symbol_alignment() -> SymbolAlignment =
"-" { SymbolAlignment::Break } /
"_" { SymbolAlignment::Extend } /
"*" { SymbolAlignment::Skip } /
"~" { SymbolAlignment::Space } /
"\\-" { SymbolAlignment::Hyphen } /
"|" { SymbolAlignment::Bar }
rule number() -> u32 =
n:number_digits() { n.parse().unwrap() }
rule number_digits() -> &'input str =
$(['1'..='9']['0'..='9']*)
rule digit_at_least_1() -> u32 =
n:$(['1'..='9']) { n.parse().unwrap() }
rule digit_at_least_2() -> u32 =
n:$(['2'..='9']) { n.parse().unwrap() }
rule space() -> &'input str =
s:$([' ' | '\t']+) { s }
rule optional_space() =
[' ' | '\t']*
rule music_line_end() =
"\n" / ![_]
}
}