Skip to main content

klib/core/
parser.rs

1//! A module for working with the parser for chord symbols.
2
3use pest_derive::Parser;
4
5use crate::core::{
6    base::Res,
7    mode_kind::ModeKind,
8    note::{self, Note},
9    octave::Octave,
10    scale_kind::ScaleKind,
11};
12
13/// A parser for chord symbols.
14///
15/// This is built from a PEG grammar defined in `chord.pest`.
16#[derive(Parser)]
17#[grammar = "../chord.pest"]
18pub struct ChordParser;
19
20// Helpers.
21
22/// Parses a [`Note`] [`str`] into a [`Note`].
23#[coverage(off)]
24pub fn note_str_to_note(note_str: &str) -> Res<Note> {
25    let chord = match note_str {
26        "A" => note::A,
27        "A#" | "A♯" => note::ASharp,
28        "A##" | "A𝄪" => note::ADoubleSharp,
29        "Ab" | "A♭" => note::AFlat,
30        "Abb" | "A𝄫" => note::ADoubleFlat,
31        "B" => note::B,
32        "B#" | "B♯" => note::BSharp,
33        "B##" | "B𝄪" => note::BDoubleSharp,
34        "Bb" | "B♭" => note::BFlat,
35        "Bbb" | "B𝄫" => note::BDoubleFlat,
36        "C" => note::C,
37        "C#" | "C♯" => note::CSharp,
38        "C##" | "C𝄪" => note::CDoubleSharp,
39        "Cb" | "C♭" => note::CFlat,
40        "Cbb" | "C𝄫" => note::CDoubleFlat,
41        "D" => note::D,
42        "D#" | "D♯" => note::DSharp,
43        "D##" | "D𝄪" => note::DDoubleSharp,
44        "Db" | "D♭" => note::DFlat,
45        "Dbb" | "D𝄫" => note::DDoubleFlat,
46        "E" => note::E,
47        "E#" | "E♯" => note::ESharp,
48        "E##" | "E𝄪" => note::EDoubleSharp,
49        "Eb" | "E♭" => note::EFlat,
50        "Ebb" | "E𝄫" => note::EDoubleFlat,
51        "F" => note::F,
52        "F#" | "F♯" => note::FSharp,
53        "F##" | "F𝄪" => note::FDoubleSharp,
54        "Fb" | "F♭" => note::FFlat,
55        "Fbb" | "F𝄫" => note::FDoubleFlat,
56        "G" => note::G,
57        "G#" | "G♯" => note::GSharp,
58        "G##" | "G𝄪" => note::GDoubleSharp,
59        "Gb" | "G♭" => note::GFlat,
60        "Gbb" | "G𝄫" => note::GDoubleFlat,
61        _ => return Err(crate::core::base::Err::msg("Please use fairly standard notes (e.g., don't use triple sharps / flats).")),
62    };
63
64    Ok(chord)
65}
66
67/// Parses an [`Octave`] [`str`] into an [`Octave`].
68#[coverage(off)]
69pub fn octave_str_to_octave(note_str: &str) -> Res<Octave> {
70    let octave = match note_str {
71        "0" => Octave::Zero,
72        "1" => Octave::One,
73        "2" => Octave::Two,
74        "3" => Octave::Three,
75        "4" => Octave::Four,
76        "5" => Octave::Five,
77        "6" => Octave::Six,
78        "7" => Octave::Seven,
79        "8" => Octave::Eight,
80        "9" => Octave::Nine,
81        _ => return Err(crate::core::base::Err::msg("Please use a valid octave (0 - 9).")),
82    };
83
84    Ok(octave)
85}
86
87/// Parses a mode name string into a [`ModeKind`].
88#[coverage(off)]
89pub fn mode_name_str_to_mode_kind(mode_str: &str) -> Res<ModeKind> {
90    let normalized = mode_str
91        .to_lowercase()
92        .replace("♮", "natural")
93        .replace("♯", "sharp")
94        .replace("#", "sharp")
95        .replace("♭", "flat")
96        .replace("b", "flat")
97        .replace(" ", "");
98
99    let mode = match normalized.as_str() {
100        // Major scale modes
101        "ionian" => ModeKind::Ionian,
102        "dorian" => ModeKind::Dorian,
103        "phrygian" => ModeKind::Phrygian,
104        "lydian" => ModeKind::Lydian,
105        "mixolydian" => ModeKind::Mixolydian,
106        "aeolian" => ModeKind::Aeolian,
107        "locrian" => ModeKind::Locrian,
108
109        // Harmonic minor modes
110        "locriannatural6" | "locriannat6" => ModeKind::LocrianNatural6,
111        "ioniansharp5" | "ionianaugmented" | "majorsharp5" => ModeKind::IonianSharp5,
112        "doriansharp4" => ModeKind::DorianSharp4,
113        "phrygiandominant" | "spanishphrygian" | "phrygianmajor" => ModeKind::PhrygianDominant,
114        "lydiansharp2" => ModeKind::LydianSharp2,
115        "ultralocrian" => ModeKind::Ultralocrian,
116
117        // Melodic minor modes
118        "dorianflat2" | "phrygiannatural6" | "phrygiannat6" => ModeKind::DorianFlat2,
119        "lydianaugmented" | "lydiansharp5" => ModeKind::LydianAugmented,
120        "lydiandominant" | "lydianflat7" | "mixolydiansharp4" | "acoustic" | "acousticscale" => ModeKind::LydianDominant,
121        "mixolydianflat6" | "aeoliandominant" => ModeKind::MixolydianFlat6,
122        "locriannatural2" | "locriannat2" | "locriansharp2" => ModeKind::LocrianNatural2,
123        "altered" | "alteredscale" | "superlocrian" => ModeKind::Altered,
124
125        _ => return Err(crate::core::base::Err::msg("Unknown mode name")),
126    };
127
128    Ok(mode)
129}
130
131/// Parses a scale name string into a [`ScaleKind`].
132#[coverage(off)]
133pub fn scale_name_str_to_scale_kind(scale_str: &str) -> Res<ScaleKind> {
134    let normalized = scale_str.to_lowercase().replace(" ", "").replace("-", "");
135    let scale = match normalized.as_str() {
136        "major" => ScaleKind::Major,
137        "naturalminor" => ScaleKind::NaturalMinor,
138        "harmonicminor" => ScaleKind::HarmonicMinor,
139        "melodicminor" => ScaleKind::MelodicMinor,
140        "wholetone" => ScaleKind::WholeTone,
141        "chromatic" => ScaleKind::Chromatic,
142        "diminishedwholehalf" => ScaleKind::DiminishedWholeHalf,
143        "diminishedhalfwhole" => ScaleKind::DiminishedHalfWhole,
144        "majorpentatonic" => ScaleKind::MajorPentatonic,
145        "minorpentatonic" => ScaleKind::MinorPentatonic,
146        "blues" => ScaleKind::Blues,
147        _ => return Err(crate::core::base::Err::msg("Unknown scale name")),
148    };
149
150    Ok(scale)
151}