bms_rs/bms/
rng.rs

1//! Random number generation for BMS control flow parsing.
2//!
3//! This module provides the [`Rng`] trait and implementations for generating random numbers
4//! used in BMS control flow constructs like `#RANDOM` and `#SWITCH` blocks.
5//!
6//! # Overview
7//!
8//! The random number generation is essential for:
9//!
10//! - **Random Blocks**: Selecting which `#IF` branch to execute based on random values
11//! - **Switch Blocks**: Determining which `#CASE` branch to execute
12//! - **Testing**: Providing deterministic behavior for reproducible test results
13//!
14//! # Implementations
15//!
16//! ## [`RngMock`]
17//!
18//! A deterministic mock implementation for testing that returns predefined values in rotation:
19//!
20//! ## [`RandRng`]
21//!
22//! A production-ready implementation using the [`rand`] crate for true random number generation:
23//!
24//! [`rand`]: https://crates.io/crates/rand
25
26use core::ops::RangeInclusive;
27
28use num::{BigUint, ToPrimitive};
29
30/// A random number generator for BMS control flow parsing.
31///
32/// This trait defines the interface for generating random numbers used in BMS control flow
33/// constructs. Implementations should generate numbers within the specified range.
34///
35/// # Contract
36///
37/// - The generated number must be within the specified `range` (inclusive)
38/// - Returning a number outside the range may cause undefined behavior in the parser
39/// - The implementation should be deterministic for testing purposes when needed
40pub trait Rng {
41    /// Generates a random integer within the specified `range`.
42    ///
43    /// # Examples
44    ///
45    /// ```rust
46    /// use bms_rs::bms::rng::{Rng, RngMock};
47    /// use num::BigUint;
48    ///
49    /// let mut rng = RngMock([BigUint::from(5u64)]);
50    /// let result = rng.generate(BigUint::from(1u64)..=BigUint::from(10u64));
51    /// assert_eq!(result, BigUint::from(5u64));
52    /// ```
53    fn generate(&mut self, range: RangeInclusive<BigUint>) -> BigUint;
54}
55
56impl<T: Rng + ?Sized> Rng for Box<T> {
57    fn generate(&mut self, range: RangeInclusive<BigUint>) -> BigUint {
58        T::generate(self, range)
59    }
60}
61
62/// A deterministic mock random number generator for testing.
63///
64/// This implementation returns values from a predefined array in rotation.
65/// It's useful for testing BMS control flow parsing with predictable results.
66///
67/// # Examples
68///
69/// ```rust
70/// use bms_rs::bms::rng::{Rng, RngMock};
71/// use num::BigUint;
72///
73/// let mut rng = RngMock([BigUint::from(1u64), BigUint::from(2u64)]);
74///
75/// // Returns values in rotation: 1, 2, 1, 2, ...
76/// assert_eq!(rng.generate(BigUint::from(0u64)..=BigUint::from(10u64)), BigUint::from(1u64));
77/// assert_eq!(rng.generate(BigUint::from(0u64)..=BigUint::from(10u64)), BigUint::from(2u64));
78/// ```
79#[derive(Debug, Clone, PartialEq, Eq, Hash)]
80pub struct RngMock<const N: usize>(pub [BigUint; N]);
81
82impl<const N: usize> Rng for RngMock<N> {
83    fn generate(&mut self, _range: RangeInclusive<BigUint>) -> BigUint {
84        self.0.rotate_left(1);
85        self.0[N - 1].clone()
86    }
87}
88
89/// A production-ready random number generator using the [`rand`] crate.
90///
91/// This implementation provides true random number generation for production use.
92/// It wraps any type implementing [`rand::RngCore`] and generates numbers within
93/// the specified range using rejection sampling.
94///
95/// # Examples
96///
97/// ```rust
98/// # #[cfg(feature = "rand")]
99/// use bms_rs::bms::rng::{Rng, RandRng};
100/// # #[cfg(feature = "rand")]
101/// use rand::{rngs::StdRng, SeedableRng};
102/// # #[cfg(feature = "rand")]
103/// use num::BigUint;
104///
105/// # #[cfg(feature = "rand")]
106/// let mut rng = RandRng(StdRng::seed_from_u64(42));
107/// # #[cfg(feature = "rand")]
108/// let n = rng.generate(BigUint::from(1u64)..=BigUint::from(10u64));
109/// # #[cfg(feature = "rand")]
110/// assert!(n >= BigUint::from(1u64) && n <= BigUint::from(10u64));
111/// ```
112///
113/// [`rand`]: https://crates.io/crates/rand
114#[cfg(feature = "rand")]
115pub struct RandRng<R>(pub R);
116
117#[cfg(feature = "rand")]
118impl<R: rand::RngCore> Rng for RandRng<R> {
119    fn generate(&mut self, range: RangeInclusive<BigUint>) -> BigUint {
120        use num::One;
121
122        let (start, end) = (range.start(), range.end());
123        let width = end - start + BigUint::one();
124        let width_bits = width.bits() as usize;
125
126        loop {
127            let mut bytes = vec![0u8; width_bits.div_ceil(8)];
128            self.0.fill_bytes(&mut bytes);
129            let mut n = BigUint::from_bytes_le(&bytes);
130            if n < width {
131                n += start;
132                return n;
133            }
134        }
135    }
136}
137
138/// A random number generator based on Java's `java.util.Random`.
139///
140/// # Deprecation Notice
141///
142/// This struct is not recommended for external use. For BMS control flow parsing,
143/// prefer using other implementations of [`Rng`] trait, e.g. [`RandRng`].
144#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
145pub struct JavaRandom {
146    seed: u64,
147}
148
149impl JavaRandom {
150    const MULT: u64 = 0x5_DEEC_E66D;
151    const ADD: u64 = 0xB;
152
153    /// Create a new [`JavaRandom`] with the given seed.
154    #[must_use]
155    pub const fn new(seed: i64) -> Self {
156        let s = (seed as u64) ^ Self::MULT;
157        Self {
158            seed: s & ((1u64 << 48) - 1),
159        }
160    }
161
162    /// Java's `next(int bits)` method
163    const fn next(&mut self, bits: i32) -> i32 {
164        self.seed =
165            (self.seed.wrapping_mul(Self::MULT).wrapping_add(Self::ADD)) & ((1u64 << 48) - 1);
166        ((self.seed >> (48 - bits)) & ((1u64 << bits) - 1)) as i32
167    }
168
169    /// Java's `nextInt()` method - returns any int value
170    pub const fn next_int(&mut self) -> i32 {
171        self.next(32)
172    }
173
174    /// Java's `nextInt(int bound)` method
175    pub fn next_int_bound(&mut self, bound: i32) -> i32 {
176        assert!(bound > 0, "bound must be positive");
177
178        let m = bound - 1;
179        if (bound & m) == 0 {
180            // i.e., bound is a power of 2
181            ((bound as i64 * self.next(31) as i64) >> 31) as i32
182        } else {
183            loop {
184                let bits = self.next(31);
185                let val = bits % bound;
186                if bits - val + m >= 0 {
187                    return val;
188                }
189            }
190        }
191    }
192}
193
194impl Default for JavaRandom {
195    fn default() -> Self {
196        Self::new(0)
197    }
198}
199
200impl Rng for JavaRandom {
201    fn generate(&mut self, range: RangeInclusive<BigUint>) -> BigUint {
202        use num::One;
203
204        let (start, end) = (range.start(), range.end());
205        let width = end - start + BigUint::one();
206
207        // If the range is small enough to fit in i32, use the efficient next_int_bound method
208        if let (Some(_start_i32), Some(width_i32)) =
209            (start.to_i32(), width.to_i32().filter(|&w| w > 0))
210        {
211            let offset = self.next_int_bound(width_i32);
212            return start + BigUint::from(offset as u32);
213        }
214
215        // For larger ranges, we need to generate multiple random values and combine them
216        // This is a simplified approach for larger BigUint ranges
217        let width_bits = width.bits() as usize;
218        let mut result = BigUint::ZERO;
219
220        // Generate random bits until we have enough to cover the range
221        let mut bits_generated = 0;
222        while bits_generated < width_bits {
223            let random_int = self.next_int();
224            let random_bits = random_int.unsigned_abs();
225
226            // Add these bits to our result
227            let shift_amount = bits_generated.min(32);
228            result |= BigUint::from(random_bits) << shift_amount;
229            bits_generated += 32;
230
231            // If we've exceeded the range, we need to reduce it
232            if result >= width {
233                result %= width.clone();
234                break;
235            }
236        }
237
238        // Ensure result is within width
239        if result >= width {
240            result %= width;
241        }
242
243        start + result
244    }
245}
246
247#[cfg(all(test, feature = "rand"))]
248mod tests {
249    use super::*;
250    use num::BigUint;
251    use rand::{SeedableRng, rngs::StdRng};
252
253    #[test]
254    fn test_rand_rng_big_range() {
255        let start = BigUint::parse_bytes(b"10000000000000000000000000000000000000000000000000", 10)
256            .unwrap();
257        let end = BigUint::parse_bytes(b"10000000000000000000000000000000000000000000000099", 10)
258            .unwrap();
259        let mut rng = RandRng(StdRng::seed_from_u64(42));
260        let range = start.clone()..=end.clone();
261        let n1 = rng.generate(range.clone());
262        let n2 = rng.generate(range.clone());
263        let n3 = rng.generate(range);
264        assert!(n1 >= start && n1 <= end, "n1 out of range");
265        assert!(n2 >= start && n2 <= end, "n2 out of range");
266        assert!(n3 >= start && n3 <= end, "n3 out of range");
267        assert!(
268            n1 != n2 && n1 != n3 && n2 != n3,
269            "random numbers are not unique"
270        );
271    }
272
273    #[test]
274    fn test_java_random_consistency() {
275        // Test with seed 123456789
276        let mut rng = JavaRandom::new(123456789);
277
278        // Test nextInt() method (returns any int value)
279        println!("First nextInt(): {}", rng.next_int());
280        println!("Second nextInt(): {}", rng.next_int());
281        println!("Third nextInt(): {}", rng.next_int());
282
283        // Test nextInt(bound) method
284        let mut rng2 = JavaRandom::new(123456789);
285        println!("First nextInt(100): {}", rng2.next_int_bound(100));
286        println!("Second nextInt(100): {}", rng2.next_int_bound(100));
287        println!("Third nextInt(100): {}", rng2.next_int_bound(100));
288
289        // Basic functionality test - should not panic
290        assert!(rng2.next_int_bound(100) >= 0 && rng2.next_int_bound(100) < 100);
291    }
292}