use crate::diagnostic::{Diagnostic, RecoveryNote, Severity, Span};
use crate::lower::{
LoweredEvent, LoweringState, PendingTie, abc_slur_reference, is_note_atom, lowered_timed_note,
note_signature,
};
use crate::model::{EventAttachments, LoweredEventAtom, TieAttachment, TieRole};
use crate::syntax::TieSyntax;
impl LoweringState {
pub(crate) fn apply_tie(&mut self, tie: TieSyntax) {
let Some(group) = self.time_groups.last().cloned() else {
self.diagnostics.push(unmatched_tie_warning(tie.span));
return;
};
if !self.broken_left_available {
self.diagnostics.push(unmatched_tie_warning(tie.span));
return;
}
let indices: Vec<usize> = group
.iter()
.copied()
.filter(|index| lowered_timed_note(self.lowered.get(*index)).is_some())
.collect();
if indices.is_empty() {
self.diagnostics.push(unmatched_tie_warning(tie.span));
return;
}
for index in indices {
self.register_pending_tie(index, tie);
}
}
pub(crate) fn register_pending_tie(&mut self, event_index: usize, marker: TieSyntax) {
let Some(signature) = lowered_timed_note(self.lowered.get(event_index))
.and_then(|timed| note_signature(timed.event.kind))
else {
self.diagnostics.push(unmatched_tie_warning(marker.span));
return;
};
self.preserve_pending_tie_carry_from_event(event_index);
self.pending_ties.push(PendingTie {
event_index,
signature,
marker,
});
}
fn drop_unmatched_tie(&mut self, signature: (char, i8), span: Span) {
self.drop_pending_tie_carry(signature);
self.diagnostics.push(unmatched_tie_warning(span));
}
pub(crate) fn finish_pending_tie_at_boundary(&mut self, _span: Span) {
for tie in std::mem::take(&mut self.pending_ties) {
self.drop_unmatched_tie(tie.signature, tie.marker.span);
}
}
pub(crate) fn finish_pending_tie_if_group_is_not_note(
&mut self,
events: &[(LoweredEventAtom, bool, EventAttachments)],
) {
if self.pending_ties.is_empty() || events.iter().any(|(event, _, _)| is_note_atom(*event)) {
return;
}
self.finish_pending_tie_at_boundary(self.source_span);
}
pub(crate) fn finish_pending_tie_if_possible(&mut self, group: &[usize]) {
if self.pending_ties.is_empty() {
return;
}
if !group
.iter()
.any(|index| lowered_timed_note(self.lowered.get(*index)).is_some())
{
return;
}
let next_notes: Vec<(usize, (char, i8))> = group
.iter()
.copied()
.filter_map(|index| {
lowered_timed_note(self.lowered.get(index))
.and_then(|timed| note_signature(timed.event.kind))
.map(|signature| (index, signature))
})
.collect();
let mut used_next: Vec<usize> = Vec::new();
for tie in std::mem::take(&mut self.pending_ties) {
let start_signature = lowered_timed_note(self.lowered.get(tie.event_index))
.and_then(|timed| note_signature(timed.event.kind));
let Some(start_signature) = start_signature else {
self.drop_unmatched_tie(tie.signature, tie.marker.span);
continue;
};
debug_assert_eq!(start_signature, tie.signature);
let matched = next_notes
.iter()
.find(|(index, signature)| {
*signature == start_signature && !used_next.contains(index)
})
.map(|(index, _)| *index);
match matched {
Some(next_index) => {
used_next.push(next_index);
self.consume_pending_tie_carry(start_signature);
let pair_id = self.next_tie_id;
self.next_tie_id = self.next_tie_id.saturating_add(1);
self.attach_tie(tie.event_index, pair_id, TieRole::Start, tie.marker);
self.attach_tie(next_index, pair_id, TieRole::Stop, tie.marker);
}
None => {
self.drop_unmatched_tie(tie.signature, tie.marker.span);
}
}
}
}
fn attach_tie(&mut self, event_index: usize, pair_id: u32, role: TieRole, marker: TieSyntax) {
if let Some(LoweredEvent::Timed(timed)) = self.lowered.get_mut(event_index) {
timed.attachments.ties.push(TieAttachment {
pair_id,
role,
span: marker.span,
dotted: marker.dotted,
});
}
}
}
fn unmatched_tie_warning(span: Span) -> Diagnostic {
Diagnostic::new(
Severity::Warning,
"abc.music.unmatched_tie",
"Tie marker does not connect two matching notes",
span,
)
.with_spec_reference(abc_slur_reference())
.with_recovery_note(RecoveryNote::new(
"The tie marker was preserved and note durations were not merged.",
))
}