1pub 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#[derive(Debug, Clone, PartialEq, Eq, Hash, Error)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
41pub enum ParseWarning {
42 #[error("syntax error: {0}")]
44 SyntaxError(String),
45 #[error("undefined object: {0:?}")]
47 UndefinedObject(ObjId),
48 #[error("duplicating definition: {0}")]
50 DuplicatingDef(ObjId),
51 #[error("duplicating track object: {0} {1}")]
53 DuplicatingTrackObj(Track, Channel),
54 #[error("duplicating channel object: {0} {1}")]
56 DuplicatingChannelObj(ObjTime, Channel),
57 #[error("unexpected control flow")]
59 UnexpectedControlFlow,
60}
61
62pub(crate) type Result<T> = core::result::Result<T, ParseWarning>;
64
65pub type ParseWarningWithRange = SourceRangeMixin<ParseWarning>;
67
68#[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 pub bms: Bms<T>,
75 pub parse_warnings: Vec<ParseWarningWithRange>,
77}
78
79impl<T: KeyLayoutMapper> Bms<T> {
80 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 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 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 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 }
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 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 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 }
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 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 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#[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 pub bms: Bms<T>,
1039 pub ast_build_warnings: Vec<AstBuildWarningWithRange>,
1041 pub ast_parse_warnings: Vec<AstParseWarningWithRange>,
1043 pub parse_warnings: Vec<ParseWarningWithRange>,
1045}
1046
1047impl<T: KeyLayoutMapper> Bms<T> {
1048 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}