Compare commits
1 Commits
v0.1.11
...
694e4ec999
| Author | SHA1 | Date | |
|---|---|---|---|
| 694e4ec999 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -776,7 +776,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lux"
|
||||
version = "0.1.9"
|
||||
version = "0.1.11"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"lsp-server",
|
||||
|
||||
@@ -3285,6 +3285,9 @@ impl CBackend {
|
||||
if module_name.name == "Map" {
|
||||
return self.emit_map_operation(&field.name, args);
|
||||
}
|
||||
if module_name.name == "Ref" {
|
||||
return self.emit_ref_operation(&field.name, args);
|
||||
}
|
||||
// Int module
|
||||
if module_name.name == "Int" && field.name == "toString" {
|
||||
let arg = self.emit_expr(&args[0])?;
|
||||
@@ -3701,6 +3704,11 @@ impl CBackend {
|
||||
return self.emit_map_operation(&operation.name, args);
|
||||
}
|
||||
|
||||
// Ref module
|
||||
if effect.name == "Ref" {
|
||||
return self.emit_ref_operation(&operation.name, args);
|
||||
}
|
||||
|
||||
// Built-in Console effect
|
||||
if effect.name == "Console" {
|
||||
if operation.name == "print" {
|
||||
@@ -5185,6 +5193,42 @@ impl CBackend {
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_ref_operation(&mut self, op: &str, args: &[Expr]) -> Result<String, CGenError> {
|
||||
match op {
|
||||
"new" => {
|
||||
let val = self.emit_expr(&args[0])?;
|
||||
let boxed = self.box_value(&val, None);
|
||||
let temp = format!("_ref_new_{}", self.fresh_name());
|
||||
self.writeln(&format!("void** {} = (void**)malloc(sizeof(void*));", temp));
|
||||
self.writeln(&format!("*{} = {};", temp, boxed));
|
||||
Ok(temp)
|
||||
}
|
||||
"get" => {
|
||||
let r = self.emit_expr(&args[0])?;
|
||||
Ok(format!("(*({})) /* Ref.get */", r))
|
||||
}
|
||||
"set" => {
|
||||
let r = self.emit_expr(&args[0])?;
|
||||
let val = self.emit_expr(&args[1])?;
|
||||
let boxed = self.box_value(&val, None);
|
||||
self.writeln(&format!("*{} = {};", r, boxed));
|
||||
Ok("0 /* Unit */".to_string())
|
||||
}
|
||||
"update" => {
|
||||
let r = self.emit_expr(&args[0])?;
|
||||
let f = self.emit_expr(&args[1])?;
|
||||
let temp = format!("_ref_upd_{}", self.fresh_name());
|
||||
self.writeln(&format!("void* {} = ((void*(*)(void*)){})(*({}));", temp, f, r));
|
||||
self.writeln(&format!("*{} = {};", r, temp));
|
||||
Ok("0 /* Unit */".to_string())
|
||||
}
|
||||
_ => Err(CGenError {
|
||||
message: format!("Unsupported Ref operation: {}", op),
|
||||
span: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_expr_with_substitution(&mut self, expr: &Expr, from: &str, to: &str) -> Result<String, CGenError> {
|
||||
// Simple substitution - in a real implementation, this would be more sophisticated
|
||||
match expr {
|
||||
|
||||
@@ -1242,6 +1242,9 @@ impl JsBackend {
|
||||
if module_name.name == "Map" {
|
||||
return self.emit_map_operation(&field.name, args);
|
||||
}
|
||||
if module_name.name == "Ref" {
|
||||
return self.emit_ref_operation(&field.name, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1388,6 +1391,11 @@ impl JsBackend {
|
||||
return self.emit_map_operation(&operation.name, args);
|
||||
}
|
||||
|
||||
// Special case: Ref module operations (not an effect)
|
||||
if effect.name == "Ref" {
|
||||
return self.emit_ref_operation(&operation.name, args);
|
||||
}
|
||||
|
||||
// Special case: Html module operations (not an effect)
|
||||
if effect.name == "Html" {
|
||||
return self.emit_html_operation(&operation.name, args);
|
||||
@@ -2486,6 +2494,37 @@ impl JsBackend {
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_ref_operation(
|
||||
&mut self,
|
||||
operation: &str,
|
||||
args: &[Expr],
|
||||
) -> Result<String, JsGenError> {
|
||||
match operation {
|
||||
"new" => {
|
||||
let val = self.emit_expr(&args[0])?;
|
||||
Ok(format!("({{value: {}}})", val))
|
||||
}
|
||||
"get" => {
|
||||
let r = self.emit_expr(&args[0])?;
|
||||
Ok(format!("({}.value)", r))
|
||||
}
|
||||
"set" => {
|
||||
let r = self.emit_expr(&args[0])?;
|
||||
let val = self.emit_expr(&args[1])?;
|
||||
Ok(format!("({}.value = {}, undefined)", r, val))
|
||||
}
|
||||
"update" => {
|
||||
let r = self.emit_expr(&args[0])?;
|
||||
let f = self.emit_expr(&args[1])?;
|
||||
Ok(format!("({0}.value = {1}({0}.value), undefined)", r, f))
|
||||
}
|
||||
_ => Err(JsGenError {
|
||||
message: format!("Unknown Ref operation: {}", operation),
|
||||
span: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit Html module operations for type-safe HTML construction
|
||||
fn emit_html_operation(
|
||||
&mut self,
|
||||
|
||||
@@ -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()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
52
src/main.rs
52
src/main.rs
@@ -5855,6 +5855,58 @@ c")"#;
|
||||
assert_eq!(eval(source).unwrap(), "Some(30)");
|
||||
}
|
||||
|
||||
// Ref cell tests
|
||||
#[test]
|
||||
fn test_ref_new_and_get() {
|
||||
let source = r#"
|
||||
let r = Ref.new(42)
|
||||
let result = Ref.get(r)
|
||||
"#;
|
||||
assert_eq!(eval(source).unwrap(), "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ref_set() {
|
||||
let source = r#"
|
||||
let r = Ref.new(0)
|
||||
let _ = Ref.set(r, 10)
|
||||
let result = Ref.get(r)
|
||||
"#;
|
||||
assert_eq!(eval(source).unwrap(), "10");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ref_update() {
|
||||
let source = r#"
|
||||
let r = Ref.new(5)
|
||||
let _ = Ref.update(r, fn(n) => n + 1)
|
||||
let result = Ref.get(r)
|
||||
"#;
|
||||
assert_eq!(eval(source).unwrap(), "6");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ref_multiple_updates() {
|
||||
let source = r#"
|
||||
let counter = Ref.new(0)
|
||||
let _ = Ref.set(counter, 1)
|
||||
let _ = Ref.update(counter, fn(n) => n * 10)
|
||||
let _ = Ref.set(counter, Ref.get(counter) + 5)
|
||||
let result = Ref.get(counter)
|
||||
"#;
|
||||
assert_eq!(eval(source).unwrap(), "15");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ref_with_string() {
|
||||
let source = r#"
|
||||
let r = Ref.new("hello")
|
||||
let _ = Ref.set(r, "world")
|
||||
let result = Ref.get(r)
|
||||
"#;
|
||||
assert_eq!(eval(source).unwrap(), "\"world\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_copy() {
|
||||
use std::io::Write;
|
||||
|
||||
@@ -3026,6 +3026,9 @@ impl TypeChecker {
|
||||
"Map" if resolved_args.len() == 2 => {
|
||||
return Type::Map(Box::new(resolved_args[0].clone()), Box::new(resolved_args[1].clone()));
|
||||
}
|
||||
"Ref" if resolved_args.len() == 1 => {
|
||||
return Type::Ref(Box::new(resolved_args[0].clone()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
40
src/types.rs
40
src/types.rs
@@ -49,6 +49,8 @@ pub enum Type {
|
||||
Option(Box<Type>),
|
||||
/// Map type (sugar for App(Map, [K, V]))
|
||||
Map(Box<Type>, Box<Type>),
|
||||
/// Ref type — mutable reference cell holding a value of type T
|
||||
Ref(Box<Type>),
|
||||
/// Versioned type (e.g., User @v2)
|
||||
Versioned {
|
||||
base: Box<Type>,
|
||||
@@ -120,7 +122,7 @@ impl Type {
|
||||
}
|
||||
Type::Tuple(elements) => elements.iter().any(|e| e.contains_var(var)),
|
||||
Type::Record(fields) => fields.iter().any(|(_, t)| t.contains_var(var)),
|
||||
Type::List(inner) | Type::Option(inner) => inner.contains_var(var),
|
||||
Type::List(inner) | Type::Option(inner) | Type::Ref(inner) => inner.contains_var(var),
|
||||
Type::Map(k, v) => k.contains_var(var) || v.contains_var(var),
|
||||
Type::Versioned { base, .. } => base.contains_var(var),
|
||||
_ => false,
|
||||
@@ -161,6 +163,7 @@ impl Type {
|
||||
),
|
||||
Type::List(inner) => Type::List(Box::new(inner.apply(subst))),
|
||||
Type::Option(inner) => Type::Option(Box::new(inner.apply(subst))),
|
||||
Type::Ref(inner) => Type::Ref(Box::new(inner.apply(subst))),
|
||||
Type::Map(k, v) => Type::Map(Box::new(k.apply(subst)), Box::new(v.apply(subst))),
|
||||
Type::Versioned { base, version } => Type::Versioned {
|
||||
base: Box::new(base.apply(subst)),
|
||||
@@ -211,7 +214,7 @@ impl Type {
|
||||
}
|
||||
vars
|
||||
}
|
||||
Type::List(inner) | Type::Option(inner) => inner.free_vars(),
|
||||
Type::List(inner) | Type::Option(inner) | Type::Ref(inner) => inner.free_vars(),
|
||||
Type::Map(k, v) => {
|
||||
let mut vars = k.free_vars();
|
||||
vars.extend(v.free_vars());
|
||||
@@ -288,6 +291,7 @@ impl fmt::Display for Type {
|
||||
}
|
||||
Type::List(inner) => write!(f, "List<{}>", inner),
|
||||
Type::Option(inner) => write!(f, "Option<{}>", inner),
|
||||
Type::Ref(inner) => write!(f, "Ref<{}>", inner),
|
||||
Type::Map(k, v) => write!(f, "Map<{}, {}>", k, v),
|
||||
Type::Versioned { base, version } => {
|
||||
write!(f, "{} {}", base, version)
|
||||
@@ -1946,6 +1950,32 @@ impl TypeEnv {
|
||||
]);
|
||||
env.bind("Map", TypeScheme::mono(map_module_type));
|
||||
|
||||
// Ref module
|
||||
let ref_inner = || Type::var();
|
||||
let ref_type = || Type::Ref(Box::new(Type::var()));
|
||||
let ref_module_type = Type::Record(vec![
|
||||
(
|
||||
"new".to_string(),
|
||||
Type::function(vec![ref_inner()], ref_type()),
|
||||
),
|
||||
(
|
||||
"get".to_string(),
|
||||
Type::function(vec![ref_type()], ref_inner()),
|
||||
),
|
||||
(
|
||||
"set".to_string(),
|
||||
Type::function(vec![ref_type(), ref_inner()], Type::Unit),
|
||||
),
|
||||
(
|
||||
"update".to_string(),
|
||||
Type::function(
|
||||
vec![ref_type(), Type::function(vec![ref_inner()], ref_inner())],
|
||||
Type::Unit,
|
||||
),
|
||||
),
|
||||
]);
|
||||
env.bind("Ref", TypeScheme::mono(ref_module_type));
|
||||
|
||||
// Result module
|
||||
let result_type = Type::App {
|
||||
constructor: Box::new(Type::Named("Result".to_string())),
|
||||
@@ -2185,6 +2215,9 @@ impl TypeEnv {
|
||||
Type::Map(k, v) => {
|
||||
Type::Map(Box::new(self.expand_type_alias(k)), Box::new(self.expand_type_alias(v)))
|
||||
}
|
||||
Type::Ref(inner) => {
|
||||
Type::Ref(Box::new(self.expand_type_alias(inner)))
|
||||
}
|
||||
Type::Versioned { base, version } => {
|
||||
Type::Versioned {
|
||||
base: Box::new(self.expand_type_alias(base)),
|
||||
@@ -2345,6 +2378,9 @@ pub fn unify(t1: &Type, t2: &Type) -> Result<Substitution, String> {
|
||||
// Option
|
||||
(Type::Option(a), Type::Option(b)) => unify(a, b),
|
||||
|
||||
// Ref
|
||||
(Type::Ref(a), Type::Ref(b)) => unify(a, b),
|
||||
|
||||
// Map
|
||||
(Type::Map(k1, v1), Type::Map(k2, v2)) => {
|
||||
let s1 = unify(k1, k2)?;
|
||||
|
||||
Reference in New Issue
Block a user