parent
24958d4896
commit
2111fcac62
|
|
@ -1671,6 +1671,40 @@ pub struct TableWrite {
|
||||||
const MAX_ITERATIONS: usize = 10_000;
|
const MAX_ITERATIONS: usize = 10_000;
|
||||||
const MAX_CALL_DEPTH: u32 = 256;
|
const MAX_CALL_DEPTH: u32 = 256;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static RNG_STATE: std::cell::Cell<u64> = std::cell::Cell::new(seed_from_time());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn seed_from_time() -> u64 {
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
let nanos = SystemTime::now().duration_since(UNIX_EPOCH)
|
||||||
|
.map(|d| d.as_nanos() as u64).unwrap_or(0x9E3779B97F4A7C15);
|
||||||
|
nanos.wrapping_mul(0x9E3779B97F4A7C15) ^ 0xDEADBEEFCAFEBABE
|
||||||
|
}
|
||||||
|
|
||||||
|
/// xorshift64* — pulls the next u64 from the thread-local PRNG.
|
||||||
|
fn rng_next_u64() -> u64 {
|
||||||
|
RNG_STATE.with(|s| {
|
||||||
|
let mut x = s.get();
|
||||||
|
if x == 0 { x = 0x9E3779B97F4A7C15; }
|
||||||
|
x ^= x >> 12;
|
||||||
|
x ^= x << 25;
|
||||||
|
x ^= x >> 27;
|
||||||
|
s.set(x);
|
||||||
|
x.wrapping_mul(0x2545F4914F6CDD1D)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// uniform f64 in [0, 1).
|
||||||
|
fn rng_next_unit() -> f64 {
|
||||||
|
(rng_next_u64() >> 11) as f64 / (1u64 << 53) as f64
|
||||||
|
}
|
||||||
|
|
||||||
|
/// seeds the thread-local PRNG to a deterministic value.
|
||||||
|
pub fn seed_rng(seed: u64) {
|
||||||
|
RNG_STATE.with(|s| s.set(if seed == 0 { 0x9E3779B97F4A7C15 } else { seed }));
|
||||||
|
}
|
||||||
|
|
||||||
impl Interpreter {
|
impl Interpreter {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Interpreter {
|
Interpreter {
|
||||||
|
|
@ -2403,6 +2437,52 @@ impl Interpreter {
|
||||||
};
|
};
|
||||||
return Ok(retag_spice(Value::Number(result), unit));
|
return Ok(retag_spice(Value::Number(result), unit));
|
||||||
}
|
}
|
||||||
|
"rand" => {
|
||||||
|
match args.len() {
|
||||||
|
0 => return Ok(Value::Number(rng_next_unit())),
|
||||||
|
1 => {
|
||||||
|
let v = self.eval_expr(&args[0], depth)?;
|
||||||
|
match v {
|
||||||
|
Value::Number(n) => {
|
||||||
|
if n <= 0.0 { return Err("rand(n) expects n > 0".into()); }
|
||||||
|
return Ok(Value::Number((rng_next_unit() * n).floor()));
|
||||||
|
}
|
||||||
|
Value::Array(a) => {
|
||||||
|
if a.is_empty() { return Err("rand(array) on empty array".into()); }
|
||||||
|
let idx = (rng_next_unit() * a.len() as f64) as usize;
|
||||||
|
return Ok(a[idx.min(a.len() - 1)].clone());
|
||||||
|
}
|
||||||
|
_ => return Err("rand(x) expects a number or array".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
let lo = match self.eval_expr(&args[0], depth)? {
|
||||||
|
Value::Number(n) => n,
|
||||||
|
_ => return Err("rand(lo, hi) expects numbers".into()),
|
||||||
|
};
|
||||||
|
let hi = match self.eval_expr(&args[1], depth)? {
|
||||||
|
Value::Number(n) => n,
|
||||||
|
_ => return Err("rand(lo, hi) expects numbers".into()),
|
||||||
|
};
|
||||||
|
if hi <= lo { return Err("rand(lo, hi) requires hi > lo".into()); }
|
||||||
|
return Ok(Value::Number(lo + rng_next_unit() * (hi - lo)));
|
||||||
|
}
|
||||||
|
_ => return Err("rand() takes 0, 1, or 2 arguments".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"seed" => {
|
||||||
|
if args.len() != 1 {
|
||||||
|
return Err("seed() expects 1 argument".into());
|
||||||
|
}
|
||||||
|
let v = self.eval_expr(&args[0], depth)?;
|
||||||
|
match v {
|
||||||
|
Value::Number(n) => {
|
||||||
|
seed_rng(n.to_bits());
|
||||||
|
return Ok(Value::Void);
|
||||||
|
}
|
||||||
|
_ => return Err("seed() expects a number".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
"floor" | "ceil" | "round" => {
|
"floor" | "ceil" | "round" => {
|
||||||
if args.is_empty() || args.len() > 2 {
|
if args.is_empty() || args.len() > 2 {
|
||||||
return Err(format!("{}() expects 1 or 2 arguments", name));
|
return Err(format!("{}() expects 1 or 2 arguments", name));
|
||||||
|
|
@ -6084,6 +6164,80 @@ fn find(arr, target) {
|
||||||
assert!(matches!(v, Value::Number(n) if n == 3.0));
|
assert!(matches!(v, Value::Number(n) if n == 3.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rand_unit_range() {
|
||||||
|
let mut i = Interpreter::new();
|
||||||
|
i.exec_line("seed(42)").unwrap();
|
||||||
|
for _ in 0..20 {
|
||||||
|
let v = i.eval_expr_str("rand()").unwrap();
|
||||||
|
match v {
|
||||||
|
Value::Number(n) => assert!(n >= 0.0 && n < 1.0, "rand() = {}", n),
|
||||||
|
_ => panic!("rand() should return Number"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rand_integer_bound() {
|
||||||
|
let mut i = Interpreter::new();
|
||||||
|
i.exec_line("seed(7)").unwrap();
|
||||||
|
for _ in 0..30 {
|
||||||
|
let v = i.eval_expr_str("rand(10)").unwrap();
|
||||||
|
match v {
|
||||||
|
Value::Number(n) => assert!(n >= 0.0 && n < 10.0 && n == n.trunc()),
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rand_lo_hi_range() {
|
||||||
|
let mut i = Interpreter::new();
|
||||||
|
i.exec_line("seed(99)").unwrap();
|
||||||
|
for _ in 0..30 {
|
||||||
|
let v = i.eval_expr_str("rand(5, 8)").unwrap();
|
||||||
|
match v {
|
||||||
|
Value::Number(n) => assert!(n >= 5.0 && n < 8.0),
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rand_picks_from_array() {
|
||||||
|
let mut i = Interpreter::new();
|
||||||
|
i.exec_line("seed(1)").unwrap();
|
||||||
|
let v = i.eval_expr_str("rand([10, 20, 30])").unwrap();
|
||||||
|
match v {
|
||||||
|
Value::Number(n) => assert!(n == 10.0 || n == 20.0 || n == 30.0),
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rand_seed_is_deterministic() {
|
||||||
|
let mut a = Interpreter::new();
|
||||||
|
a.exec_line("seed(12345)").unwrap();
|
||||||
|
let va = a.eval_expr_str("rand()").unwrap();
|
||||||
|
a.exec_line("seed(12345)").unwrap();
|
||||||
|
let vb = a.eval_expr_str("rand()").unwrap();
|
||||||
|
match (va, vb) {
|
||||||
|
(Value::Number(x), Value::Number(y)) => assert_eq!(x.to_bits(), y.to_bits()),
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rand_method_call_on_array() {
|
||||||
|
let mut i = Interpreter::new();
|
||||||
|
i.exec_line("seed(3)").unwrap();
|
||||||
|
let v = i.eval_expr_str("[1, 2, 3, 4, 5].rand()").unwrap();
|
||||||
|
match v {
|
||||||
|
Value::Number(n) => assert!((1.0..=5.0).contains(&n)),
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn for_loop_does_not_leak_var() {
|
fn for_loop_does_not_leak_var() {
|
||||||
let mut i = Interpreter::new();
|
let mut i = Interpreter::new();
|
||||||
|
|
|
||||||
|
|
@ -728,6 +728,8 @@ fn is_cordial_builtin(w: &str) -> bool {
|
||||||
| "ring" | "iter" | "peek" | "history"
|
| "ring" | "iter" | "peek" | "history"
|
||||||
// aggregates
|
// aggregates
|
||||||
| "sum" | "avg" | "min" | "max" | "count" | "std_devp" | "std_devs"
|
| "sum" | "avg" | "min" | "max" | "count" | "std_devp" | "std_devs"
|
||||||
|
// random
|
||||||
|
| "rand" | "seed"
|
||||||
// constants
|
// constants
|
||||||
| "pi"
|
| "pi"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue