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}