bms_rs/bms/parse/
token_processor.rs

1//! This module provides [`TokenProcessor`] and its implementations, which reads [`Token`] and applies data to [`Bms`].
2//!
3//! Also it provides preset functions that returns a [`TokenProcessor`] trait object:
4//!
5//! - [`common_preset`] - Commonly used processors.
6//! - [`minor_preset`] - All of processors this crate provided.
7
8use std::{borrow::Cow, cell::RefCell, num::NonZeroU64, rc::Rc};
9
10use itertools::Itertools;
11
12use crate::bms::{
13    error::{ParseError, ParseErrorWithRange},
14    prelude::*,
15};
16
17mod bmp;
18mod bpm;
19mod identity;
20mod judge;
21mod metadata;
22mod music_info;
23mod option;
24mod random;
25mod repr;
26mod resources;
27mod scroll;
28mod section_len;
29mod speed;
30mod sprite;
31mod stop;
32mod text;
33mod video;
34mod volume;
35mod wav;
36
37/// A type alias of `Result<T, Vec<ParseWarningWithRange>`.
38pub type TokenProcessorResult<T> = Result<T, ParseErrorWithRange>;
39
40/// A processor of tokens in the BMS. An implementation takes control only one feature about definitions and placements such as `WAVxx` definition and its sound object.
41pub trait TokenProcessor {
42    /// A result data of the process.
43    type Output;
44
45    /// Processes commands by consuming all the stream `input`. It mutates `input`
46    fn process<P: Prompter>(
47        &self,
48        input: &mut &[&TokenWithRange<'_>],
49        prompter: &P,
50    ) -> TokenProcessorResult<Self::Output>;
51
52    /// Creates a processor [`SequentialProcessor`] which does `self` then `second`.
53    fn then<S>(self, second: S) -> SequentialProcessor<Self, S>
54    where
55        Self: Sized,
56        S: TokenProcessor + Sized,
57    {
58        SequentialProcessor {
59            first: self,
60            second,
61        }
62    }
63
64    /// Maps a result of the processor by the mapping function `f`.
65    fn map<F, O>(self, f: F) -> Mapped<Self, F>
66    where
67        Self: Sized,
68        F: Fn(Self::Output) -> O,
69    {
70        Mapped {
71            source: self,
72            mapping: f,
73        }
74    }
75}
76
77impl<T: TokenProcessor + ?Sized> TokenProcessor for Box<T> {
78    type Output = <T as TokenProcessor>::Output;
79
80    fn process<P: Prompter>(
81        &self,
82        input: &mut &[&TokenWithRange<'_>],
83        prompter: &P,
84    ) -> TokenProcessorResult<Self::Output> {
85        T::process(self, input, prompter)
86    }
87}
88
89/// A processor [`SequentialProcessor`] which does `first` then `second`.
90#[derive(Debug, Clone, PartialEq, Eq, Hash)]
91pub struct SequentialProcessor<F, S> {
92    first: F,
93    second: S,
94}
95
96impl<F, S> TokenProcessor for SequentialProcessor<F, S>
97where
98    F: TokenProcessor,
99    S: TokenProcessor,
100{
101    type Output = (F::Output, S::Output);
102
103    fn process<P: Prompter>(
104        &self,
105        input: &mut &[&TokenWithRange<'_>],
106        prompter: &P,
107    ) -> TokenProcessorResult<Self::Output> {
108        let mut cloned = *input;
109        let first_output = self.first.process(&mut cloned, prompter)?;
110        let second_output = self.second.process(input, prompter)?;
111        Ok((first_output, second_output))
112    }
113}
114
115/// A processor [`SequentialProcessor`] which maps the output of the token processor `TP` by the function `F`.
116#[derive(Debug, Clone, PartialEq, Eq, Hash)]
117pub struct Mapped<TP, F> {
118    source: TP,
119    mapping: F,
120}
121
122impl<O, TP, F> TokenProcessor for Mapped<TP, F>
123where
124    TP: TokenProcessor,
125    F: Fn(TP::Output) -> O,
126{
127    type Output = O;
128
129    fn process<P: Prompter>(
130        &self,
131        input: &mut &[&TokenWithRange<'_>],
132        prompter: &P,
133    ) -> TokenProcessorResult<Self::Output> {
134        let res = self.source.process(input, prompter)?;
135        Ok((self.mapping)(res))
136    }
137}
138
139/// Returns commonly used processors.
140pub(crate) fn common_preset<T: KeyLayoutMapper, R: Rng>(
141    rng: Rc<RefCell<R>>,
142    relaxed: bool,
143) -> impl TokenProcessor<Output = Bms> {
144    let case_sensitive_obj_id = Rc::new(RefCell::new(false));
145    let sub_processor = repr::RepresentationProcessor::new(&case_sensitive_obj_id)
146        .then(bmp::BmpProcessor::new(&case_sensitive_obj_id))
147        .then(bpm::BpmProcessor::new(&case_sensitive_obj_id))
148        .then(judge::JudgeProcessor::new(&case_sensitive_obj_id))
149        .then(metadata::MetadataProcessor)
150        .then(music_info::MusicInfoProcessor)
151        .then(scroll::ScrollProcessor::new(&case_sensitive_obj_id))
152        .then(section_len::SectionLenProcessor)
153        .then(speed::SpeedProcessor::new(&case_sensitive_obj_id))
154        .then(sprite::SpriteProcessor)
155        .then(stop::StopProcessor::new(&case_sensitive_obj_id))
156        .then(video::VideoProcessor::new(&case_sensitive_obj_id))
157        .then(wav::WavProcessor::<T>::new(&case_sensitive_obj_id));
158    random::RandomTokenProcessor::new(rng, sub_processor, relaxed).map(
159        |(
160            (
161                (
162                    (
163                        (
164                            (
165                                ((((((repr, bmp), bpm), judge), metadata), music_info), scroll),
166                                section_len,
167                            ),
168                            speed,
169                        ),
170                        sprite,
171                    ),
172                    stop,
173                ),
174                video,
175            ),
176            wav,
177        )| {
178            Bms {
179                bmp,
180                bpm,
181                judge,
182                metadata,
183                music_info,
184
185                option: Default::default(),
186                repr,
187
188                resources: Default::default(),
189                scroll,
190                section_len,
191                speed,
192                sprite,
193                stop,
194                text: Default::default(),
195                video,
196                volume: Default::default(),
197                wav,
198            }
199        },
200    )
201}
202
203/// Returns all of processors this crate provided.
204pub(crate) fn minor_preset<T: KeyLayoutMapper, R: Rng>(
205    rng: Rc<RefCell<R>>,
206    relaxed: bool,
207) -> impl TokenProcessor<Output = Bms> {
208    let case_sensitive_obj_id = Rc::new(RefCell::new(false));
209    let sub_processor = repr::RepresentationProcessor::new(&case_sensitive_obj_id)
210        .then(bmp::BmpProcessor::new(&case_sensitive_obj_id))
211        .then(bpm::BpmProcessor::new(&case_sensitive_obj_id))
212        .then(judge::JudgeProcessor::new(&case_sensitive_obj_id))
213        .then(metadata::MetadataProcessor)
214        .then(music_info::MusicInfoProcessor);
215
216    let sub_processor = sub_processor
217        .then(option::OptionProcessor::new(&case_sensitive_obj_id))
218        .then(resources::ResourcesProcessor);
219    let sub_processor = sub_processor
220        .then(scroll::ScrollProcessor::new(&case_sensitive_obj_id))
221        .then(section_len::SectionLenProcessor)
222        .then(speed::SpeedProcessor::new(&case_sensitive_obj_id))
223        .then(sprite::SpriteProcessor)
224        .then(stop::StopProcessor::new(&case_sensitive_obj_id))
225        .then(text::TextProcessor::new(&case_sensitive_obj_id))
226        .then(video::VideoProcessor::new(&case_sensitive_obj_id))
227        .then(volume::VolumeProcessor)
228        .then(wav::WavProcessor::<T>::new(&case_sensitive_obj_id));
229    random::RandomTokenProcessor::new(rng, sub_processor, relaxed).map(
230        |(
231            (
232                (
233                    (
234                        (
235                            (
236                                (
237                                    (
238                                        (
239                                            (
240                                                (
241                                                    (
242                                                        ((((repr, bmp), bpm), judge), metadata),
243                                                        music_info,
244                                                    ),
245                                                    option,
246                                                ),
247                                                resources,
248                                            ),
249                                            scroll,
250                                        ),
251                                        section_len,
252                                    ),
253                                    speed,
254                                ),
255                                sprite,
256                            ),
257                            stop,
258                        ),
259                        text,
260                    ),
261                    video,
262                ),
263                volume,
264            ),
265            wav,
266        )| Bms {
267            bmp,
268            bpm,
269            judge,
270            metadata,
271            music_info,
272            option,
273            repr,
274            resources,
275            scroll,
276            section_len,
277            speed,
278            sprite,
279            stop,
280            text,
281            video,
282            volume,
283            wav,
284        },
285    )
286}
287
288fn all_tokens<
289    'a,
290    P: Prompter,
291    F: FnMut(&'a Token<'_>) -> Result<Option<ParseWarning>, ParseError>,
292>(
293    input: &mut &'a [&TokenWithRange<'_>],
294    prompter: &P,
295    mut f: F,
296) -> TokenProcessorResult<()> {
297    for token in &**input {
298        if let Some(warning) = f(token.content()).map_err(|err| err.into_wrapper(token))? {
299            prompter.warn(warning.into_wrapper(token));
300        }
301    }
302    *input = &[];
303    Ok(())
304}
305
306fn all_tokens_with_range<
307    'a,
308    P: Prompter,
309    F: FnMut(&'a TokenWithRange<'_>) -> Result<Option<ParseWarning>, ParseError>,
310>(
311    input: &mut &'a [&TokenWithRange<'_>],
312    prompter: &P,
313    mut f: F,
314) -> TokenProcessorResult<()> {
315    for token in &**input {
316        if let Some(warning) = f(token).map_err(|err| err.into_wrapper(token))? {
317            prompter.warn(warning.into_wrapper(token));
318        }
319    }
320    *input = &[];
321    Ok(())
322}
323
324fn parse_obj_ids<P: Prompter>(
325    track: Track,
326    message: SourceRangeMixin<&str>,
327    prompter: &P,
328    case_sensitive_obj_id: &RefCell<bool>,
329) -> impl Iterator<Item = (ObjTime, ObjId)> {
330    if !message.content().len().is_multiple_of(2) {
331        prompter.warn(
332            ParseWarning::SyntaxError("expected 2-digit object ids".into()).into_wrapper(&message),
333        );
334    }
335
336    let denom_opt = NonZeroU64::new(message.content().len() as u64 / 2);
337    message
338        .content()
339        .chars()
340        .tuples()
341        .enumerate()
342        .filter_map(move |(i, (c1, c2))| {
343            let buf = String::from_iter([c1, c2]);
344            match ObjId::try_from(&buf, *case_sensitive_obj_id.borrow()) {
345                Ok(id) if id.is_null() => None,
346                Ok(id) => Some((
347                    ObjTime::new(
348                        track.0,
349                        i as u64,
350                        denom_opt.expect("len / 2 won't be zero on reading tuples"),
351                    ),
352                    id,
353                )),
354                Err(warning) => {
355                    prompter.warn(warning.into_wrapper(&message));
356                    None
357                }
358            }
359        })
360}
361
362fn parse_hex_values<P: Prompter>(
363    track: Track,
364    message: SourceRangeMixin<&str>,
365    prompter: &P,
366) -> impl Iterator<Item = (ObjTime, u8)> {
367    if !message.content().len().is_multiple_of(2) {
368        prompter.warn(
369            ParseWarning::SyntaxError("expected 2-digit hex values".into()).into_wrapper(&message),
370        );
371    }
372
373    let denom_opt = NonZeroU64::new(message.content().len() as u64 / 2);
374    message
375        .content()
376        .chars()
377        .tuples()
378        .enumerate()
379        .filter_map(move |(i, (c1, c2))| {
380            let buf = String::from_iter([c1, c2]);
381            match u8::from_str_radix(&buf, 16) {
382                Ok(value) => Some((
383                    ObjTime::new(
384                        track.0,
385                        i as u64,
386                        denom_opt.expect("len / 2 won't be zero on reading tuples"),
387                    ),
388                    value,
389                )),
390                Err(_) => {
391                    prompter.warn(
392                        ParseWarning::SyntaxError(format!("invalid hex digits ({buf:?}"))
393                            .into_wrapper(&message),
394                    );
395                    None
396                }
397            }
398        })
399}
400
401fn filter_message(message: &str) -> Cow<'_, str> {
402    let result = message
403        .chars()
404        .try_fold(String::with_capacity(message.len()), |mut acc, ch| {
405            if ch.is_ascii_alphanumeric() || ch == '-' || ch == '.' {
406                acc.push(ch);
407                Ok(acc)
408            } else {
409                Err(acc)
410            }
411        });
412    match result {
413        Ok(_) => Cow::Borrowed(message),
414        Err(filtered) => Cow::Owned(filtered),
415    }
416}