feat: add Ref cells for mutable state (Ref.new, Ref.get, Ref.set, Ref.update)
Implements WISH-013 mutable state primitives. Ref<T> is a mutable container using existing module call syntax. Supported across interpreter, JS, and C backends. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -144,6 +144,12 @@ pub enum BuiltinFn {
|
||||
MapFromList,
|
||||
MapToList,
|
||||
MapMerge,
|
||||
|
||||
// Ref operations
|
||||
RefNew,
|
||||
RefGet,
|
||||
RefSet,
|
||||
RefUpdate,
|
||||
}
|
||||
|
||||
/// Runtime value
|
||||
@@ -181,6 +187,8 @@ pub enum Value {
|
||||
name: String,
|
||||
arity: usize,
|
||||
},
|
||||
/// Mutable reference cell
|
||||
Ref(Rc<RefCell<Value>>),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
@@ -203,6 +211,7 @@ impl Value {
|
||||
Value::Versioned { .. } => "Versioned",
|
||||
Value::Json(_) => "Json",
|
||||
Value::ExternFn { .. } => "ExternFn",
|
||||
Value::Ref(_) => "Ref",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,6 +267,7 @@ impl Value {
|
||||
t1 == t2 && v1 == v2 && Value::values_equal(val1, val2)
|
||||
}
|
||||
(Value::Json(j1), Value::Json(j2)) => j1 == j2,
|
||||
(Value::Ref(r1), Value::Ref(r2)) => Rc::ptr_eq(r1, r2),
|
||||
// Functions and handlers cannot be compared for equality
|
||||
_ => false,
|
||||
}
|
||||
@@ -414,6 +424,7 @@ impl fmt::Display for Value {
|
||||
}
|
||||
Value::Json(json) => write!(f, "{}", json),
|
||||
Value::ExternFn { name, .. } => write!(f, "<extern fn {}>", name),
|
||||
Value::Ref(cell) => write!(f, "<ref: {}>", cell.borrow()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1202,6 +1213,15 @@ impl Interpreter {
|
||||
("merge".to_string(), Value::Builtin(BuiltinFn::MapMerge)),
|
||||
]));
|
||||
env.define("Map", map_module);
|
||||
|
||||
// Ref module
|
||||
let ref_module = Value::Record(HashMap::from([
|
||||
("new".to_string(), Value::Builtin(BuiltinFn::RefNew)),
|
||||
("get".to_string(), Value::Builtin(BuiltinFn::RefGet)),
|
||||
("set".to_string(), Value::Builtin(BuiltinFn::RefSet)),
|
||||
("update".to_string(), Value::Builtin(BuiltinFn::RefUpdate)),
|
||||
]));
|
||||
env.define("Ref", ref_module);
|
||||
}
|
||||
|
||||
/// Execute a program
|
||||
@@ -3440,6 +3460,56 @@ impl Interpreter {
|
||||
}
|
||||
Ok(EvalResult::Value(Value::Map(map1)))
|
||||
}
|
||||
|
||||
BuiltinFn::RefNew => {
|
||||
if args.len() != 1 {
|
||||
return Err(err("Ref.new requires 1 argument"));
|
||||
}
|
||||
Ok(EvalResult::Value(Value::Ref(Rc::new(RefCell::new(args.into_iter().next().unwrap())))))
|
||||
}
|
||||
|
||||
BuiltinFn::RefGet => {
|
||||
if args.len() != 1 {
|
||||
return Err(err("Ref.get requires 1 argument"));
|
||||
}
|
||||
match &args[0] {
|
||||
Value::Ref(cell) => Ok(EvalResult::Value(cell.borrow().clone())),
|
||||
v => Err(err(&format!("Ref.get expects Ref, got {}", v.type_name()))),
|
||||
}
|
||||
}
|
||||
|
||||
BuiltinFn::RefSet => {
|
||||
if args.len() != 2 {
|
||||
return Err(err("Ref.set requires 2 arguments: ref, value"));
|
||||
}
|
||||
match &args[0] {
|
||||
Value::Ref(cell) => {
|
||||
*cell.borrow_mut() = args[1].clone();
|
||||
Ok(EvalResult::Value(Value::Unit))
|
||||
}
|
||||
v => Err(err(&format!("Ref.set expects Ref as first argument, got {}", v.type_name()))),
|
||||
}
|
||||
}
|
||||
|
||||
BuiltinFn::RefUpdate => {
|
||||
if args.len() != 2 {
|
||||
return Err(err("Ref.update requires 2 arguments: ref, fn"));
|
||||
}
|
||||
match &args[0] {
|
||||
Value::Ref(cell) => {
|
||||
let old = cell.borrow().clone();
|
||||
let result = self.eval_call(args[1].clone(), vec![old], span)?;
|
||||
match result {
|
||||
EvalResult::Value(new_val) => {
|
||||
*cell.borrow_mut() = new_val;
|
||||
}
|
||||
_ => return Err(err("Ref.update callback must return a value")),
|
||||
}
|
||||
Ok(EvalResult::Value(Value::Unit))
|
||||
}
|
||||
v => Err(err(&format!("Ref.update expects Ref as first argument, got {}", v.type_name()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user