Skip to main content

croma_core/parse/field/
mod.rs

1pub(crate) mod key;
2pub(crate) mod meter;
3pub(crate) mod misc;
4pub(crate) mod tempo;
5pub(crate) mod voice;
6
7use crate::diagnostic::{Diagnostic, Span};
8use crate::model::Fraction;
9use crate::options::{AbcSpecVersion, DiagnosticOptions, ParseMode, ParseOptions};
10use crate::source::SourceText;
11use crate::syntax::tune::{ContinuationKind, LineContext, LineKind, SurfaceMap};
12
13pub(crate) use key::parse_key;
14use meter::ensure_default_unit_note_length;
15pub(crate) use meter::{parse_meter, parse_unit_note_length};
16use misc::{
17    invalid_field_warning, parse_version_line, parse_version_value, split_first_word,
18    trimmed_uncommented_span, unknown_field_warning, unknown_instruction_warning,
19};
20use tempo::{parse_decoration_delimiter, parse_line_break_mode, parse_macro, parse_user_symbol};
21use voice::parse_voice;
22pub(crate) use voice::{parse_score_directive, parse_voice_for_music};
23
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct ParsedAbcFields {
26    pub fields: Vec<ParsedField>,
27    pub file_header: FieldState,
28    pub tunes: Vec<ParsedTuneFields>,
29    pub state_transitions: Vec<StateTransition>,
30}
31
32impl ParsedAbcFields {
33    pub fn tune(&self, tune_index: usize) -> Option<&ParsedTuneFields> {
34        self.tunes.iter().find(|tune| tune.index == tune_index)
35    }
36
37    pub fn field(&self, index: usize) -> Option<&ParsedField> {
38        self.fields.get(index)
39    }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct ParsedTuneFields {
44    pub index: usize,
45    pub span: Span,
46    pub inherited_file_header: FieldState,
47    pub header: FieldState,
48    pub current: FieldState,
49    pub field_indices: Vec<usize>,
50    pub header_field_indices: Vec<usize>,
51    pub body_field_indices: Vec<usize>,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct ParsedField {
56    pub code: char,
57    pub line_index: usize,
58    pub context: LineContext,
59    pub line_span: Span,
60    pub marker_span: Span,
61    pub value_span: Span,
62    pub parsed_value_span: Span,
63    pub continuation_spans: Vec<Span>,
64    pub kind: ParsedFieldKind,
65}
66
67#[allow(clippy::large_enum_variant)]
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub enum ParsedFieldKind {
70    Version(VersionDirective),
71    Reference(Spanned<String>),
72    Title(Spanned<String>),
73    Meter(Spanned<Meter>),
74    UnitNoteLength(Spanned<UnitNoteLength>),
75    Key(Spanned<KeySignature>),
76    Tempo(Spanned<String>),
77    Part(Spanned<String>),
78    Voice(Spanned<VoiceDefinition>),
79    TextMetadata(TextMetadataField),
80    LyricLine(Spanned<String>),
81    SymbolLine(Spanned<String>),
82    Interpretation(InterpretationField),
83    UserSymbol(UserSymbol),
84    Macro(MacroDefinition),
85    Continuation(Spanned<String>),
86    Unknown(UnknownField),
87}
88
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct UnknownField {
91    pub code: char,
92    pub value: Spanned<String>,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct VersionDirective {
97    pub version: Option<Spanned<AbcVersion>>,
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub enum InterpretationField {
102    AbcVersion {
103        version: Spanned<AbcVersion>,
104    },
105    AbcCharset {
106        charset: Spanned<String>,
107    },
108    LineBreak {
109        mode: Spanned<LineBreakMode>,
110    },
111    Decoration {
112        delimiter: Spanned<DecorationDelimiter>,
113    },
114    Score {
115        directive: ScoreDirective,
116    },
117    CromaTimeSymbol {
118        value: Spanned<String>,
119    },
120    Unknown {
121        directive: Spanned<String>,
122        value: Spanned<String>,
123    },
124}
125
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct TextMetadataField {
128    pub code: char,
129    pub value: Spanned<String>,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct UserSymbol {
134    pub symbol: Option<Spanned<char>>,
135    pub replacement: Spanned<String>,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct UserSymbolDefinition {
140    pub symbol: Spanned<char>,
141    pub replacement: Spanned<String>,
142    pub span: Span,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct MacroDefinition {
147    pub name: Spanned<String>,
148    pub replacement: Spanned<String>,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub struct VoiceDefinition {
153    pub id: Spanned<String>,
154    pub properties: Spanned<String>,
155    pub parsed_properties: VoiceProperties,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, Default)]
159pub struct VoiceProperties {
160    pub name: Option<Spanned<String>>,
161    pub nm: Option<Spanned<String>>,
162    pub subname: Option<Spanned<String>>,
163    pub snm: Option<Spanned<String>>,
164    pub clef: Option<Spanned<String>>,
165    pub stem: Option<Spanned<StemDirection>>,
166    pub octave: Option<Spanned<String>>,
167    pub transpose: Option<Spanned<String>>,
168    pub middle: Option<Spanned<String>>,
169    pub other: Vec<VoiceProperty>,
170}
171
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub struct VoiceProperty {
174    pub key: Spanned<String>,
175    pub value: Spanned<String>,
176    pub span: Span,
177}
178
179#[derive(Debug, Clone, Copy, PartialEq, Eq)]
180pub enum StemDirection {
181    Up,
182    Down,
183}
184
185#[derive(Debug, Clone, PartialEq, Eq)]
186pub struct ScoreDirective {
187    pub value: Spanned<String>,
188    pub tokens: Vec<ScoreDirectiveToken>,
189}
190
191#[derive(Debug, Clone, PartialEq, Eq)]
192pub struct ScoreDirectiveToken {
193    pub span: Span,
194    pub kind: ScoreDirectiveTokenKind,
195}
196
197#[derive(Debug, Clone, PartialEq, Eq)]
198pub enum ScoreDirectiveTokenKind {
199    Voice(String),
200    GroupStart(char),
201    GroupEnd(char),
202    StaffSeparator,
203    MeasureSeparator,
204    FloatingVoiceMarker,
205}
206
207#[derive(Debug, Clone, PartialEq, Eq)]
208pub struct Spanned<T> {
209    pub value: T,
210    pub span: Span,
211}
212
213impl<T> Spanned<T> {
214    pub fn new(value: T, span: Span) -> Self {
215        Self { value, span }
216    }
217}
218
219#[derive(Debug, Clone, PartialEq, Eq)]
220pub struct FieldState {
221    pub dialect: DialectState,
222    pub meter: Option<Spanned<Meter>>,
223    pub unit_note_length: Option<Spanned<UnitNoteLength>>,
224    pub key: Option<Spanned<KeySignature>>,
225    pub voice: Option<Spanned<VoiceDefinition>>,
226    pub voices: Vec<Spanned<VoiceDefinition>>,
227}
228
229impl FieldState {
230    pub fn from_options(options: ParseOptions) -> Self {
231        Self {
232            dialect: DialectState::from_options(options),
233            meter: None,
234            unit_note_length: None,
235            key: None,
236            voice: None,
237            voices: Vec::new(),
238        }
239    }
240
241    pub(crate) fn unit_note_length_fraction(&self) -> Fraction {
242        self.unit_note_length
243            .as_ref()
244            .map(|unit| unit.value.fraction.to_model_fraction())
245            .unwrap_or_else(|| NoteLengthFraction::new(1, 8).to_model_fraction())
246    }
247}
248
249#[derive(Debug, Clone, PartialEq, Eq)]
250pub struct DialectState {
251    pub spec: AbcSpecVersion,
252    pub mode: ParseMode,
253    pub diagnostics: DiagnosticOptions,
254    pub declared_version: Option<Spanned<AbcVersion>>,
255    pub charset: Option<Spanned<String>>,
256    pub line_break: LineBreakMode,
257    pub decoration_delimiter: DecorationDelimiter,
258    pub user_symbols: Vec<UserSymbolDefinition>,
259}
260
261impl DialectState {
262    pub fn from_options(options: ParseOptions) -> Self {
263        Self {
264            spec: options.spec,
265            mode: options.mode,
266            diagnostics: options.diagnostics,
267            declared_version: None,
268            charset: None,
269            line_break: LineBreakMode::default(),
270            decoration_delimiter: DecorationDelimiter::Bang,
271            user_symbols: Vec::new(),
272        }
273    }
274}
275
276#[derive(Debug, Clone, PartialEq, Eq)]
277pub struct AbcVersion {
278    pub raw: String,
279    pub major: Option<u16>,
280    pub minor: Option<u16>,
281}
282
283#[derive(Debug, Clone, Copy, PartialEq, Eq)]
284pub struct LineBreakMode {
285    pub end_of_line: bool,
286    pub dollar: bool,
287    pub bang: bool,
288}
289
290impl LineBreakMode {
291    pub const fn none() -> Self {
292        Self {
293            end_of_line: false,
294            dollar: false,
295            bang: false,
296        }
297    }
298
299    pub const fn uses_bang(self) -> bool {
300        self.bang
301    }
302}
303
304impl Default for LineBreakMode {
305    fn default() -> Self {
306        Self {
307            end_of_line: true,
308            dollar: true,
309            bang: false,
310        }
311    }
312}
313
314#[derive(Debug, Clone, Copy, PartialEq, Eq)]
315pub enum DecorationDelimiter {
316    Bang,
317    Plus,
318}
319
320#[derive(Debug, Clone, PartialEq, Eq)]
321pub struct StateTransition {
322    pub scope: FieldScope,
323    pub field_index: Option<usize>,
324    pub span: Span,
325    pub kind: StateTransitionKind,
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq)]
329pub enum FieldScope {
330    Preamble,
331    FileHeader,
332    BetweenBlocks,
333    FreeText,
334    TypesetText,
335    TuneHeader { tune_index: usize },
336    TuneBody { tune_index: usize },
337    TuneTerminator { tune_index: usize },
338}
339
340impl From<LineContext> for FieldScope {
341    fn from(context: LineContext) -> Self {
342        match context {
343            LineContext::Preamble => Self::Preamble,
344            LineContext::FileHeader => Self::FileHeader,
345            LineContext::BetweenBlocks => Self::BetweenBlocks,
346            LineContext::FreeText => Self::FreeText,
347            LineContext::TypesetText => Self::TypesetText,
348            LineContext::TuneHeader { tune_index } => Self::TuneHeader { tune_index },
349            LineContext::TuneBody { tune_index } => Self::TuneBody { tune_index },
350            LineContext::TuneTerminator { tune_index } => Self::TuneTerminator { tune_index },
351        }
352    }
353}
354
355#[allow(clippy::large_enum_variant)]
356#[derive(Debug, Clone, PartialEq, Eq)]
357pub enum StateTransitionKind {
358    SpecVersion {
359        from: AbcSpecVersion,
360        to: AbcSpecVersion,
361    },
362    InterpretationMode {
363        from: ParseMode,
364        to: ParseMode,
365    },
366    Charset {
367        from: Option<String>,
368        to: String,
369    },
370    LineBreak {
371        from: LineBreakMode,
372        to: LineBreakMode,
373    },
374    DecorationDelimiter {
375        from: DecorationDelimiter,
376        to: DecorationDelimiter,
377    },
378    Meter {
379        from: Option<Meter>,
380        to: Meter,
381    },
382    UnitNoteLength {
383        from: Option<UnitNoteLength>,
384        to: UnitNoteLength,
385    },
386    Key {
387        from: Option<KeySignature>,
388        to: KeySignature,
389    },
390    Voice {
391        from: Option<VoiceDefinition>,
392        to: VoiceDefinition,
393    },
394    UserSymbol {
395        symbol: Option<char>,
396    },
397    Macro {
398        name: String,
399    },
400}
401
402#[derive(Debug, Clone, PartialEq, Eq)]
403pub struct Meter {
404    pub raw: String,
405    pub kind: MeterKind,
406}
407
408#[derive(Debug, Clone, PartialEq, Eq)]
409pub enum MeterKind {
410    CommonTime,
411    CutTime,
412    None,
413    Fraction { numerator: u32, denominator: u32 },
414    Complex,
415}
416
417#[derive(Debug, Clone, Copy, PartialEq, Eq)]
418pub struct NoteLengthFraction {
419    pub numerator: u32,
420    pub denominator: u32,
421}
422
423impl NoteLengthFraction {
424    pub const fn new(numerator: u32, denominator: u32) -> Self {
425        Self {
426            numerator,
427            denominator,
428        }
429    }
430
431    pub(crate) fn to_model_fraction(self) -> Fraction {
432        Fraction::new(self.numerator, self.denominator)
433    }
434}
435
436#[derive(Debug, Clone, Copy, PartialEq, Eq)]
437pub struct UnitNoteLength {
438    pub fraction: NoteLengthFraction,
439    pub origin: UnitNoteLengthOrigin,
440}
441
442#[derive(Debug, Clone, Copy, PartialEq, Eq)]
443pub enum UnitNoteLengthOrigin {
444    Explicit,
445    DefaultFromMeter,
446    DefaultFreeMeter,
447}
448
449#[derive(Debug, Clone, PartialEq, Eq)]
450pub struct KeySignature {
451    pub raw: String,
452    pub tonic: Option<KeyTonic>,
453    pub mode: KeyMode,
454    pub accidentals: Vec<KeyAccidental>,
455    pub explicit: bool,
456    pub compact_accidentals_ignored: bool,
457    /// Set when a valid leading tonic was recovered from a token carrying trailing
458    /// non-alphabetic junk (`K:Bb,`); the junk was discarded (ABC 2.1 §3.1.14). The
459    /// lowering stage flags this with `abc.field.key.tonic_trailing_junk_ignored`.
460    pub tonic_trailing_junk_ignored: bool,
461    /// Clef/octave/middle/transpose modifiers carried on the `K:` field
462    /// (ABC 2.1 §4.6), e.g. `K:C treble+8` or `K: Dm octave=1`. Empty when the
463    /// key field carries only tonic/mode/accidentals.
464    pub properties: VoiceProperties,
465}
466
467#[derive(Debug, Clone, Copy, PartialEq, Eq)]
468pub struct KeyTonic {
469    pub step: char,
470    pub accidental: Option<KeyTonicAccidental>,
471}
472
473#[derive(Debug, Clone, Copy, PartialEq, Eq)]
474pub enum KeyTonicAccidental {
475    Sharp,
476    Flat,
477}
478
479#[derive(Debug, Clone, PartialEq, Eq)]
480pub enum KeyMode {
481    Major,
482    Ionian,
483    Minor,
484    Aeolian,
485    Mixolydian,
486    Dorian,
487    Phrygian,
488    Lydian,
489    Locrian,
490    Explicit,
491    None,
492    HighlandPipes,
493    HighlandPipesMarked,
494    Unknown(String),
495}
496
497#[derive(Debug, Clone, PartialEq, Eq)]
498pub struct KeyAccidental {
499    pub sign: AccidentalSign,
500    pub note: Spanned<char>,
501    pub span: Span,
502}
503
504#[derive(Debug, Clone, Copy, PartialEq, Eq)]
505pub enum AccidentalSign {
506    DoubleFlat,
507    Flat,
508    Natural,
509    Sharp,
510    DoubleSharp,
511}
512
513pub(crate) fn parse_fields(
514    source: &SourceText,
515    surface: &SurfaceMap,
516    options: ParseOptions,
517) -> (ParsedAbcFields, Vec<Diagnostic>) {
518    let mut parser = FieldParser::new(source, surface, options);
519    parser.parse();
520    parser.finish()
521}
522
523struct FieldParser<'source> {
524    source: &'source SourceText,
525    surface: &'source SurfaceMap,
526    options: ParseOptions,
527    fields: Vec<ParsedField>,
528    diagnostics: Vec<Diagnostic>,
529    state_transitions: Vec<StateTransition>,
530    file_header: FieldState,
531    tunes: Vec<Option<ParsedTuneFields>>,
532}
533
534impl<'source> FieldParser<'source> {
535    fn new(
536        source: &'source SourceText,
537        surface: &'source SurfaceMap,
538        options: ParseOptions,
539    ) -> Self {
540        let tunes = surface.line_map.tunes.iter().map(|_| None).collect();
541        Self {
542            source,
543            surface,
544            options,
545            fields: Vec::new(),
546            diagnostics: Vec::new(),
547            state_transitions: Vec::new(),
548            file_header: FieldState::from_options(options),
549            tunes,
550        }
551    }
552
553    fn parse(&mut self) {
554        for line in &self.surface.line_map.lines {
555            match line.kind {
556                LineKind::VersionLine => self.parse_version_line(line),
557                LineKind::InformationField => self.parse_information_field(line),
558                LineKind::FieldContinuation => self.parse_field_continuation(line),
559                LineKind::EmptyLine
560                | LineKind::Comment
561                | LineKind::StylesheetDirective
562                | LineKind::MusicCode
563                | LineKind::FreeText
564                | LineKind::TypesetTextDirective => {}
565            }
566        }
567
568        for index in 0..self.tunes.len() {
569            if let Some(tune) = self.tunes.get_mut(index).and_then(Option::as_mut) {
570                ensure_default_unit_note_length(&mut tune.header, tune.span, self.options);
571                ensure_default_unit_note_length(&mut tune.current, tune.span, self.options);
572            }
573        }
574    }
575
576    fn parse_version_line(&mut self, line: &crate::syntax::tune::ClassifiedLine) {
577        let Some(line_text) = self.source.slice(line.text_span) else {
578            return;
579        };
580        let Some((marker_span, version)) = parse_version_line(line_text, line.text_span.start)
581        else {
582            return;
583        };
584        let value_span = version
585            .as_ref()
586            .map(|version| version.span)
587            .unwrap_or_else(|| Span::new(marker_span.end, marker_span.end));
588        let field = ParsedField {
589            code: '%',
590            line_index: line.index,
591            context: line.context,
592            line_span: line.span,
593            marker_span,
594            value_span,
595            parsed_value_span: value_span,
596            continuation_spans: Vec::new(),
597            kind: ParsedFieldKind::Version(VersionDirective { version }),
598        };
599
600        let field_index = self.push_field(field);
601        let version = match &self.fields[field_index].kind {
602            ParsedFieldKind::Version(version) => version.version.clone(),
603            _ => None,
604        };
605        self.apply_version_directive(field_index, line.context, version.as_ref(), value_span);
606    }
607
608    fn parse_information_field(&mut self, line: &crate::syntax::tune::ClassifiedLine) {
609        let Some(field_header) = line.field else {
610            return;
611        };
612        let value = self.field_value(line, field_header.value_span);
613        let kind = self.parse_field_kind(field_header.code, value.clone());
614        let field = ParsedField {
615            code: field_header.code,
616            line_index: line.index,
617            context: line.context,
618            line_span: line.span,
619            marker_span: field_header.marker_span,
620            value_span: field_header.value_span,
621            parsed_value_span: value.span,
622            continuation_spans: self.continuation_spans(line.index),
623            kind,
624        };
625        let field_index = self.push_field(field);
626        self.apply_field_state(field_index);
627    }
628
629    fn parse_field_continuation(&mut self, line: &crate::syntax::tune::ClassifiedLine) {
630        let Some(marker_span) = line.marker_span else {
631            return;
632        };
633        let value_span = Span::new(marker_span.end, line.text_span.end);
634        let value = self.field_value(line, value_span);
635        let field = ParsedField {
636            code: '+',
637            line_index: line.index,
638            context: line.context,
639            line_span: line.span,
640            marker_span,
641            value_span,
642            parsed_value_span: value.span,
643            continuation_spans: Vec::new(),
644            kind: ParsedFieldKind::Continuation(value),
645        };
646        self.push_field(field);
647    }
648
649    fn field_value(
650        &self,
651        line: &crate::syntax::tune::ClassifiedLine,
652        value_span: Span,
653    ) -> Spanned<String> {
654        let mut spans = vec![trimmed_uncommented_span(self.source, line, value_span)];
655        spans.extend(self.continuation_value_spans(line.index));
656
657        let mut value = String::new();
658        let mut parsed_span = spans.first().copied().unwrap_or(value_span);
659        for span in spans.into_iter().filter(|span| !span.is_empty()) {
660            if !value.is_empty() {
661                value.push('\n');
662            }
663            if let Some(text) = self.source.slice(span) {
664                value.push_str(text);
665            }
666            parsed_span = if parsed_span.is_empty() {
667                span
668            } else {
669                Span::new(
670                    parsed_span.start.min(span.start),
671                    parsed_span.end.max(span.end),
672                )
673            };
674        }
675
676        Spanned::new(value, parsed_span)
677    }
678
679    fn continuation_spans(&self, line_index: usize) -> Vec<Span> {
680        self.surface
681            .line_map
682            .continuation_edges
683            .iter()
684            .filter(|edge| {
685                edge.kind == ContinuationKind::FieldContinuation && edge.from_line == line_index
686            })
687            .filter_map(|edge| {
688                self.surface
689                    .line_map
690                    .lines
691                    .get(edge.to_line)
692                    .map(|line| line.content_span)
693            })
694            .collect()
695    }
696
697    fn continuation_value_spans(&self, line_index: usize) -> Vec<Span> {
698        self.surface
699            .line_map
700            .continuation_edges
701            .iter()
702            .filter(|edge| {
703                edge.kind == ContinuationKind::FieldContinuation && edge.from_line == line_index
704            })
705            .filter_map(|edge| {
706                let line = self.surface.line_map.lines.get(edge.to_line)?;
707                let marker_span = line.marker_span?;
708                Some(trimmed_uncommented_span(
709                    self.source,
710                    line,
711                    Span::new(marker_span.end, line.text_span.end),
712                ))
713            })
714            .collect()
715    }
716
717    fn parse_field_kind(&mut self, code: char, value: Spanned<String>) -> ParsedFieldKind {
718        match code {
719            'X' => ParsedFieldKind::Reference(value),
720            'T' => ParsedFieldKind::Title(value),
721            'M' => ParsedFieldKind::Meter(Spanned::new(parse_meter(&value.value), value.span)),
722            'L' => match parse_unit_note_length(&value.value) {
723                Some(unit) => ParsedFieldKind::UnitNoteLength(Spanned::new(unit, value.span)),
724                None => {
725                    self.diagnostics.push(invalid_field_warning(
726                        "abc.field.invalid_l",
727                        "L",
728                        value.span,
729                    ));
730                    ParsedFieldKind::Unknown(UnknownField {
731                        code,
732                        value: value.clone(),
733                    })
734                }
735            },
736            'K' => ParsedFieldKind::Key(Spanned::new(
737                parse_key(&value.value, value.span),
738                value.span,
739            )),
740            'Q' => ParsedFieldKind::Tempo(value),
741            'P' => ParsedFieldKind::Part(value),
742            'V' => {
743                let span = value.span;
744                ParsedFieldKind::Voice(Spanned::new(parse_voice(value), span))
745            }
746            'C' | 'O' | 'R' | 'N' | 'Z' | 'W' => {
747                ParsedFieldKind::TextMetadata(TextMetadataField { code, value })
748            }
749            'w' => ParsedFieldKind::LyricLine(value),
750            's' => ParsedFieldKind::SymbolLine(value),
751            'I' => self.parse_interpretation(value),
752            'U' => ParsedFieldKind::UserSymbol(parse_user_symbol(value)),
753            'm' => ParsedFieldKind::Macro(parse_macro(value)),
754            _ => {
755                self.diagnostics
756                    .push(unknown_field_warning(code, value.span));
757                ParsedFieldKind::Unknown(UnknownField { code, value })
758            }
759        }
760    }
761
762    fn parse_interpretation(&mut self, value: Spanned<String>) -> ParsedFieldKind {
763        let (directive, rest) = split_first_word(value);
764        let directive_lower = directive.value.to_ascii_lowercase();
765        match directive_lower.as_str() {
766            "abc-version" => {
767                let version = parse_version_value(rest.value.trim());
768                if version.major.is_none() {
769                    self.diagnostics.push(invalid_field_warning(
770                        "abc.field.invalid_abc_version",
771                        "I:abc-version",
772                        rest.span,
773                    ));
774                }
775                ParsedFieldKind::Interpretation(InterpretationField::AbcVersion {
776                    version: Spanned::new(version, rest.span),
777                })
778            }
779            "abc-charset" => {
780                ParsedFieldKind::Interpretation(InterpretationField::AbcCharset { charset: rest })
781            }
782            "linebreak" => match parse_line_break_mode(&rest.value) {
783                Some(mode) => ParsedFieldKind::Interpretation(InterpretationField::LineBreak {
784                    mode: Spanned::new(mode, rest.span),
785                }),
786                None => {
787                    self.diagnostics.push(invalid_field_warning(
788                        "abc.field.invalid_linebreak",
789                        "I:linebreak",
790                        rest.span,
791                    ));
792                    ParsedFieldKind::Interpretation(InterpretationField::Unknown {
793                        directive,
794                        value: rest,
795                    })
796                }
797            },
798            "decoration" => match parse_decoration_delimiter(&rest.value) {
799                Some(delimiter) => {
800                    ParsedFieldKind::Interpretation(InterpretationField::Decoration {
801                        delimiter: Spanned::new(delimiter, rest.span),
802                    })
803                }
804                None => {
805                    self.diagnostics.push(invalid_field_warning(
806                        "abc.field.invalid_decoration",
807                        "I:decoration",
808                        rest.span,
809                    ));
810                    ParsedFieldKind::Interpretation(InterpretationField::Unknown {
811                        directive,
812                        value: rest,
813                    })
814                }
815            },
816            // `%%score` and `%%staves` share the same voice-grouping syntax.
817            "score" | "staves" => ParsedFieldKind::Interpretation(InterpretationField::Score {
818                directive: parse_score_directive(rest),
819            }),
820            "croma-time-symbol" => {
821                ParsedFieldKind::Interpretation(InterpretationField::CromaTimeSymbol {
822                    value: rest,
823                })
824            }
825            _ => {
826                if self
827                    .options
828                    .diagnostics
829                    .should_emit_croma_carrier_warning(&directive.value)
830                {
831                    self.diagnostics.push(unknown_instruction_warning(
832                        &directive.value,
833                        directive.span,
834                    ));
835                }
836                ParsedFieldKind::Interpretation(InterpretationField::Unknown {
837                    directive,
838                    value: rest,
839                })
840            }
841        }
842    }
843
844    fn push_field(&mut self, field: ParsedField) -> usize {
845        let field_index = self.fields.len();
846        let context = field.context;
847        self.fields.push(field);
848
849        match context {
850            LineContext::TuneHeader { tune_index } | LineContext::TuneBody { tune_index } => {
851                self.ensure_tune(tune_index);
852                if let Some(tune) = self.tunes.get_mut(tune_index).and_then(Option::as_mut) {
853                    tune.field_indices.push(field_index);
854                    match context {
855                        LineContext::TuneHeader { .. } => {
856                            tune.header_field_indices.push(field_index);
857                        }
858                        LineContext::TuneBody { .. } => {
859                            tune.body_field_indices.push(field_index);
860                        }
861                        _ => {}
862                    }
863                }
864            }
865            LineContext::Preamble
866            | LineContext::FileHeader
867            | LineContext::BetweenBlocks
868            | LineContext::FreeText
869            | LineContext::TypesetText
870            | LineContext::TuneTerminator { .. } => {}
871        }
872
873        field_index
874    }
875
876    fn ensure_tune(&mut self, tune_index: usize) {
877        if self
878            .tunes
879            .get(tune_index)
880            .and_then(Option::as_ref)
881            .is_some()
882        {
883            return;
884        }
885
886        let Some(tune_block) = self
887            .surface
888            .line_map
889            .tunes
890            .iter()
891            .find(|tune| tune.index == tune_index)
892        else {
893            return;
894        };
895        let inherited = self.file_header.clone();
896        if let Some(slot) = self.tunes.get_mut(tune_index) {
897            *slot = Some(ParsedTuneFields {
898                index: tune_index,
899                span: tune_block.span,
900                inherited_file_header: inherited.clone(),
901                header: inherited.clone(),
902                current: inherited,
903                field_indices: Vec::new(),
904                header_field_indices: Vec::new(),
905                body_field_indices: Vec::new(),
906            });
907        }
908    }
909
910    fn finish(self) -> (ParsedAbcFields, Vec<Diagnostic>) {
911        (
912            ParsedAbcFields {
913                fields: self.fields,
914                file_header: self.file_header,
915                tunes: self.tunes.into_iter().flatten().collect(),
916                state_transitions: self.state_transitions,
917            },
918            self.diagnostics,
919        )
920    }
921}
922
923#[cfg(test)]
924#[path = "mod_tests.rs"]
925mod tests;