feat: implement Random and Time built-in effects
Add Random and Time effects for random number generation and time-based operations. These effects can be used in any effectful code block. Random effect operations: - Random.int(min, max) - random integer in range [min, max] - Random.float() - random float in range [0.0, 1.0) - Random.bool() - random boolean Time effect operations: - Time.now() - current Unix timestamp in milliseconds - Time.sleep(ms) - sleep for specified milliseconds Changes: - Add rand crate dependency - Add Random and Time effect definitions to types.rs - Add effects to built-in effects list in typechecker - Implement effect handlers in interpreter - Add 4 new tests for Random and Time effects - Add examples/random.lux demonstrating usage Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
use crate::ast::*;
|
||||
use crate::diagnostics::{Diagnostic, Severity};
|
||||
use rand::Rng;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
@@ -2177,6 +2178,47 @@ impl Interpreter {
|
||||
("Reader", "ask") => {
|
||||
Ok(self.builtin_reader.borrow().clone())
|
||||
}
|
||||
("Random", "int") => {
|
||||
let min = match request.args.first() {
|
||||
Some(Value::Int(n)) => *n,
|
||||
_ => 0,
|
||||
};
|
||||
let max = match request.args.get(1) {
|
||||
Some(Value::Int(n)) => *n,
|
||||
_ => i64::MAX,
|
||||
};
|
||||
let mut rng = rand::thread_rng();
|
||||
let value = rng.gen_range(min..=max);
|
||||
Ok(Value::Int(value))
|
||||
}
|
||||
("Random", "float") => {
|
||||
let mut rng = rand::thread_rng();
|
||||
let value: f64 = rng.gen();
|
||||
Ok(Value::Float(value))
|
||||
}
|
||||
("Random", "bool") => {
|
||||
let mut rng = rand::thread_rng();
|
||||
let value: bool = rng.gen();
|
||||
Ok(Value::Bool(value))
|
||||
}
|
||||
("Time", "now") => {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
let duration = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default();
|
||||
let millis = duration.as_millis() as i64;
|
||||
Ok(Value::Int(millis))
|
||||
}
|
||||
("Time", "sleep") => {
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
let ms = match request.args.first() {
|
||||
Some(Value::Int(n)) => *n as u64,
|
||||
_ => 0,
|
||||
};
|
||||
thread::sleep(Duration::from_millis(ms));
|
||||
Ok(Value::Unit)
|
||||
}
|
||||
_ => Err(RuntimeError {
|
||||
message: format!(
|
||||
"Unhandled effect operation: {}.{}",
|
||||
|
||||
44
src/main.rs
44
src/main.rs
@@ -1248,6 +1248,50 @@ c")"#;
|
||||
assert_eq!(result, "5");
|
||||
assert_eq!(final_state, "6");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_random_int() {
|
||||
let source = r#"
|
||||
fn getRandomInt(): Int with {Random} = Random.int(1, 10)
|
||||
let result = run getRandomInt() with {}
|
||||
"#;
|
||||
let (result, _) = run_with_effects(source, Value::Unit, Value::Unit).unwrap();
|
||||
let num: i64 = result.parse().expect("Should be an integer");
|
||||
assert!(num >= 1 && num <= 10, "Random int should be in range 1-10, got {}", num);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_random_float() {
|
||||
let source = r#"
|
||||
fn getRandomFloat(): Float with {Random} = Random.float()
|
||||
let result = run getRandomFloat() with {}
|
||||
"#;
|
||||
let (result, _) = run_with_effects(source, Value::Unit, Value::Unit).unwrap();
|
||||
let num: f64 = result.parse().expect("Should be a float");
|
||||
assert!(num >= 0.0 && num < 1.0, "Random float should be in range [0, 1), got {}", num);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_random_bool() {
|
||||
let source = r#"
|
||||
fn getRandomBool(): Bool with {Random} = Random.bool()
|
||||
let result = run getRandomBool() with {}
|
||||
"#;
|
||||
let (result, _) = run_with_effects(source, Value::Unit, Value::Unit).unwrap();
|
||||
assert!(result == "true" || result == "false", "Random bool should be true or false, got {}", result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_now() {
|
||||
let source = r#"
|
||||
fn getTime(): Int with {Time} = Time.now()
|
||||
let result = run getTime() with {}
|
||||
"#;
|
||||
let (result, _) = run_with_effects(source, Value::Unit, Value::Unit).unwrap();
|
||||
let timestamp: i64 = result.parse().expect("Should be a timestamp");
|
||||
// Timestamp should be a reasonable Unix time in milliseconds (after 2020)
|
||||
assert!(timestamp > 1577836800000, "Timestamp should be after 2020");
|
||||
}
|
||||
}
|
||||
|
||||
// Diagnostic rendering tests
|
||||
|
||||
@@ -935,7 +935,7 @@ impl TypeChecker {
|
||||
}
|
||||
|
||||
// Built-in effects are always available
|
||||
let builtin_effects = ["Console", "Fail", "State", "Reader"];
|
||||
let builtin_effects = ["Console", "Fail", "State", "Reader", "Random", "Time"];
|
||||
let is_builtin = builtin_effects.contains(&effect.name.as_str());
|
||||
|
||||
// Track this effect for inference
|
||||
@@ -1440,7 +1440,7 @@ impl TypeChecker {
|
||||
|
||||
// Built-in effects are always available in run blocks (they have runtime implementations)
|
||||
let builtin_effects: EffectSet =
|
||||
EffectSet::from_iter(["Console", "Fail", "State", "Reader"].iter().map(|s| s.to_string()));
|
||||
EffectSet::from_iter(["Console", "Fail", "State", "Reader", "Random", "Time"].iter().map(|s| s.to_string()));
|
||||
|
||||
// Extend current effects with handled ones and built-in effects
|
||||
let combined = self.current_effects.union(&handled_effects).union(&builtin_effects);
|
||||
|
||||
50
src/types.rs
50
src/types.rs
@@ -802,6 +802,56 @@ impl TypeEnv {
|
||||
},
|
||||
);
|
||||
|
||||
// Add Random effect
|
||||
env.effects.insert(
|
||||
"Random".to_string(),
|
||||
EffectDef {
|
||||
name: "Random".to_string(),
|
||||
type_params: Vec::new(),
|
||||
operations: vec![
|
||||
EffectOpDef {
|
||||
name: "int".to_string(),
|
||||
params: vec![
|
||||
("min".to_string(), Type::Int),
|
||||
("max".to_string(), Type::Int),
|
||||
],
|
||||
return_type: Type::Int,
|
||||
},
|
||||
EffectOpDef {
|
||||
name: "float".to_string(),
|
||||
params: Vec::new(),
|
||||
return_type: Type::Float,
|
||||
},
|
||||
EffectOpDef {
|
||||
name: "bool".to_string(),
|
||||
params: Vec::new(),
|
||||
return_type: Type::Bool,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
// Add Time effect
|
||||
env.effects.insert(
|
||||
"Time".to_string(),
|
||||
EffectDef {
|
||||
name: "Time".to_string(),
|
||||
type_params: Vec::new(),
|
||||
operations: vec![
|
||||
EffectOpDef {
|
||||
name: "now".to_string(),
|
||||
params: Vec::new(),
|
||||
return_type: Type::Int, // Unix timestamp in milliseconds
|
||||
},
|
||||
EffectOpDef {
|
||||
name: "sleep".to_string(),
|
||||
params: vec![("ms".to_string(), Type::Int)],
|
||||
return_type: Type::Unit,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
// Add Some and Ok, Err constructors
|
||||
// Some : fn(a) -> Option<a>
|
||||
let a = Type::var();
|
||||
|
||||
Reference in New Issue
Block a user