1use crate::diagnostic::Span;
4use crate::lower::invalid_tuplet_warning;
5use crate::model::{Accidental, Fraction, RestVisibility};
6use crate::parse::music::{
7 MusicLineParser, invalid_length_warning, is_note_letter, redundant_chord_tie_warning,
8};
9use crate::syntax::{
10 AccidentalSyntax, BrokenRhythmDirection, BrokenRhythmSyntax, ChordMemberSyntax, ChordSyntax,
11 GraceElementSyntax, GraceGroupSyntax, LengthSyntax, MalformedSyntax, MalformedSyntaxKind,
12 MultiMeasureRestSyntax, MusicItem, MusicTokenKind, NoteSyntax, OctaveMark, OctaveMarkSyntax,
13 PitchSyntax, RestSyntax, SlurDirection, SlurSyntax, SpannedNumber, TieSyntax, TupletSyntax,
14};
15
16impl<'line> MusicLineParser<'line> {
17 pub(super) fn parse_note(&mut self, accidental: Option<AccidentalSyntax>) {
18 let attachments = self.take_pending_attachments();
19 let core_start = accidental
20 .as_ref()
21 .map(|accidental| accidental.span.start - self.line_offset)
22 .unwrap_or(self.index);
23 let start = attachments
24 .span_start()
25 .map(|start| start - self.line_offset)
26 .unwrap_or(core_start)
27 .min(core_start);
28 let pitch_start = self.index;
29 let Some(step) = self.bump_char() else {
30 return;
31 };
32 let pitch_span = self.span(pitch_start, self.index);
33 self.push_token(MusicTokenKind::Pitch, pitch_span);
34
35 let octave_marks = self.parse_octave_marks();
36 let length = self.parse_length_suffix();
37 let end = length
38 .as_ref()
39 .map(|length| length.span.end - self.line_offset)
40 .or_else(|| {
41 octave_marks
42 .last()
43 .map(|mark| mark.span.end - self.line_offset)
44 })
45 .unwrap_or(self.index);
46 let note = NoteSyntax {
47 span: self.span(start, end),
48 attachments,
49 accidental,
50 pitch: PitchSyntax {
51 step,
52 span: pitch_span,
53 },
54 octave_marks,
55 length,
56 };
57 self.items.push(MusicItem::Note(note));
58 }
59
60 pub(super) fn parse_accidental_token(&mut self) -> Option<AccidentalSyntax> {
61 let start = self.index;
62 let sign = if self.starts_with("__") {
63 self.index += 2;
64 Accidental::DoubleFlat
65 } else if self.starts_with("^^") {
66 self.index += 2;
67 Accidental::DoubleSharp
68 } else {
69 match self.bump_char()? {
70 '_' => Accidental::Flat,
71 '^' => Accidental::Sharp,
72 '=' => Accidental::Natural,
73 _ => return None,
74 }
75 };
76 let span = self.span(start, self.index);
77 self.push_token(MusicTokenKind::Accidental, span);
78 Some(AccidentalSyntax { sign, span })
79 }
80
81 pub(super) fn parse_octave_marks(&mut self) -> Vec<OctaveMarkSyntax> {
82 let mut marks = Vec::new();
83 while let Some(ch @ ('\'' | ',')) = self.peek_char() {
84 let start = self.index;
85 self.bump_char();
86 let span = self.span(start, self.index);
87 self.push_token(MusicTokenKind::OctaveMark, span);
88 marks.push(OctaveMarkSyntax {
89 mark: if ch == '\'' {
90 OctaveMark::Raise
91 } else {
92 OctaveMark::Lower
93 },
94 span,
95 });
96 }
97 marks
98 }
99
100 pub(super) fn parse_rest(&mut self, visibility: RestVisibility) {
101 let attachments = self.take_pending_attachments();
102 let start = self.index;
103 self.bump_char();
104 let marker_span = self.span(start, self.index);
105 self.push_token(MusicTokenKind::Rest, marker_span);
106 let length = self.parse_length_suffix();
107 let span = length
108 .as_ref()
109 .map(|length| {
110 Span::new(
111 attachments.span_start().unwrap_or(marker_span.start),
112 length.span.end,
113 )
114 })
115 .unwrap_or_else(|| {
116 Span::new(
117 attachments.span_start().unwrap_or(marker_span.start),
118 marker_span.end,
119 )
120 });
121 self.items.push(MusicItem::Rest(RestSyntax {
122 span,
123 attachments,
124 visibility,
125 marker_span,
126 length,
127 }));
128 }
129
130 pub(super) fn parse_multi_measure_rest(&mut self, visibility: RestVisibility) {
131 self.flush_pending_attachments();
132 let start = self.index;
133 self.bump_char();
134 let marker_span = self.span(start, self.index);
135 self.push_token(MusicTokenKind::MultiMeasureRest, marker_span);
136 let count = self.parse_number_token();
137 if let Some(count) = count {
138 self.push_token(MusicTokenKind::Length, count.span);
139 }
140 let span = count
141 .map(|count| Span::new(marker_span.start, count.span.end))
142 .unwrap_or(marker_span);
143 self.items
144 .push(MusicItem::MultiMeasureRest(MultiMeasureRestSyntax {
145 span,
146 visibility,
147 marker_span,
148 count,
149 }));
150 }
151
152 pub(super) fn parse_chord(&mut self, recover_barline_members_as_notes: bool) {
153 let attachments = self.take_pending_attachments();
154 let start = attachments
155 .span_start()
156 .map(|start| start - self.line_offset)
157 .unwrap_or(self.index);
158 let open_start = self.index;
159 self.bump_char();
160 let open_span = self.span(open_start, self.index);
161 let mut members = Vec::new();
162 let mut closed = false;
163 let mut stopped_at_barline = false;
164 let mut pending_member_starts: Vec<SlurSyntax> = Vec::new();
173 let mut deferred_slur_ends = Vec::new();
174
175 while let Some(ch) = self.peek_char() {
176 match ch {
177 ']' => {
178 self.bump_char();
179 closed = true;
180 break;
181 }
182 ch if ch.is_whitespace() => self.parse_whitespace(),
183 '|' => {
184 stopped_at_barline = true;
193 break;
194 }
195 '^' | '_' | '=' => {
196 let Some(accidental) = self.parse_accidental_token() else {
197 continue;
198 };
199 if self.peek_char().is_some_and(is_note_letter) {
200 if let Some(note) = self.parse_note_syntax(Some(accidental)) {
201 let tie = self.parse_chord_member_tie();
202 members.push(ChordMemberSyntax {
203 span: note.span,
204 note,
205 tie,
206 slur_starts: std::mem::take(&mut pending_member_starts),
207 slur_ends: Vec::new(),
208 });
209 }
210 } else {
211 self.push_malformed(
212 accidental.span,
213 MalformedSyntaxKind::DanglingAccidental,
214 "abc.music.malformed_accidental",
215 "Accidentals must appear immediately before a chord member note",
216 );
217 }
218 }
219 'A'..='G' | 'a'..='g' => {
220 if let Some(note) = self.parse_note_syntax(None) {
221 let tie = self.parse_chord_member_tie();
222 members.push(ChordMemberSyntax {
223 span: note.span,
224 note,
225 tie,
226 slur_starts: std::mem::take(&mut pending_member_starts),
227 slur_ends: Vec::new(),
228 });
229 }
230 }
231 '-' => {
232 if let Some(tie) = self.parse_chord_member_tie() {
238 self.diagnostics.push(redundant_chord_tie_warning(tie.span));
239 if let Some(last) = members.last_mut()
240 && last.tie.is_none()
241 {
242 last.tie = Some(tie);
243 }
244 }
245 }
246 '"' => self.parse_quoted_text(),
247 '{' => self.parse_grace_group(),
248 '!' | '+' => self.parse_decoration(ch),
249 '.' => self.parse_dot(),
250 '~' | 'H' | 'L' | 'M' | 'O' | 'P' | 'S' | 'T' | 'u' | 'v' => {
251 self.parse_shorthand_decoration()
252 }
253 ch if self.is_user_symbol(ch) => self.parse_shorthand_decoration(),
254 '(' if !self
255 .peek_next_char()
256 .is_some_and(|next| next.is_ascii_digit()) =>
257 {
258 let slur_start = self.index;
259 self.bump_char();
260 let span = self.span(slur_start, self.index);
261 self.push_token(MusicTokenKind::Slur, span);
262 pending_member_starts.push(SlurSyntax {
263 span,
264 dotted: false,
265 direction: SlurDirection::Start,
266 });
267 }
268 ')' => {
269 let slur_start = self.index;
270 self.bump_char();
271 let span = self.span(slur_start, self.index);
272 self.push_token(MusicTokenKind::Slur, span);
273 let end = SlurSyntax {
274 span,
275 dotted: false,
276 direction: SlurDirection::End,
277 };
278 if let Some(last) = members.last_mut() {
281 last.slur_ends.push(end);
282 } else {
283 deferred_slur_ends.push(end);
284 }
285 }
286 _ => {
287 self.parse_malformed_single(
288 MalformedSyntaxKind::UnknownToken,
289 "abc.music.unknown_chord_token",
290 "Unknown chord-member token was preserved and skipped",
291 );
292 }
293 }
294 }
295
296 let close_span = closed.then(|| self.span(self.index - 1, self.index));
297 if !closed {
298 let span = self.span(start, self.index);
299 self.push_token(MusicTokenKind::Malformed, span);
300 self.push_malformed(
301 span,
302 MalformedSyntaxKind::UnclosedChord,
303 "abc.music.unclosed_chord",
304 "Chord group was preserved and skipped",
305 );
306 if recover_barline_members_as_notes && stopped_at_barline {
307 for member in members {
308 for slur in member.slur_starts {
309 self.items.push(MusicItem::Slur(slur));
310 }
311 self.items.push(MusicItem::Note(member.note));
312 if let Some(tie) = member.tie {
313 self.items.push(MusicItem::Tie(tie));
314 }
315 for slur in member.slur_ends {
316 self.items.push(MusicItem::Slur(slur));
317 }
318 }
319 for slur in deferred_slur_ends {
320 self.items.push(MusicItem::Slur(slur));
321 }
322 }
323 return;
324 }
325
326 let length = self.parse_length_suffix();
327 let end = length
328 .as_ref()
329 .map(|length| length.span.end - self.line_offset)
330 .unwrap_or(self.index);
331 let span = self.span(start, end);
332 self.push_token(MusicTokenKind::Chord, span);
333 self.items.push(MusicItem::Chord(ChordSyntax {
334 span,
335 attachments,
336 open_span,
337 close_span,
338 members,
339 length,
340 }));
341 for slur in deferred_slur_ends {
342 self.items.push(MusicItem::Slur(slur));
343 }
344 for slur in pending_member_starts {
347 self.items.push(MusicItem::Slur(slur));
348 }
349 }
350
351 fn parse_chord_member_tie(&mut self) -> Option<TieSyntax> {
355 if self.peek_char() != Some('-') {
356 return None;
357 }
358 let start = self.index;
359 self.bump_char();
360 let span = self.span(start, self.index);
361 self.push_token(MusicTokenKind::Tie, span);
362 Some(TieSyntax {
363 span,
364 dotted: false,
365 })
366 }
367
368 pub(super) fn parse_grace_group(&mut self) {
369 let stashed = std::mem::take(&mut self.pending_attachments);
374 let start = self.index;
375 self.bump_char();
376 let slash_span = if self.peek_char() == Some('/') {
377 let slash_start = self.index;
378 self.bump_char();
379 Some(self.span(slash_start, self.index))
380 } else {
381 None
382 };
383 let mut elements = Vec::new();
384 let mut closed = false;
385
386 while let Some(ch) = self.peek_char() {
387 match ch {
388 '}' => {
389 self.bump_char();
390 closed = true;
391 break;
392 }
393 ch if ch.is_whitespace() => self.parse_whitespace(),
394 '^' | '_' | '=' => {
395 let Some(accidental) = self.parse_accidental_token() else {
396 continue;
397 };
398 if self.peek_char().is_some_and(is_note_letter) {
399 if let Some(note) = self.parse_note_syntax(Some(accidental)) {
400 elements.push(GraceElementSyntax::Note(note));
401 }
402 } else {
403 let malformed = MalformedSyntax {
404 span: accidental.span,
405 kind: MalformedSyntaxKind::DanglingAccidental,
406 };
407 elements.push(GraceElementSyntax::Malformed(malformed.clone()));
408 self.push_malformed(
409 malformed.span,
410 malformed.kind,
411 "abc.music.malformed_accidental",
412 "Accidentals must appear immediately before a grace note",
413 );
414 }
415 }
416 'A'..='G' | 'a'..='g' => {
417 if let Some(note) = self.parse_note_syntax(None) {
418 elements.push(GraceElementSyntax::Note(note));
419 }
420 }
421 'z' => {
422 let rest = self.parse_rest_syntax(RestVisibility::Visible);
423 elements.push(GraceElementSyntax::Rest(rest));
424 }
425 'x' => {
426 let rest = self.parse_rest_syntax(RestVisibility::Invisible);
427 elements.push(GraceElementSyntax::Rest(rest));
428 }
429 '(' => {
430 let slur = self.parse_grace_slur(SlurDirection::Start);
431 elements.push(GraceElementSyntax::Slur(slur));
432 }
433 ')' => {
434 let slur = self.parse_grace_slur(SlurDirection::End);
435 elements.push(GraceElementSyntax::Slur(slur));
436 }
437 '[' => {
438 self.parse_chord(false);
439 if let Some(MusicItem::Chord(chord)) = self.items.pop() {
440 elements.push(GraceElementSyntax::Chord(chord));
441 }
442 }
443 '"' => self.parse_quoted_text(),
444 '!' | '+' => self.parse_decoration(ch),
445 '.' => self.parse_dot(),
446 '~' | 'H' | 'L' | 'M' | 'O' | 'P' | 'S' | 'T' | 'u' | 'v' => {
447 self.parse_shorthand_decoration()
448 }
449 ch if self.is_user_symbol(ch) => self.parse_shorthand_decoration(),
450 _ => {
451 let start = self.index;
452 self.bump_char();
453 let malformed = MalformedSyntax {
454 span: self.span(start, self.index),
455 kind: MalformedSyntaxKind::UnknownToken,
456 };
457 elements.push(GraceElementSyntax::Malformed(malformed.clone()));
458 self.push_malformed(
459 malformed.span,
460 malformed.kind,
461 "abc.music.unknown_grace_token",
462 "Unknown grace-group token was preserved and skipped",
463 );
464 }
465 }
466 }
467
468 let inner_leftovers = std::mem::take(&mut self.pending_attachments);
472 self.pending_attachments = stashed;
473 self.pending_attachments.extend(inner_leftovers);
474
475 let span = self.span(start, self.index);
476 if closed {
477 self.push_token(MusicTokenKind::GraceGroup, span);
478 self.pending_attachments
479 .grace_groups
480 .push(GraceGroupSyntax {
481 span,
482 slash_span,
483 elements,
484 });
485 } else {
486 self.push_token(MusicTokenKind::Malformed, span);
487 self.push_malformed(
488 span,
489 MalformedSyntaxKind::UnclosedGraceGroup,
490 "abc.music.unclosed_grace",
491 "Grace group was preserved and skipped",
492 );
493 }
494 }
495
496 fn parse_grace_slur(&mut self, direction: SlurDirection) -> SlurSyntax {
497 let start = self.index;
498 self.bump_char();
499 let span = self.span(start, self.index);
500 self.push_token(MusicTokenKind::Slur, span);
501 SlurSyntax {
502 span,
503 dotted: false,
504 direction,
505 }
506 }
507
508 pub(super) fn parse_note_syntax(
509 &mut self,
510 accidental: Option<AccidentalSyntax>,
511 ) -> Option<NoteSyntax> {
512 let attachments = self.take_pending_attachments();
513 let core_start = accidental
514 .as_ref()
515 .map(|accidental| accidental.span.start - self.line_offset)
516 .unwrap_or(self.index);
517 let start = attachments
518 .span_start()
519 .map(|start| start - self.line_offset)
520 .unwrap_or(core_start)
521 .min(core_start);
522 let pitch_start = self.index;
523 let step = self.bump_char()?;
524 let pitch_span = self.span(pitch_start, self.index);
525 self.push_token(MusicTokenKind::Pitch, pitch_span);
526
527 let octave_marks = self.parse_octave_marks();
528 let length = self.parse_length_suffix();
529 let end = length
530 .as_ref()
531 .map(|length| length.span.end - self.line_offset)
532 .or_else(|| {
533 octave_marks
534 .last()
535 .map(|mark| mark.span.end - self.line_offset)
536 })
537 .unwrap_or(self.index);
538 Some(NoteSyntax {
539 span: self.span(start, end),
540 attachments,
541 accidental,
542 pitch: PitchSyntax {
543 step,
544 span: pitch_span,
545 },
546 octave_marks,
547 length,
548 })
549 }
550
551 pub(super) fn parse_rest_syntax(&mut self, visibility: RestVisibility) -> RestSyntax {
552 let attachments = self.take_pending_attachments();
553 let start = self.index;
554 self.bump_char();
555 let marker_span = self.span(start, self.index);
556 self.push_token(MusicTokenKind::Rest, marker_span);
557 let length = self.parse_length_suffix();
558 let span = length
559 .as_ref()
560 .map(|length| {
561 Span::new(
562 attachments.span_start().unwrap_or(marker_span.start),
563 length.span.end,
564 )
565 })
566 .unwrap_or_else(|| {
567 Span::new(
568 attachments.span_start().unwrap_or(marker_span.start),
569 marker_span.end,
570 )
571 });
572 RestSyntax {
573 span,
574 attachments,
575 visibility,
576 marker_span,
577 length,
578 }
579 }
580
581 pub(super) fn parse_length_suffix(&mut self) -> Option<LengthSyntax> {
582 let start = self.index;
583 let numerator = self.parse_number_token();
584 let mut slash_count = 0u8;
585 let mut denominator = None;
586
587 if self.peek_char() == Some('/') {
588 while self.peek_char() == Some('/') {
589 slash_count = slash_count.saturating_add(1);
590 self.bump_char();
591 }
592 denominator = self.parse_number_token();
593 }
594
595 if numerator.is_none() && slash_count == 0 {
596 return None;
597 }
598
599 let end = self.index;
600 let span = self.span(start, end);
601 self.push_token(MusicTokenKind::Length, span);
602 let numerator_value = numerator.map(|number| number.value).unwrap_or(1);
603 let denominator_value = match (slash_count, denominator) {
604 (0, _) => 1,
605 (_, Some(number)) if number.value == 0 => {
606 self.diagnostics.push(invalid_length_warning(
607 number.span,
608 "Length denominator cannot be zero; recovered as denominator 1",
609 ));
610 1
611 }
612 (_, Some(number)) => number.value,
613 (slashes, None) => slash_denominator(slashes).unwrap_or_else(|| {
614 self.diagnostics.push(invalid_length_warning(
615 span,
616 "Length slash shorthand is too long; recovered as denominator 1",
617 ));
618 1
619 }),
620 };
621 let numerator_value = if let Some(number) = numerator
622 && number.value == 0
623 {
624 self.diagnostics.push(invalid_length_warning(
625 number.span,
626 "Length numerator cannot be zero; recovered as numerator 1",
627 ));
628 1
629 } else {
630 numerator_value
631 };
632
633 Some(LengthSyntax {
634 span,
635 raw: self.text[start..end].to_owned(),
636 numerator,
637 slash_count,
638 denominator,
639 multiplier: Fraction::new(numerator_value, denominator_value),
640 })
641 }
642
643 pub(super) fn parse_number_token(&mut self) -> Option<SpannedNumber> {
644 let start = self.index;
645 while self.peek_char().is_some_and(|ch| ch.is_ascii_digit()) {
646 self.bump_char();
647 }
648 if start == self.index {
649 return None;
650 }
651 let span = self.span(start, self.index);
652 let raw = &self.text[start..self.index];
653 let value = raw.parse().unwrap_or_else(|_| {
654 self.diagnostics.push(invalid_length_warning(
655 span,
656 "Number is too large; recovered as 1",
657 ));
658 1
659 });
660 Some(SpannedNumber { value, span })
661 }
662
663 pub(super) fn parse_broken_rhythm(&mut self) {
664 let start = self.index;
665 let Some(marker) = self.bump_char() else {
666 return;
667 };
668 while self.peek_char() == Some(marker) {
669 self.bump_char();
670 }
671 let span = self.span(start, self.index);
672 self.push_token(MusicTokenKind::BrokenRhythm, span);
673 self.items.push(MusicItem::BrokenRhythm(BrokenRhythmSyntax {
674 span,
675 direction: if marker == '<' {
676 BrokenRhythmDirection::LeftShorter
677 } else {
678 BrokenRhythmDirection::RightShorter
679 },
680 count: u8::try_from(self.index - start).unwrap_or(u8::MAX),
681 }));
682 }
683
684 pub(super) fn parse_tuplet(&mut self) {
685 let start = self.index;
688 self.bump_char();
689 let Some(p) = self.parse_number_token() else {
690 let span = self.span(start, self.index);
691 self.push_token(MusicTokenKind::Malformed, span);
692 self.push_malformed(
693 span,
694 MalformedSyntaxKind::InvalidTuplet,
695 "abc.music.invalid_tuplet",
696 "Tuplet specifier must start with a number",
697 );
698 return;
699 };
700
701 let mut q = None;
702 let mut r = None;
703 if self.peek_char() == Some(':') {
704 self.bump_char();
705 q = self.parse_number_token();
706 if self.peek_char() == Some(':') {
707 self.bump_char();
708 r = self.parse_number_token();
709 }
710 }
711
712 let span = self.span(start, self.index);
713 self.push_token(MusicTokenKind::Tuplet, span);
714 if !(2..=9).contains(&p.value) {
715 self.diagnostics.push(invalid_tuplet_warning(span));
716 }
717 self.items
718 .push(MusicItem::Tuplet(TupletSyntax { span, p, q, r }));
719 }
720}
721
722fn slash_denominator(slash_count: u8) -> Option<u32> {
723 1u32.checked_shl(u32::from(slash_count))
724}