bms_rs/bms/
parse.rs

1//! Parsing Bms from [`TokenStream`].
2//!
3//! Raw [String] == [lex] ==> [`TokenStream`] (in [`BmsLexOutput`]) == [parse] ==> [Bms] (in
4//! [`BmsParseOutput`])
5
6pub mod check_playing;
7pub mod prompt;
8pub mod validity;
9
10use fraction::GenericFraction;
11use itertools::Itertools;
12use std::str::FromStr;
13use thiserror::Error;
14
15use super::prelude::*;
16use crate::bms::{
17    ast::{
18        AstBuildOutput, AstBuildWarningWithRange, AstParseOutput, AstParseWarningWithRange,
19        AstRoot, rng::Rng,
20    },
21    command::{
22        ObjId,
23        channel::{
24            Channel,
25            mapper::{KeyLayoutBeat, KeyLayoutMapper},
26        },
27        mixin::SourceRangeMixin,
28        time::{ObjTime, Track},
29    },
30    lex::token::{Token, TokenWithRange},
31    model::Bms,
32};
33
34#[cfg(feature = "minor-command")]
35use self::prompt::ChannelDuplication;
36use self::prompt::PromptHandler;
37
38/// An error occurred when parsing the [`TokenStream`].
39#[derive(Debug, Clone, PartialEq, Eq, Hash, Error)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
41pub enum ParseWarning {
42    /// Syntax formed from the commands was invalid.
43    #[error("syntax error: {0}")]
44    SyntaxError(String),
45    /// The object has required but not defined,
46    #[error("undefined object: {0:?}")]
47    UndefinedObject(ObjId),
48    /// Has duplicated definition, that `prompt_handler` returned [`DuplicationWorkaround::Warn`].
49    #[error("duplicating definition: {0}")]
50    DuplicatingDef(ObjId),
51    /// Has duplicated track object, that `prompt_handler` returned [`DuplicationWorkaround::Warn`].
52    #[error("duplicating track object: {0} {1}")]
53    DuplicatingTrackObj(Track, Channel),
54    /// Has duplicated channel object, that `prompt_handler` returned [`DuplicationWorkaround::Warn`].
55    #[error("duplicating channel object: {0} {1}")]
56    DuplicatingChannelObj(ObjTime, Channel),
57    /// Unexpected control flow.
58    #[error("unexpected control flow")]
59    UnexpectedControlFlow,
60}
61
62/// Type alias of `core::result::Result<T, ParseWarning>`
63pub(crate) type Result<T> = core::result::Result<T, ParseWarning>;
64
65/// A parse warning with position information.
66pub type ParseWarningWithRange = SourceRangeMixin<ParseWarning>;
67
68/// Bms Parse Output
69#[derive(Debug, Clone, PartialEq, Eq)]
70#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
71#[must_use]
72pub struct ParseOutput<T: KeyLayoutMapper = KeyLayoutBeat> {
73    /// The output Bms.
74    pub bms: Bms<T>,
75    /// Warnings that occurred during parsing.
76    pub parse_warnings: Vec<ParseWarningWithRange>,
77}
78
79impl<T: KeyLayoutMapper> Bms<T> {
80    /// Parses a token stream into [`Bms`] without AST.
81    pub fn from_token_stream<'a>(
82        token_iter: impl IntoIterator<Item = &'a TokenWithRange<'a>>,
83        mut prompt_handler: impl PromptHandler,
84    ) -> ParseOutput<T> {
85        let mut bms = Self::default();
86        let mut parse_warnings = vec![];
87        for token in token_iter {
88            if let Err(error) = bms.parse(token, &mut prompt_handler) {
89                parse_warnings.push(error.into_wrapper(token));
90            }
91        }
92
93        ParseOutput {
94            bms,
95            parse_warnings,
96        }
97    }
98}
99
100impl<T: KeyLayoutMapper> Bms<T> {
101    pub(crate) fn parse(
102        &mut self,
103        token: &TokenWithRange,
104        prompt_handler: &mut impl PromptHandler,
105    ) -> Result<()> {
106        match token.content() {
107            Token::Artist(artist) => self.header.artist = Some(artist.to_string()),
108            #[cfg(feature = "minor-command")]
109            Token::AtBga {
110                id,
111                source_bmp,
112                trim_top_left,
113                trim_size,
114                draw_point,
115            } => {
116                let to_insert = AtBgaDef {
117                    id: *id,
118                    source_bmp: *source_bmp,
119                    trim_top_left: trim_top_left.to_owned().into(),
120                    trim_size: trim_size.to_owned().into(),
121                    draw_point: draw_point.to_owned().into(),
122                };
123                if let Some(older) = self.scope_defines.atbga_defs.get_mut(id) {
124                    prompt_handler
125                        .handle_def_duplication(DefDuplication::AtBga {
126                            id: *id,
127                            older,
128                            newer: &to_insert,
129                        })
130                        .apply_def(older, to_insert, *id)?;
131                } else {
132                    self.scope_defines.atbga_defs.insert(*id, to_insert);
133                }
134            }
135            Token::Banner(file) => self.header.banner = Some(file.into()),
136            Token::BackBmp(bmp) => self.header.back_bmp = Some(bmp.into()),
137            #[cfg(feature = "minor-command")]
138            Token::Bga {
139                id,
140                source_bmp,
141                trim_top_left,
142                trim_bottom_right,
143                draw_point,
144            } => {
145                let to_insert = BgaDef {
146                    id: *id,
147                    source_bmp: *source_bmp,
148                    trim_top_left: trim_top_left.to_owned().into(),
149                    trim_bottom_right: trim_bottom_right.to_owned().into(),
150                    draw_point: draw_point.to_owned().into(),
151                };
152                if let Some(older) = self.scope_defines.bga_defs.get_mut(id) {
153                    prompt_handler
154                        .handle_def_duplication(DefDuplication::Bga {
155                            id: *id,
156                            older,
157                            newer: &to_insert,
158                        })
159                        .apply_def(older, to_insert, *id)?;
160                } else {
161                    self.scope_defines.bga_defs.insert(*id, to_insert);
162                }
163            }
164            Token::Bmp(id, path) => {
165                if id.is_none() {
166                    self.graphics.poor_bmp = Some(path.into());
167                    return Ok(());
168                }
169                let id = id.ok_or(ParseWarning::SyntaxError(
170                    "BMP id should not be None".to_string(),
171                ))?;
172                let to_insert = Bmp {
173                    file: path.into(),
174                    transparent_color: Argb::default(),
175                };
176                if let Some(older) = self.graphics.bmp_files.get_mut(&id) {
177                    prompt_handler
178                        .handle_def_duplication(DefDuplication::Bmp {
179                            id,
180                            older,
181                            newer: &to_insert,
182                        })
183                        .apply_def(older, to_insert, id)?;
184                } else {
185                    self.graphics.bmp_files.insert(id, to_insert);
186                }
187            }
188            Token::Bpm(bpm) => {
189                self.arrangers.bpm = Some(bpm.clone());
190            }
191            Token::BpmChange(id, bpm) => {
192                if let Some(older) = self.scope_defines.bpm_defs.get_mut(id) {
193                    prompt_handler
194                        .handle_def_duplication(DefDuplication::BpmChange {
195                            id: *id,
196                            older: older.clone(),
197                            newer: bpm.clone(),
198                        })
199                        .apply_def(older, bpm.clone(), *id)?;
200                } else {
201                    self.scope_defines.bpm_defs.insert(*id, bpm.clone());
202                }
203            }
204            #[cfg(feature = "minor-command")]
205            Token::ChangeOption(id, option) => {
206                if let Some(older) = self.others.change_options.get_mut(id) {
207                    prompt_handler
208                        .handle_def_duplication(DefDuplication::ChangeOption {
209                            id: *id,
210                            older,
211                            newer: option,
212                        })
213                        .apply_def(older, option.to_string(), *id)?;
214                } else {
215                    self.others.change_options.insert(*id, option.to_string());
216                }
217            }
218            Token::Comment(comment) => self
219                .header
220                .comment
221                .get_or_insert_with(Vec::new)
222                .push(comment.to_string()),
223            Token::Difficulty(diff) => self.header.difficulty = Some(*diff),
224            Token::Email(email) => self.header.email = Some(email.to_string()),
225            Token::ExBmp(id, transparent_color, path) => {
226                let to_insert = Bmp {
227                    file: path.into(),
228                    transparent_color: *transparent_color,
229                };
230                if let Some(older) = self.graphics.bmp_files.get_mut(id) {
231                    prompt_handler
232                        .handle_def_duplication(DefDuplication::Bmp {
233                            id: *id,
234                            older,
235                            newer: &to_insert,
236                        })
237                        .apply_def(older, to_insert, *id)?;
238                } else {
239                    self.graphics.bmp_files.insert(*id, to_insert);
240                }
241            }
242            Token::ExRank(id, judge_level) => {
243                let to_insert = ExRankDef {
244                    id: *id,
245                    judge_level: *judge_level,
246                };
247                if let Some(older) = self.scope_defines.exrank_defs.get_mut(id) {
248                    prompt_handler
249                        .handle_def_duplication(DefDuplication::ExRank {
250                            id: *id,
251                            older,
252                            newer: &to_insert,
253                        })
254                        .apply_def(older, to_insert, *id)?;
255                } else {
256                    self.scope_defines.exrank_defs.insert(*id, to_insert);
257                }
258            }
259            #[cfg(feature = "minor-command")]
260            Token::ExWav {
261                id,
262                pan,
263                volume,
264                frequency,
265                path,
266            } => {
267                let to_insert = ExWavDef {
268                    id: *id,
269                    pan: *pan,
270                    volume: *volume,
271                    frequency: *frequency,
272                    path: path.into(),
273                };
274                if let Some(older) = self.scope_defines.exwav_defs.get_mut(id) {
275                    prompt_handler
276                        .handle_def_duplication(DefDuplication::ExWav {
277                            id: *id,
278                            older,
279                            newer: &to_insert,
280                        })
281                        .apply_def(older, to_insert, *id)?;
282                } else {
283                    self.scope_defines.exwav_defs.insert(*id, to_insert);
284                }
285            }
286            Token::Genre(genre) => self.header.genre = Some(genre.to_string()),
287            Token::LnTypeRdm => {
288                self.header.ln_type = LnType::Rdm;
289            }
290            Token::LnTypeMgq => {
291                self.header.ln_type = LnType::Mgq;
292            }
293            Token::Maker(maker) => self.header.maker = Some(maker.to_string()),
294            #[cfg(feature = "minor-command")]
295            Token::MidiFile(midi_file) => self.notes.midi_file = Some(midi_file.into()),
296            #[cfg(feature = "minor-command")]
297            Token::OctFp => self.others.is_octave = true,
298            #[cfg(feature = "minor-command")]
299            Token::Option(option) => self
300                .others
301                .options
302                .get_or_insert_with(Vec::new)
303                .push(option.to_string()),
304            Token::PathWav(wav_path_root) => self.notes.wav_path_root = Some(wav_path_root.into()),
305            Token::Player(player) => self.header.player = Some(*player),
306            Token::PlayLevel(play_level) => self.header.play_level = Some(*play_level),
307            Token::PoorBga(poor_bga_mode) => self.graphics.poor_bga_mode = *poor_bga_mode,
308            Token::Rank(rank) => self.header.rank = Some(*rank),
309            Token::Scroll(id, factor) => {
310                if let Some(older) = self.scope_defines.scroll_defs.get_mut(id) {
311                    prompt_handler
312                        .handle_def_duplication(DefDuplication::ScrollingFactorChange {
313                            id: *id,
314                            older: older.clone(),
315                            newer: factor.clone(),
316                        })
317                        .apply_def(older, factor.clone(), *id)?;
318                } else {
319                    self.scope_defines.scroll_defs.insert(*id, factor.clone());
320                }
321            }
322            Token::Speed(id, factor) => {
323                if let Some(older) = self.scope_defines.speed_defs.get_mut(id) {
324                    prompt_handler
325                        .handle_def_duplication(DefDuplication::SpeedFactorChange {
326                            id: *id,
327                            older: older.clone(),
328                            newer: factor.clone(),
329                        })
330                        .apply_def(older, factor.clone(), *id)?;
331                } else {
332                    self.scope_defines.speed_defs.insert(*id, factor.clone());
333                }
334            }
335            Token::StageFile(file) => self.header.stage_file = Some(file.into()),
336            Token::Stop(id, len) => {
337                if let Some(older) = self.scope_defines.stop_defs.get_mut(id) {
338                    prompt_handler
339                        .handle_def_duplication(DefDuplication::Stop {
340                            id: *id,
341                            older: older.clone(),
342                            newer: len.clone(),
343                        })
344                        .apply_def(older, len.clone(), *id)?;
345                } else {
346                    self.scope_defines.stop_defs.insert(*id, len.clone());
347                }
348            }
349            Token::SubArtist(sub_artist) => self.header.sub_artist = Some(sub_artist.to_string()),
350            Token::SubTitle(subtitle) => self.header.subtitle = Some(subtitle.to_string()),
351            Token::Text(id, text) => {
352                if let Some(older) = self.others.texts.get_mut(id) {
353                    prompt_handler
354                        .handle_def_duplication(DefDuplication::Text {
355                            id: *id,
356                            older,
357                            newer: text,
358                        })
359                        .apply_def(older, text.to_string(), *id)?;
360                } else {
361                    self.others.texts.insert(*id, text.to_string());
362                }
363            }
364            Token::Title(title) => self.header.title = Some(title.to_string()),
365            Token::Total(total) => {
366                self.header.total = Some(total.clone());
367            }
368            Token::Url(url) => self.header.url = Some(url.to_string()),
369            Token::VideoFile(video_file) => self.graphics.video_file = Some(video_file.into()),
370            Token::VolWav(volume) => self.header.volume = *volume,
371            Token::Wav(id, path) => {
372                if let Some(older) = self.notes.wav_files.get_mut(id) {
373                    prompt_handler
374                        .handle_def_duplication(DefDuplication::Wav {
375                            id: *id,
376                            older,
377                            newer: path,
378                        })
379                        .apply_def(older, path.into(), *id)?;
380                } else {
381                    self.notes.wav_files.insert(*id, path.into());
382                }
383            }
384            #[cfg(feature = "minor-command")]
385            Token::Stp(ev) => {
386                // Store by ObjTime as key, handle duplication with prompt handler
387                let key = ev.time;
388                if let Some(older) = self.arrangers.stp_events.get_mut(&key) {
389                    prompt_handler
390                        .handle_channel_duplication(ChannelDuplication::StpEvent {
391                            time: key,
392                            older,
393                            newer: ev,
394                        })
395                        .apply_channel(older, *ev, key, Channel::Stop)?;
396                } else {
397                    self.arrangers.stp_events.insert(key, *ev);
398                }
399            }
400            #[cfg(feature = "minor-command")]
401            Token::WavCmd(ev) => {
402                // Store by wav_index as key, handle duplication with prompt handler
403                let key = ev.wav_index;
404                if let Some(older) = self.scope_defines.wavcmd_events.get_mut(&key) {
405                    prompt_handler
406                        .handle_def_duplication(DefDuplication::WavCmdEvent {
407                            wav_index: key,
408                            older,
409                            newer: ev,
410                        })
411                        .apply_def(older, *ev, key)?;
412                } else {
413                    self.scope_defines.wavcmd_events.insert(key, *ev);
414                }
415            }
416            #[cfg(feature = "minor-command")]
417            Token::SwBga(id, ev) => {
418                if let Some(older) = self.scope_defines.swbga_events.get_mut(id) {
419                    prompt_handler
420                        .handle_def_duplication(DefDuplication::SwBgaEvent {
421                            id: *id,
422                            older,
423                            newer: ev,
424                        })
425                        .apply_def(older, ev.clone(), *id)?;
426                } else {
427                    self.scope_defines.swbga_events.insert(*id, ev.clone());
428                }
429            }
430            #[cfg(feature = "minor-command")]
431            Token::Argb(id, argb) => {
432                if let Some(older) = self.scope_defines.argb_defs.get_mut(id) {
433                    prompt_handler
434                        .handle_def_duplication(DefDuplication::BgaArgb {
435                            id: *id,
436                            older,
437                            newer: argb,
438                        })
439                        .apply_def(older, *argb, *id)?;
440                } else {
441                    self.scope_defines.argb_defs.insert(*id, *argb);
442                }
443            }
444            #[cfg(feature = "minor-command")]
445            Token::Seek(id, v) => {
446                if let Some(older) = self.others.seek_events.get_mut(id) {
447                    prompt_handler
448                        .handle_def_duplication(DefDuplication::SeekEvent {
449                            id: *id,
450                            older,
451                            newer: v,
452                        })
453                        .apply_def(older, v.clone(), *id)?;
454                } else {
455                    self.others.seek_events.insert(*id, v.clone());
456                }
457            }
458            #[cfg(feature = "minor-command")]
459            Token::ExtChr(ev) => {
460                self.others.extchr_events.push(*ev);
461            }
462            #[cfg(feature = "minor-command")]
463            Token::MaterialsWav(path) => {
464                self.notes.materials_wav.push(path.to_path_buf());
465            }
466            #[cfg(feature = "minor-command")]
467            Token::MaterialsBmp(path) => {
468                self.graphics.materials_bmp.push(path.to_path_buf());
469            }
470            Token::Message {
471                track,
472                channel: Channel::BpmChange,
473                message,
474            } => {
475                for (time, obj) in ids_from_message(*track, message) {
476                    // Record used BPM change id for validity checks
477                    self.arrangers.bpm_change_ids_used.insert(obj);
478                    let bpm = self
479                        .scope_defines
480                        .bpm_defs
481                        .get(&obj)
482                        .ok_or(ParseWarning::UndefinedObject(obj))?;
483                    self.arrangers.push_bpm_change(
484                        BpmChangeObj {
485                            time,
486                            bpm: bpm.clone(),
487                        },
488                        prompt_handler,
489                    )?;
490                }
491            }
492            Token::Message {
493                track,
494                channel: Channel::BpmChangeU8,
495                message,
496            } => {
497                let denominator = message.len() as u64 / 2;
498                for (i, (c1, c2)) in message.chars().tuples().enumerate() {
499                    let bpm = c1.to_digit(16).ok_or_else(|| {
500                        ParseWarning::SyntaxError(format!("Invalid hex digit: {c1}",))
501                    })? * 16
502                        + c2.to_digit(16).ok_or_else(|| {
503                            ParseWarning::SyntaxError(format!("Invalid hex digit: {c2}",))
504                        })?;
505                    if bpm == 0 {
506                        continue;
507                    }
508                    let time = ObjTime::new(track.0, i as u64, denominator);
509                    self.arrangers.push_bpm_change(
510                        BpmChangeObj {
511                            time,
512                            bpm: Decimal::from(bpm),
513                        },
514                        prompt_handler,
515                    )?;
516                }
517            }
518            Token::Message {
519                track,
520                channel: Channel::Scroll,
521                message,
522            } => {
523                for (time, obj) in ids_from_message(*track, message) {
524                    let factor = self
525                        .scope_defines
526                        .scroll_defs
527                        .get(&obj)
528                        .ok_or(ParseWarning::UndefinedObject(obj))?;
529                    self.arrangers.push_scrolling_factor_change(
530                        ScrollingFactorObj {
531                            time,
532                            factor: factor.clone(),
533                        },
534                        prompt_handler,
535                    )?;
536                }
537            }
538            Token::Message {
539                track,
540                channel: Channel::Speed,
541                message,
542            } => {
543                for (time, obj) in ids_from_message(*track, message) {
544                    let factor = self
545                        .scope_defines
546                        .speed_defs
547                        .get(&obj)
548                        .ok_or(ParseWarning::UndefinedObject(obj))?;
549                    self.arrangers.push_speed_factor_change(
550                        SpeedObj {
551                            time,
552                            factor: factor.clone(),
553                        },
554                        prompt_handler,
555                    )?;
556                }
557            }
558            #[cfg(feature = "minor-command")]
559            Token::Message {
560                track,
561                channel: Channel::ChangeOption,
562                message,
563            } => {
564                for (_time, obj) in ids_from_message(*track, message) {
565                    let _option = self
566                        .others
567                        .change_options
568                        .get(&obj)
569                        .ok_or(ParseWarning::UndefinedObject(obj))?;
570                    // Here we can add logic to handle ChangeOption
571                    // Currently just ignored because change_options are already stored in notes
572                }
573            }
574            Token::Message {
575                track,
576                channel: Channel::SectionLen,
577                message,
578            } => {
579                let length = Decimal::from(Decimal::from_fraction(
580                    GenericFraction::from_str(message).map_err(|_| {
581                        ParseWarning::SyntaxError(format!("Invalid section length: {message}"))
582                    })?,
583                ));
584                if length <= Decimal::from(0u64) {
585                    return Err(ParseWarning::SyntaxError(
586                        "section length must be greater than zero".to_string(),
587                    ));
588                }
589                self.arrangers.push_section_len_change(
590                    SectionLenChangeObj {
591                        track: *track,
592                        length,
593                    },
594                    prompt_handler,
595                )?;
596            }
597            Token::Message {
598                track,
599                channel: Channel::Stop,
600                message,
601            } => {
602                for (time, obj) in ids_from_message(*track, message) {
603                    // Record used STOP id for validity checks
604                    self.arrangers.stop_ids_used.insert(obj);
605                    let duration = self
606                        .scope_defines
607                        .stop_defs
608                        .get(&obj)
609                        .ok_or(ParseWarning::UndefinedObject(obj))?;
610                    self.arrangers.push_stop(StopObj {
611                        time,
612                        duration: duration.clone(),
613                    });
614                }
615            }
616            Token::Message {
617                track,
618                channel:
619                    channel @ (Channel::BgaBase
620                    | Channel::BgaPoor
621                    | Channel::BgaLayer
622                    | Channel::BgaLayer2),
623                message,
624            } => {
625                for (time, obj) in ids_from_message(*track, message) {
626                    if !self.graphics.bmp_files.contains_key(&obj) {
627                        return Err(ParseWarning::UndefinedObject(obj));
628                    }
629                    let layer = BgaLayer::from_channel(*channel)
630                        .unwrap_or_else(|| panic!("Invalid channel for BgaLayer: {channel:?}"));
631                    self.graphics.push_bga_change(
632                        BgaObj {
633                            time,
634                            id: obj,
635                            layer,
636                        },
637                        *channel,
638                        prompt_handler,
639                    )?;
640                }
641            }
642            Token::Message {
643                track,
644                channel: Channel::Bgm,
645                message,
646            } => {
647                for (time, obj) in ids_from_message(*track, message) {
648                    self.notes.push_bgm(time, obj);
649                }
650            }
651            Token::Message {
652                track,
653                channel: Channel::Note { channel_id },
654                message,
655            } => {
656                // Parse the channel ID to get note components
657                for (offset, obj) in ids_from_message(*track, message) {
658                    self.notes.push_note(WavObj {
659                        offset,
660                        channel_id: *channel_id,
661                        wav_id: obj,
662                    });
663                }
664            }
665            #[cfg(feature = "minor-command")]
666            Token::Message {
667                track,
668                channel:
669                    channel @ (Channel::BgaBaseOpacity
670                    | Channel::BgaLayerOpacity
671                    | Channel::BgaLayer2Opacity
672                    | Channel::BgaPoorOpacity),
673                message,
674            } => {
675                for (time, opacity_value) in opacity_from_message(*track, message) {
676                    let layer = BgaLayer::from_channel(*channel)
677                        .unwrap_or_else(|| panic!("Invalid channel for BgaLayer: {channel:?}"));
678                    self.graphics.push_bga_opacity_change(
679                        BgaOpacityObj {
680                            time,
681                            layer,
682                            opacity: opacity_value,
683                        },
684                        *channel,
685                        prompt_handler,
686                    )?;
687                }
688            }
689            Token::Message {
690                track,
691                channel: Channel::BgmVolume,
692                message,
693            } => {
694                for (time, volume_value) in volume_from_message(*track, message) {
695                    self.notes.push_bgm_volume_change(
696                        BgmVolumeObj {
697                            time,
698                            volume: volume_value,
699                        },
700                        prompt_handler,
701                    )?;
702                }
703            }
704            Token::Message {
705                track,
706                channel: Channel::KeyVolume,
707                message,
708            } => {
709                for (time, volume_value) in volume_from_message(*track, message) {
710                    self.notes.push_key_volume_change(
711                        KeyVolumeObj {
712                            time,
713                            volume: volume_value,
714                        },
715                        prompt_handler,
716                    )?;
717                }
718            }
719            #[cfg(feature = "minor-command")]
720            Token::Message {
721                track,
722                channel:
723                    channel @ (Channel::BgaBaseArgb
724                    | Channel::BgaLayerArgb
725                    | Channel::BgaLayer2Argb
726                    | Channel::BgaPoorArgb),
727                message,
728            } => {
729                for (time, argb_id) in ids_from_message(*track, message) {
730                    let layer = BgaLayer::from_channel(*channel)
731                        .unwrap_or_else(|| panic!("Invalid channel for BgaLayer: {channel:?}"));
732                    let argb = self
733                        .scope_defines
734                        .argb_defs
735                        .get(&argb_id)
736                        .ok_or(ParseWarning::UndefinedObject(argb_id))?;
737                    self.graphics.push_bga_argb_change(
738                        BgaArgbObj {
739                            time,
740                            layer,
741                            argb: *argb,
742                        },
743                        *channel,
744                        prompt_handler,
745                    )?;
746                }
747            }
748            #[cfg(feature = "minor-command")]
749            Token::Message {
750                track,
751                channel: Channel::Seek,
752                message,
753            } => {
754                for (time, seek_id) in ids_from_message(*track, message) {
755                    let position = self
756                        .others
757                        .seek_events
758                        .get(&seek_id)
759                        .ok_or(ParseWarning::UndefinedObject(seek_id))?;
760                    self.notes.push_seek_event(
761                        SeekObj {
762                            time,
763                            position: position.clone(),
764                        },
765                        prompt_handler,
766                    )?;
767                }
768            }
769            Token::Message {
770                track,
771                channel: Channel::Text,
772                message,
773            } => {
774                for (time, text_id) in ids_from_message(*track, message) {
775                    let text = self
776                        .others
777                        .texts
778                        .get(&text_id)
779                        .ok_or(ParseWarning::UndefinedObject(text_id))?;
780                    self.notes.push_text_event(
781                        TextObj {
782                            time,
783                            text: text.clone(),
784                        },
785                        prompt_handler,
786                    )?;
787                }
788            }
789            Token::Message {
790                track,
791                channel: Channel::Judge,
792                message,
793            } => {
794                for (time, judge_id) in ids_from_message(*track, message) {
795                    let exrank_def = self
796                        .scope_defines
797                        .exrank_defs
798                        .get(&judge_id)
799                        .ok_or(ParseWarning::UndefinedObject(judge_id))?;
800                    self.notes.push_judge_event(
801                        JudgeObj {
802                            time,
803                            judge_level: exrank_def.judge_level,
804                        },
805                        prompt_handler,
806                    )?;
807                }
808            }
809            #[cfg(feature = "minor-command")]
810            Token::Message {
811                track,
812                channel: Channel::BgaKeybound,
813                message,
814            } => {
815                for (time, keybound_id) in ids_from_message(*track, message) {
816                    let event = self
817                        .scope_defines
818                        .swbga_events
819                        .get(&keybound_id)
820                        .ok_or(ParseWarning::UndefinedObject(keybound_id))?;
821                    self.notes.push_bga_keybound_event(
822                        BgaKeyboundObj {
823                            time,
824                            event: event.clone(),
825                        },
826                        prompt_handler,
827                    )?;
828                }
829            }
830            #[cfg(feature = "minor-command")]
831            Token::Message {
832                track,
833                channel: Channel::Option,
834                message,
835            } => {
836                for (time, option_id) in ids_from_message(*track, message) {
837                    let option = self
838                        .others
839                        .change_options
840                        .get(&option_id)
841                        .ok_or(ParseWarning::UndefinedObject(option_id))?;
842                    self.notes.push_option_event(
843                        OptionObj {
844                            time,
845                            option: option.clone(),
846                        },
847                        prompt_handler,
848                    )?;
849                }
850            }
851            Token::LnObj(end_id) => {
852                let mut end_note = self
853                    .notes
854                    .pop_latest_of(*end_id)
855                    .ok_or(ParseWarning::UndefinedObject(*end_id))?;
856                let WavObj {
857                    offset, channel_id, ..
858                } = &end_note;
859                let begin_idx = self
860                    .notes
861                    .notes_in(..offset)
862                    .rev()
863                    .find(|(_, obj)| obj.channel_id == *channel_id)
864                    .ok_or_else(|| {
865                        ParseWarning::SyntaxError(format!(
866                            "expected preceding object for #LNOBJ {end_id:?}",
867                        ))
868                    })
869                    .map(|(index, _)| index)?;
870                let mut begin_note = self.notes.pop_by_idx(begin_idx).ok_or_else(|| {
871                    ParseWarning::SyntaxError(format!(
872                        "Cannot find begin note for LNOBJ {end_id:?}"
873                    ))
874                })?;
875
876                let mut begin_note_tuple = begin_note
877                    .channel_id
878                    .try_into_map::<T>()
879                    .ok_or_else(|| {
880                        ParseWarning::SyntaxError(format!(
881                            "channel of specified note for LNOBJ cannot become LN {end_id:?}"
882                        ))
883                    })?
884                    .into_tuple();
885                begin_note_tuple.1 = NoteKind::Long;
886                begin_note.channel_id = T::from_tuple(begin_note_tuple).to_channel_id();
887                self.notes.push_note(begin_note);
888
889                let mut end_note_tuple = end_note
890                    .channel_id
891                    .try_into_map::<T>()
892                    .ok_or_else(|| {
893                        ParseWarning::SyntaxError(format!(
894                            "channel of specified note for LNOBJ cannot become LN {end_id:?}"
895                        ))
896                    })?
897                    .into_tuple();
898                end_note_tuple.1 = NoteKind::Long;
899                end_note.channel_id = T::from_tuple(end_note_tuple).to_channel_id();
900                self.notes.push_note(end_note);
901            }
902            Token::DefExRank(judge_level) => {
903                let judge_level = JudgeLevel::OtherInt(*judge_level as i64);
904                self.scope_defines.exrank_defs.insert(
905                    ObjId::try_from([0, 0]).map_err(|_| {
906                        ParseWarning::SyntaxError("Invalid ObjId [0, 0]".to_string())
907                    })?,
908                    ExRankDef {
909                        id: ObjId::try_from([0, 0]).map_err(|_| {
910                            ParseWarning::SyntaxError("Invalid ObjId [0, 0]".to_string())
911                        })?,
912                        judge_level,
913                    },
914                );
915            }
916            Token::LnMode(ln_mode_type) => {
917                self.header.ln_mode = *ln_mode_type;
918            }
919            Token::Movie(path) => self.header.movie = Some(path.into()),
920            Token::Preview(path) => self.header.preview_music = Some(path.into()),
921            #[cfg(feature = "minor-command")]
922            Token::Cdda(big_uint) => self.others.cdda.push(big_uint.clone()),
923            #[cfg(feature = "minor-command")]
924            Token::BaseBpm(generic_decimal) => {
925                self.arrangers.base_bpm = Some(generic_decimal.clone());
926            }
927            Token::NotACommand(line) => self.others.non_command_lines.push(line.to_string()),
928            Token::UnknownCommand(line) => self.others.unknown_command_lines.push(line.to_string()),
929            Token::Base62 | Token::Charset(_) => {
930                // Pass.
931            }
932            Token::Random(_)
933            | Token::SetRandom(_)
934            | Token::If(_)
935            | Token::ElseIf(_)
936            | Token::Else
937            | Token::EndIf
938            | Token::EndRandom
939            | Token::Switch(_)
940            | Token::SetSwitch(_)
941            | Token::Case(_)
942            | Token::Def
943            | Token::Skip
944            | Token::EndSwitch => {
945                return Err(ParseWarning::UnexpectedControlFlow);
946            }
947            #[cfg(feature = "minor-command")]
948            Token::CharFile(path) => {
949                self.graphics.char_file = Some(path.into());
950            }
951            #[cfg(feature = "minor-command")]
952            Token::DivideProp(prop) => {
953                self.others.divide_prop = Some(prop.to_string());
954            }
955            #[cfg(feature = "minor-command")]
956            Token::Materials(path) => {
957                self.others.materials_path = Some(path.to_path_buf());
958            }
959            #[cfg(feature = "minor-command")]
960            Token::VideoColors(colors) => {
961                self.graphics.video_colors = Some(*colors);
962            }
963            #[cfg(feature = "minor-command")]
964            Token::VideoDly(delay) => {
965                self.graphics.video_dly = Some(delay.clone());
966            }
967            #[cfg(feature = "minor-command")]
968            Token::VideoFs(frame_rate) => {
969                self.graphics.video_fs = Some(frame_rate.clone());
970            }
971        }
972        Ok(())
973    }
974}
975
976fn ids_from_message(track: Track, message: &'_ str) -> impl Iterator<Item = (ObjTime, ObjId)> + '_ {
977    let denominator = message.len() as u64 / 2;
978    let mut chars = message.chars().tuples().enumerate();
979    std::iter::from_fn(move || {
980        let (i, c1, c2) = loop {
981            let (i, (c1, c2)) = chars.next()?;
982            if !(c1 == '0' && c2 == '0') {
983                break (i, c1, c2);
984            }
985        };
986        let obj = ObjId::try_from([c1, c2]).ok()?;
987        let time = ObjTime::new(track.0, i as u64, denominator);
988        Some((time, obj))
989    })
990}
991
992#[cfg(feature = "minor-command")]
993fn opacity_from_message(
994    track: Track,
995    message: &'_ str,
996) -> impl Iterator<Item = (ObjTime, u8)> + '_ {
997    let denominator = message.len() as u64 / 2;
998    let mut chars = message.chars().tuples().enumerate();
999    std::iter::from_fn(move || {
1000        let (i, c1, c2) = loop {
1001            let (i, (c1, c2)) = chars.next()?;
1002            if !(c1 == '0' && c2 == '0') {
1003                break (i, c1, c2);
1004            }
1005        };
1006        // Parse opacity value from hex string
1007        let opacity_hex = format!("{c1}{c2}");
1008        let opacity_value = u8::from_str_radix(&opacity_hex, 16).ok()?;
1009        let time = ObjTime::new(track.0, i as u64, denominator);
1010        Some((time, opacity_value))
1011    })
1012}
1013
1014fn volume_from_message(track: Track, message: &'_ str) -> impl Iterator<Item = (ObjTime, u8)> + '_ {
1015    let denominator = message.len() as u64 / 2;
1016    let mut chars = message.chars().tuples().enumerate();
1017    std::iter::from_fn(move || {
1018        let (i, c1, c2) = loop {
1019            let (i, (c1, c2)) = chars.next()?;
1020            if !(c1 == '0' && c2 == '0') {
1021                break (i, c1, c2);
1022            }
1023        };
1024        // Parse volume value from hex string
1025        let volume_hex = format!("{c1}{c2}");
1026        let volume_value = u8::from_str_radix(&volume_hex, 16).ok()?;
1027        let time = ObjTime::new(track.0, i as u64, denominator);
1028        Some((time, volume_value))
1029    })
1030}
1031
1032/// Bms Parse Output with AST
1033#[derive(Debug, Clone, PartialEq, Eq)]
1034#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1035#[must_use]
1036pub struct ParseOutputWithAst<T: KeyLayoutMapper = KeyLayoutBeat> {
1037    /// The output Bms.
1038    pub bms: Bms<T>,
1039    /// Warnings that occurred during AST building.
1040    pub ast_build_warnings: Vec<AstBuildWarningWithRange>,
1041    /// Warnings that occurred during AST parsing (RNG execution stage).
1042    pub ast_parse_warnings: Vec<AstParseWarningWithRange>,
1043    /// Warnings that occurred during parsing.
1044    pub parse_warnings: Vec<ParseWarningWithRange>,
1045}
1046
1047impl<T: KeyLayoutMapper> Bms<T> {
1048    /// Parses a token stream into [`Bms`] with AST.
1049    pub fn from_token_stream_with_ast<'a>(
1050        token_iter: impl IntoIterator<Item = &'a TokenWithRange<'a>>,
1051        rng: impl Rng,
1052        prompt_handler: impl PromptHandler,
1053    ) -> ParseOutputWithAst<T> {
1054        let AstBuildOutput {
1055            root,
1056            ast_build_warnings,
1057        } = AstRoot::from_token_stream(token_iter);
1058        let (AstParseOutput { token_refs }, ast_parse_warnings) = root.parse_with_warnings(rng);
1059        let ParseOutput {
1060            bms,
1061            parse_warnings,
1062        } = Self::from_token_stream(token_refs, prompt_handler);
1063        ParseOutputWithAst {
1064            bms,
1065            ast_build_warnings,
1066            ast_parse_warnings,
1067            parse_warnings,
1068        }
1069    }
1070}