Skip to main content

croma_core/syntax/
music.rs

1//! Music-line surface AST: the `*Syntax` types produced by parsing an ABC
2//! music line into notes, rests, chords, decorations, and barlines.
3
4use crate::diagnostic::Span;
5use crate::model::{Accidental, BarlineKind, Fraction, RestVisibility};
6use crate::syntax::field::{
7    InlineFieldSyntax, MalformedSyntax, MusicFieldLine, PreservedDirectiveSyntax,
8    ScoreDirectiveSyntax, UnsupportedSyntax,
9};
10use crate::syntax::lyric::{LyricLineSyntax, SymbolLineSyntax};
11
12#[derive(Debug, Clone, PartialEq, Eq, Default)]
13pub struct ParsedMusicDocument {
14    pub tunes: Vec<ParsedTuneMusic>,
15}
16
17impl ParsedMusicDocument {
18    pub fn tune(&self, tune_index: usize) -> Option<&ParsedTuneMusic> {
19        self.tunes.iter().find(|tune| tune.tune_index == tune_index)
20    }
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct ParsedTuneMusic {
25    pub tune_index: usize,
26    pub span: Span,
27    pub lines: Vec<MusicLine>,
28    pub body_fields: Vec<MusicFieldLine>,
29    pub lyric_lines: Vec<LyricLineSyntax>,
30    pub symbol_lines: Vec<SymbolLineSyntax>,
31    pub score_directives: Vec<ScoreDirectiveSyntax>,
32    pub preserved_directives: Vec<PreservedDirectiveSyntax>,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct MusicLine {
37    pub line_index: usize,
38    pub span: Span,
39    pub code_span: Span,
40    pub tokens: Vec<MusicToken>,
41    pub items: Vec<MusicItem>,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct MusicToken {
46    pub kind: MusicTokenKind,
47    pub span: Span,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum MusicTokenKind {
52    Whitespace,
53    Accidental,
54    Pitch,
55    OctaveMark,
56    Length,
57    Rest,
58    MultiMeasureRest,
59    Spacer,
60    Chord,
61    GraceGroup,
62    ChordSymbol,
63    Annotation,
64    Decoration,
65    Tuplet,
66    Slur,
67    Tie,
68    BrokenRhythm,
69    Overlay,
70    RepeatEnding,
71    Barline,
72    InlineField,
73    Unsupported,
74    Malformed,
75    Comment,
76    ScoreLineBreak,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
80#[allow(clippy::large_enum_variant)]
81pub enum MusicItem {
82    Note(NoteSyntax),
83    Rest(RestSyntax),
84    MultiMeasureRest(MultiMeasureRestSyntax),
85    Spacer(SpacerSyntax),
86    Chord(ChordSyntax),
87    GraceGroup(GraceGroupSyntax),
88    ChordSymbol(QuotedTextSyntax),
89    Annotation(QuotedTextSyntax),
90    Decoration(DecorationSyntax),
91    Tuplet(TupletSyntax),
92    Slur(SlurSyntax),
93    Tie(TieSyntax),
94    BrokenRhythm(BrokenRhythmSyntax),
95    Overlay(OverlaySyntax),
96    VariantEnding(VariantEndingSyntax),
97    Barline(BarlineSyntax),
98    InlineField(InlineFieldSyntax),
99    Unsupported(UnsupportedSyntax),
100    Malformed(MalformedSyntax),
101}
102
103impl MusicItem {
104    pub fn span(&self) -> Span {
105        match self {
106            Self::Note(item) => item.span,
107            Self::Rest(item) => item.span,
108            Self::MultiMeasureRest(item) => item.span,
109            Self::Spacer(item) => item.span,
110            Self::Chord(item) => item.span,
111            Self::GraceGroup(item) => item.span,
112            Self::ChordSymbol(item) | Self::Annotation(item) => item.span,
113            Self::Decoration(item) => item.span,
114            Self::Tuplet(item) => item.span,
115            Self::Slur(item) => item.span,
116            Self::Tie(item) => item.span,
117            Self::BrokenRhythm(item) => item.span,
118            Self::Overlay(item) => item.span,
119            Self::VariantEnding(item) => item.span,
120            Self::Barline(item) => item.span,
121            Self::InlineField(item) => item.span,
122            Self::Unsupported(item) => item.span,
123            Self::Malformed(item) => item.span,
124        }
125    }
126}
127
128#[derive(Debug, Clone, PartialEq, Eq)]
129pub struct NoteSyntax {
130    pub span: Span,
131    pub attachments: AttachmentBundle,
132    pub accidental: Option<AccidentalSyntax>,
133    pub pitch: PitchSyntax,
134    pub octave_marks: Vec<OctaveMarkSyntax>,
135    pub length: Option<LengthSyntax>,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Default)]
139pub struct AttachmentBundle {
140    pub grace_groups: Vec<GraceGroupSyntax>,
141    pub chord_symbols: Vec<QuotedTextSyntax>,
142    pub annotations: Vec<QuotedTextSyntax>,
143    pub decorations: Vec<DecorationSyntax>,
144}
145
146impl AttachmentBundle {
147    pub fn is_empty(&self) -> bool {
148        self.grace_groups.is_empty()
149            && self.chord_symbols.is_empty()
150            && self.annotations.is_empty()
151            && self.decorations.is_empty()
152    }
153
154    pub(crate) fn span_start(&self) -> Option<usize> {
155        self.grace_groups
156            .iter()
157            .map(|item| item.span.start)
158            .chain(self.chord_symbols.iter().map(|item| item.span.start))
159            .chain(self.annotations.iter().map(|item| item.span.start))
160            .chain(self.decorations.iter().map(|item| item.span.start))
161            .min()
162    }
163
164    pub(crate) fn push_quoted_text(&mut self, text: QuotedTextSyntax) {
165        match text.kind {
166            QuotedTextKind::ChordSymbol => self.chord_symbols.push(text),
167            QuotedTextKind::Annotation(_) => self.annotations.push(text),
168        }
169    }
170
171    /// Appends `other`'s attachments after this bundle's, per category. Used
172    /// when a nested construct (grace group) restores the caller's pending
173    /// bundle: the caller's items came first in the source, so they keep
174    /// their position; `span_start()` stays correct because it is computed
175    /// as the minimum over all member spans.
176    pub(crate) fn extend(&mut self, other: AttachmentBundle) {
177        self.grace_groups.extend(other.grace_groups);
178        self.chord_symbols.extend(other.chord_symbols);
179        self.annotations.extend(other.annotations);
180        self.decorations.extend(other.decorations);
181    }
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
185pub struct PitchSyntax {
186    pub step: char,
187    pub span: Span,
188}
189
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub struct AccidentalSyntax {
192    pub sign: Accidental,
193    pub span: Span,
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub struct OctaveMarkSyntax {
198    pub mark: OctaveMark,
199    pub span: Span,
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub enum OctaveMark {
204    Lower,
205    Raise,
206}
207
208#[derive(Debug, Clone, PartialEq, Eq)]
209pub struct LengthSyntax {
210    pub span: Span,
211    pub raw: String,
212    pub numerator: Option<SpannedNumber>,
213    pub slash_count: u8,
214    pub denominator: Option<SpannedNumber>,
215    pub multiplier: Fraction,
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub struct SpannedNumber {
220    pub value: u32,
221    pub span: Span,
222}
223
224#[derive(Debug, Clone, PartialEq, Eq)]
225pub struct RestSyntax {
226    pub span: Span,
227    pub attachments: AttachmentBundle,
228    pub visibility: RestVisibility,
229    pub marker_span: Span,
230    pub length: Option<LengthSyntax>,
231}
232
233#[derive(Debug, Clone, PartialEq, Eq)]
234pub struct MultiMeasureRestSyntax {
235    pub span: Span,
236    pub visibility: RestVisibility,
237    pub marker_span: Span,
238    pub count: Option<SpannedNumber>,
239}
240
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub struct SpacerSyntax {
243    pub span: Span,
244}
245
246#[derive(Debug, Clone, PartialEq, Eq)]
247pub struct ChordSyntax {
248    pub span: Span,
249    pub attachments: AttachmentBundle,
250    pub open_span: Span,
251    pub close_span: Option<Span>,
252    pub members: Vec<ChordMemberSyntax>,
253    pub length: Option<LengthSyntax>,
254}
255
256#[derive(Debug, Clone, PartialEq, Eq)]
257pub struct ChordMemberSyntax {
258    pub span: Span,
259    pub note: NoteSyntax,
260    /// A tie marker (`-`) attached directly to this chord member, e.g. the `A`
261    /// in `[DA-]`. ABC 2.1 §4.11 allows ties into, out of, and between chords.
262    pub tie: Option<TieSyntax>,
263    /// Slur-open markers written immediately before this member inside the chord
264    /// (`[(C(E]` → `(` on C and on E). ABC 2.1 §4.11: slurs may be used into, out
265    /// of and between chords, so each binds to its own member, not the chord head.
266    pub slur_starts: Vec<SlurSyntax>,
267    /// Slur-close markers written immediately after this member inside the chord
268    /// (`[C)E)]` → `)` on C and on E).
269    pub slur_ends: Vec<SlurSyntax>,
270}
271
272#[derive(Debug, Clone, PartialEq, Eq)]
273pub struct GraceGroupSyntax {
274    pub span: Span,
275    pub slash_span: Option<Span>,
276    pub elements: Vec<GraceElementSyntax>,
277}
278
279#[derive(Debug, Clone, PartialEq, Eq)]
280pub enum GraceElementSyntax {
281    Note(NoteSyntax),
282    Chord(ChordSyntax),
283    Rest(RestSyntax),
284    Slur(SlurSyntax),
285    Malformed(MalformedSyntax),
286}
287
288#[derive(Debug, Clone, PartialEq, Eq)]
289pub struct QuotedTextSyntax {
290    pub span: Span,
291    pub content_span: Span,
292    pub text: String,
293    pub kind: QuotedTextKind,
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297pub enum QuotedTextKind {
298    ChordSymbol,
299    Annotation(AnnotationPlacement),
300}
301
302#[derive(Debug, Clone, Copy, PartialEq, Eq)]
303pub enum AnnotationPlacement {
304    Above,
305    Below,
306    Left,
307    Right,
308    Free,
309}
310
311#[derive(Debug, Clone, PartialEq, Eq)]
312pub struct DecorationSyntax {
313    pub span: Span,
314    pub name_span: Span,
315    pub name: String,
316    pub kind: DecorationKind,
317}
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq)]
320pub enum DecorationKind {
321    Named,
322    LegacyNamed,
323    Shorthand,
324    UserDefined,
325}
326
327#[derive(Debug, Clone, PartialEq, Eq)]
328pub struct TupletSyntax {
329    pub span: Span,
330    pub p: SpannedNumber,
331    pub q: Option<SpannedNumber>,
332    pub r: Option<SpannedNumber>,
333}
334
335impl TupletSyntax {
336    pub(crate) fn r_value(&self) -> u32 {
337        self.r.map(|r| r.value).unwrap_or(self.p.value)
338    }
339}
340
341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
342pub struct SlurSyntax {
343    pub span: Span,
344    pub dotted: bool,
345    pub direction: SlurDirection,
346}
347
348#[derive(Debug, Clone, Copy, PartialEq, Eq)]
349pub enum SlurDirection {
350    Start,
351    End,
352}
353
354#[derive(Debug, Clone, Copy, PartialEq, Eq)]
355pub struct TieSyntax {
356    pub span: Span,
357    pub dotted: bool,
358}
359
360#[derive(Debug, Clone, Copy, PartialEq, Eq)]
361pub struct BrokenRhythmSyntax {
362    pub span: Span,
363    pub direction: BrokenRhythmDirection,
364    pub count: u8,
365}
366
367#[derive(Debug, Clone, Copy, PartialEq, Eq)]
368pub struct OverlaySyntax {
369    pub span: Span,
370}
371
372#[derive(Debug, Clone, Copy, PartialEq, Eq)]
373pub enum BrokenRhythmDirection {
374    LeftShorter,
375    RightShorter,
376}
377
378#[derive(Debug, Clone, PartialEq, Eq)]
379pub struct VariantEndingSyntax {
380    pub span: Span,
381    pub shorthand: bool,
382    pub endings: Vec<VariantEndingPart>,
383}
384
385#[derive(Debug, Clone, PartialEq, Eq)]
386pub enum VariantEndingPart {
387    Single(SpannedNumber),
388    Range {
389        start: SpannedNumber,
390        end: SpannedNumber,
391        span: Span,
392    },
393    Text {
394        text: String,
395        span: Span,
396    },
397}
398
399#[derive(Debug, Clone, PartialEq, Eq)]
400pub struct BarlineSyntax {
401    pub span: Span,
402    pub kind: BarlineKind,
403    pub dotted: bool,
404    pub raw: String,
405}