bms_rs/bms/
command.rs

1//! Definitions of command argument data.
2//!
3//! Structures in this module can be used in [Lex] part, [Parse] part, and the output models.
4
5use std::collections::{HashMap, HashSet, VecDeque};
6
7use super::{error::Result, prelude::ParseWarning};
8
9pub mod channel;
10pub mod graphics;
11pub mod mixin;
12pub mod time;
13
14/// Minor command types and utilities.
15///
16/// This module contains types and utilities for minor BMS commands that are only available
17/// when the `minor-command` feature is enabled.
18pub mod minor_command;
19
20/// A play style of the score.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23pub enum PlayerMode {
24    /// For single play, a player uses 5 or 7 keys.
25    Single,
26    /// For couple play, two players use each 5 or 7 keys.
27    Two,
28    /// For double play, a player uses 10 or 14 keys.
29    Double,
30}
31
32impl std::fmt::Display for PlayerMode {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        match self {
35            Self::Single => write!(f, "1"),
36            Self::Two => write!(f, "2"),
37            Self::Double => write!(f, "3"),
38        }
39    }
40}
41
42impl std::str::FromStr for PlayerMode {
43    type Err = ParseWarning;
44
45    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
46        Ok(match s {
47            "1" => Self::Single,
48            "2" => Self::Two,
49            "3" => Self::Double,
50            _ => {
51                return Err(ParseWarning::SyntaxError(
52                    "expected one of 0, 1 or 2".into(),
53                ));
54            }
55        })
56    }
57}
58
59/// A rank to determine judge level, but treatment differs among the BMS players.
60///
61/// IIDX/LR2/beatoraja judge windows: <https://iidx.org/misc/iidx_lr2_beatoraja_diff>
62///
63/// Note: The difficulty `VeryEasy` is decided to be unimplemented.
64/// See [discussions in the PR](https://github.com/MikuroXina/bms-rs/pull/122).
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
67pub enum JudgeLevel {
68    /// Rank 0, the most difficult rank.
69    VeryHard,
70    /// Rank 1, the harder rank.
71    Hard,
72    /// Rank 2, the normal rank.
73    Normal,
74    /// Rank 3, the easier rank.
75    Easy,
76    /// Other integer value. Please See `JudgeLevel` for more details.
77    /// If used for `#EXRANK`, representing percentage.
78    OtherInt(i64),
79}
80
81impl From<i64> for JudgeLevel {
82    fn from(value: i64) -> Self {
83        match value {
84            0 => Self::VeryHard,
85            1 => Self::Hard,
86            2 => Self::Normal,
87            3 => Self::Easy,
88            val => Self::OtherInt(val),
89        }
90    }
91}
92
93impl<'a> TryFrom<&'a str> for JudgeLevel {
94    type Error = &'a str;
95    fn try_from(value: &'a str) -> core::result::Result<Self, Self::Error> {
96        value.parse::<i64>().map(Self::from).map_err(|_| value)
97    }
98}
99
100impl std::fmt::Display for JudgeLevel {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        match self {
103            Self::VeryHard => write!(f, "0"),
104            Self::Hard => write!(f, "1"),
105            Self::Normal => write!(f, "2"),
106            Self::Easy => write!(f, "3"),
107            Self::OtherInt(value) => write!(f, "{value}"),
108        }
109    }
110}
111
112pub(crate) const fn char_to_base62(ch: char) -> Option<u8> {
113    match ch {
114        '0'..='9' | 'A'..='Z' | 'a'..='z' => Some(ch as u32 as u8),
115        _ => None,
116    }
117}
118
119pub(crate) fn base62_to_byte(base62: u8) -> u8 {
120    match base62 {
121        b'0'..=b'9' => base62 - b'0',
122        b'A'..=b'Z' => base62 - b'A' + 10,
123        b'a'..=b'z' => base62 - b'a' + 36,
124        _ => unreachable!(),
125    }
126}
127
128/// An object id. Its meaning is determined by the channel belonged to.
129///
130/// The representation is 2 digits of ASCII characters.
131#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
132#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
133pub struct ObjId([u8; 2]);
134
135impl std::fmt::Debug for ObjId {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        f.debug_tuple("ObjId")
138            .field(&format!("{}{}", self.0[0] as char, self.0[1] as char))
139            .finish()
140    }
141}
142
143impl std::fmt::Display for ObjId {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        write!(f, "{}{}", self.0[0] as char, self.0[1] as char)
146    }
147}
148
149impl From<ObjId> for u16 {
150    fn from(value: ObjId) -> Self {
151        base62_to_byte(value.0[0]) as u16 * 62 + base62_to_byte(value.0[1]) as u16
152    }
153}
154
155impl From<ObjId> for u32 {
156    fn from(value: ObjId) -> Self {
157        Into::<u16>::into(value) as u32
158    }
159}
160
161impl From<ObjId> for u64 {
162    fn from(value: ObjId) -> Self {
163        Into::<u16>::into(value) as u64
164    }
165}
166
167impl ObjId {
168    /// Instances a special null id, which means the rest object.
169    #[must_use]
170    pub const fn null() -> Self {
171        Self([b'0', b'0'])
172    }
173
174    /// Returns whether the id is `00`.
175    #[must_use]
176    pub fn is_null(self) -> bool {
177        self.0 == [b'0', b'0']
178    }
179
180    /// Parses the object id from the string `value`.
181    ///
182    /// If `case_sensitive_obj_id` is true, then the object id considered as a case-sensitive. Otherwise, it will be all uppercase characters.
183    pub fn try_from(value: &str, case_sensitive_obj_id: bool) -> Result<Self> {
184        if value.len() != 2 {
185            return Err(ParseWarning::SyntaxError(format!(
186                "expected 2 digits as object id but found: {value}"
187            )));
188        }
189        let mut chars = value.bytes();
190        let [Some(ch1), Some(ch2), None] = [chars.next(), chars.next(), chars.next()] else {
191            return Err(ParseWarning::SyntaxError(format!(
192                "expected 2 digits as object id but found: {value}"
193            )));
194        };
195        if !(ch1.is_ascii_alphanumeric() && ch2.is_ascii_alphanumeric()) {
196            return Err(ParseWarning::SyntaxError(format!(
197                "expected alphanumeric characters as object id but found: {value}"
198            )));
199        }
200        if case_sensitive_obj_id {
201            Ok(Self([ch1, ch2]))
202        } else {
203            Ok(Self([ch1.to_ascii_uppercase(), ch2.to_ascii_uppercase()]))
204        }
205    }
206
207    /// Converts the object id into an `u16` value.
208    #[must_use]
209    pub fn as_u16(self) -> u16 {
210        self.into()
211    }
212
213    /// Converts the object id into an `u32` value.
214    #[must_use]
215    pub fn as_u32(self) -> u32 {
216        self.into()
217    }
218
219    /// Converts the object id into an `u64` value.
220    #[must_use]
221    pub fn as_u64(self) -> u64 {
222        self.into()
223    }
224
225    /// Converts the object id into 2 `char`s.
226    #[must_use]
227    pub fn into_chars(self) -> [char; 2] {
228        self.0.map(|c| c as char)
229    }
230
231    /// Makes the object id uppercase.
232    pub const fn make_uppercase(&mut self) {
233        self.0[0] = self.0[0].to_ascii_uppercase();
234        self.0[1] = self.0[1].to_ascii_uppercase();
235    }
236
237    /// Returns whether both characters are valid Base36 characters (0-9, A-Z).
238    #[must_use]
239    pub fn is_base36(self) -> bool {
240        self.0
241            .iter()
242            .all(|c| c.is_ascii_digit() || c.is_ascii_uppercase())
243    }
244
245    /// Returns whether both characters are valid Base62 characters (0-9, A-Z, a-z).
246    #[must_use]
247    pub fn is_base62(self) -> bool {
248        self.0
249            .iter()
250            .all(|c| c.is_ascii_digit() || c.is_ascii_uppercase() || c.is_ascii_lowercase())
251    }
252
253    /// Returns an iterator over all possible ObjId values, ordered by priority:
254    /// first all Base36 values (0-9, A-Z), then remaining Base62 values.
255    ///
256    /// Total: 3843 values (excluding null "00"), with first 1295 being Base36.
257    pub fn all_values() -> impl Iterator<Item = Self> {
258        const BASE36_CHARS: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
259        const BASE62_CHARS: &[u8] =
260            b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
261
262        // Generate all Base36 values first (1296 values: "00" to "ZZ")
263        let base36_values = (0..36usize).flat_map(move |first_idx| {
264            (0..36usize)
265                .map(move |second_idx| Self([BASE36_CHARS[first_idx], BASE36_CHARS[second_idx]]))
266        });
267
268        // Generate all Base62 values, then filter out Base36 ones and "00"
269        let remaining_values = (0..62usize).flat_map(move |first_idx| {
270            (0..62usize)
271                .map(move |second_idx| Self([BASE62_CHARS[first_idx], BASE62_CHARS[second_idx]]))
272                .filter(move |obj_id| {
273                    // Skip "00" and Base36 values (already yielded above)
274                    !obj_id.is_null() && !obj_id.is_base36() && obj_id.is_base62()
275                })
276        });
277
278        // Chain them: first Base36 (1296 values), then remaining (2548 values)
279        // Total: 1296 + 2548 = 3844 values
280        // Skip the first Base36 value ("00") to get 1295 Base36 + 2548 remaining = 3843 total
281        base36_values.skip(1).chain(remaining_values)
282    }
283}
284
285/// A play volume of the sound in the score. Defaults to 100.
286#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
287#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
288pub struct Volume {
289    /// A play volume percentage of the sound.
290    pub relative_percent: u8,
291}
292
293impl Default for Volume {
294    fn default() -> Self {
295        Self {
296            relative_percent: 100,
297        }
298    }
299}
300
301/// A POOR BGA display mode.
302#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
303#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
304pub enum PoorMode {
305    /// To hide the normal BGA and display the POOR BGA.
306    #[default]
307    Interrupt,
308    /// To overlap the POOR BGA onto the normal BGA.
309    Overlay,
310    /// Not to display the POOR BGA.
311    Hidden,
312}
313
314impl std::str::FromStr for PoorMode {
315    type Err = ParseWarning;
316
317    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
318        Ok(match s {
319            "0" => Self::Interrupt,
320            "1" => Self::Overlay,
321            "2" => Self::Hidden,
322            _ => {
323                return Err(ParseWarning::SyntaxError(
324                    "expected one of 0, 1 or 2".into(),
325                ));
326            }
327        })
328    }
329}
330
331impl PoorMode {
332    /// Converts an display type of Poor BGA into the corresponding string literal.
333    #[must_use]
334    pub const fn as_str(self) -> &'static str {
335        match self {
336            Self::Interrupt => "0",
337            Self::Overlay => "1",
338            Self::Hidden => "2",
339        }
340    }
341}
342
343/// A notation type about LN in the score. But you don't have to take care of how the notes are actually placed in.
344#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
345#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
346pub enum LnType {
347    /// The RDM type.
348    #[default]
349    Rdm,
350    /// The MGQ type.
351    Mgq,
352}
353
354/// Long Note Mode Type
355#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
356#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
357#[repr(u8)]
358pub enum LnMode {
359    /// Normal Long Note, no tail judge (LN)
360    #[default]
361    Ln = 1,
362    /// IIDX Classic Long Note, with tail judge (CN)
363    Cn = 2,
364    /// IIDX Hell Long Note, with tail judge. holding add gurge, un-holding lose gurge (HCN)
365    Hcn = 3,
366}
367
368impl From<LnMode> for u8 {
369    fn from(mode: LnMode) -> u8 {
370        match mode {
371            LnMode::Ln => 1,
372            LnMode::Cn => 2,
373            LnMode::Hcn => 3,
374        }
375    }
376}
377
378impl TryFrom<u8> for LnMode {
379    type Error = u8;
380    fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
381        Ok(match value {
382            1 => Self::Ln,
383            2 => Self::Cn,
384            3 => Self::Hcn,
385            _ => return Err(value),
386        })
387    }
388}
389
390/// Associates between object `K` and [`ObjId`] with memoization.
391/// It is useful to assign object ids for many objects with its equality.
392pub struct ObjIdManager<'a, K: ?Sized> {
393    value_to_id: HashMap<&'a K, ObjId>,
394    used_ids: HashSet<ObjId>,
395    unused_ids: VecDeque<ObjId>,
396}
397
398impl<'a, K> Default for ObjIdManager<'a, K>
399where
400    K: std::hash::Hash + Eq + ?Sized,
401{
402    fn default() -> Self {
403        Self::new()
404    }
405}
406
407impl<'a, K> ObjIdManager<'a, K>
408where
409    K: std::hash::Hash + Eq + ?Sized,
410{
411    /// Creates a new empty ObjIdManager.
412    #[must_use]
413    pub fn new() -> Self {
414        let unused_ids: VecDeque<ObjId> = ObjId::all_values().collect();
415
416        Self {
417            value_to_id: HashMap::new(),
418            used_ids: HashSet::new(),
419            unused_ids,
420        }
421    }
422
423    /// Creates a new ObjIdManager with iterator of assigned entries.
424    pub fn from_entries<I: IntoIterator<Item = (&'a K, ObjId)>>(iter: I) -> Self {
425        let mut value_to_id: HashMap<&'a K, ObjId> = HashMap::new();
426        let mut used_ids: HashSet<ObjId> = HashSet::new();
427
428        // Collect all entries first
429        let entries: Vec<_> = iter.into_iter().collect();
430
431        // Mark used IDs and build the mapping
432        for (key, assigned_id) in entries {
433            value_to_id.insert(key, assigned_id);
434            used_ids.insert(assigned_id);
435        }
436
437        let unused_ids: VecDeque<ObjId> = ObjId::all_values()
438            .filter(|id| !used_ids.contains(id))
439            .collect();
440
441        Self {
442            value_to_id,
443            used_ids,
444            unused_ids,
445        }
446    }
447
448    /// Returns whether the key is already assigned any id.
449    pub fn is_assigned(&self, key: &'a K) -> bool {
450        self.value_to_id.contains_key(key)
451    }
452
453    /// Gets or allocates an ObjId for a key without creating tokens.
454    pub fn get_or_new_id(&mut self, key: &'a K) -> Option<ObjId> {
455        if let Some(&id) = self.value_to_id.get(key) {
456            Some(id)
457        } else if let Some(new_id) = self.unused_ids.pop_front() {
458            self.used_ids.insert(new_id);
459            self.value_to_id.insert(key, new_id);
460            Some(new_id)
461        } else {
462            None
463        }
464    }
465
466    /// Get assigned ids as an iterator.
467    pub fn into_assigned_ids(self) -> impl Iterator<Item = ObjId> {
468        self.used_ids.into_iter()
469    }
470}
471
472#[cfg(test)]
473mod tests {
474    use super::*;
475
476    #[test]
477    fn test_base62() {
478        assert_eq!(char_to_base62('/'), None);
479        assert_eq!(char_to_base62('0'), Some(b'0'));
480        assert_eq!(char_to_base62('9'), Some(b'9'));
481        assert_eq!(char_to_base62(':'), None);
482        assert_eq!(char_to_base62('@'), None);
483        assert_eq!(char_to_base62('A'), Some(b'A'));
484        assert_eq!(char_to_base62('Z'), Some(b'Z'));
485        assert_eq!(char_to_base62('['), None);
486        assert_eq!(char_to_base62('`'), None);
487        assert_eq!(char_to_base62('a'), Some(b'a'));
488        assert_eq!(char_to_base62('z'), Some(b'z'));
489        assert_eq!(char_to_base62('{'), None);
490    }
491
492    #[test]
493    fn test_all_values() {
494        let all_values: Vec<ObjId> = ObjId::all_values().collect();
495
496        // Should have exactly 3843 values
497        assert_eq!(all_values.len(), 3843);
498
499        // First 1295 values should be Base36 (0-9, A-Z)
500        for (i, obj_id) in all_values.iter().enumerate() {
501            if i < 1295 {
502                assert!(
503                    obj_id.is_base36(),
504                    "Value at index {} should be Base36: {:?}",
505                    i,
506                    obj_id
507                );
508            } else {
509                assert!(
510                    !obj_id.is_base36(),
511                    "Value at index {} should NOT be Base36: {:?}",
512                    i,
513                    obj_id
514                );
515            }
516        }
517
518        // Verify some specific values
519        assert_eq!(all_values[0], ObjId::try_from("01", false).unwrap()); // First Base36 value
520        assert_eq!(all_values[1294], ObjId::try_from("ZZ", false).unwrap()); // Last Base36 value
521
522        // Verify that "00" is not included
523        assert!(!all_values.contains(&ObjId::null()));
524
525        // Verify that all values are unique
526        let mut unique_values = std::collections::HashSet::new();
527        for value in &all_values {
528            assert!(
529                unique_values.insert(*value),
530                "Duplicate value found: {:?}",
531                value
532            );
533        }
534    }
535}