1use 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
37pub type TokenProcessorResult<T> = Result<T, ParseErrorWithRange>;
39
40pub trait TokenProcessor {
42 type Output;
44
45 fn process<P: Prompter>(
47 &self,
48 input: &mut &[&TokenWithRange<'_>],
49 prompter: &P,
50 ) -> TokenProcessorResult<Self::Output>;
51
52 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 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#[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#[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
139pub(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
203pub(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}