diff --git a/src/ast/compiler/analyzer.rs b/src/ast/compiler/analyzer.rs index f74c0c3..0027cd0 100644 --- a/src/ast/compiler/analyzer.rs +++ b/src/ast/compiler/analyzer.rs @@ -178,11 +178,11 @@ impl<'a> Analyzer<'a> { ) } - BoundKind::DefDestructure { pattern, value } => { + BoundKind::Destructure { pattern, value } => { let pat_m = self.visit(Rc::new((**pattern).clone())); let val_m = self.visit(Rc::new((**value).clone())); ( - BoundKind::DefDestructure { + BoundKind::Destructure { pattern: Box::new(pat_m), value: Box::new(val_m), }, diff --git a/src/ast/compiler/binder.rs b/src/ast/compiler/binder.rs index bb2078a..76e43d8 100644 --- a/src/ast/compiler/binder.rs +++ b/src/ast/compiler/binder.rs @@ -209,7 +209,7 @@ impl Binder { Ok(self.make_node( node.identity.clone(), - BoundKind::DefDestructure { + BoundKind::Destructure { pattern: Box::new(target_node), value: Box::new(val_node), }, @@ -230,7 +230,14 @@ impl Binder { }, )) } else { - Err("Assignment target must be an identifier".to_string()) + let target_node = self.bind_assign_pattern(target)?; + Ok(self.make_node( + node.identity.clone(), + BoundKind::Destructure { + pattern: Box::new(target_node), + value: Box::new(val_node), + }, + )) } } @@ -456,6 +463,34 @@ impl Binder { } } + fn bind_assign_pattern(&mut self, node: &Node) -> Result { + match &node.kind { + UntypedKind::Identifier(sym) => { + let addr = self.resolve_variable(sym)?; + Ok(self.make_node( + node.identity.clone(), + BoundKind::Set { + addr, + value: Box::new(self.make_node(node.identity.clone(), BoundKind::Nop)), + }, + )) + } + UntypedKind::Tuple { elements } => { + let mut bound_elems = Vec::new(); + for e in elements { + bound_elems.push(self.bind_assign_pattern(e)?); + } + Ok(self.make_node( + node.identity.clone(), + BoundKind::Tuple { + elements: bound_elems, + }, + )) + } + _ => Err(format!("Invalid node in assignment pattern: {:?}", node.kind)), + } + } + fn make_node(&self, identity: Identity, kind: BoundKind<()>) -> BoundNode { Node { identity, diff --git a/src/ast/compiler/bound_nodes.rs b/src/ast/compiler/bound_nodes.rs index 7f4da6d..66d2ee0 100644 --- a/src/ast/compiler/bound_nodes.rs +++ b/src/ast/compiler/bound_nodes.rs @@ -82,8 +82,8 @@ pub enum BoundKind { value: Box>, }, - /// A destructuring definition (can be local or global depending on the pattern) - DefDestructure { + /// A destructuring operation (can be a definition or an assignment depending on the pattern) + Destructure { pattern: Box>, value: Box>, }, @@ -194,11 +194,11 @@ where }, ) => na == nb && ga == gb && va == vb, ( - BoundKind::DefDestructure { + BoundKind::Destructure { pattern: pa, value: va, }, - BoundKind::DefDestructure { + BoundKind::Destructure { pattern: pb, value: vb, }, @@ -265,7 +265,7 @@ impl BoundKind { BoundKind::DefGlobal { name, global_index, .. } => format!("DEF_GLOBAL({}, Idx:{})", name.name, global_index), - BoundKind::DefDestructure { .. } => "DEF_DESTRUCTURE".to_string(), + BoundKind::Destructure { .. } => "DESTRUCTURE".to_string(), BoundKind::Lambda { params, upvalues, .. } => { diff --git a/src/ast/compiler/dumper.rs b/src/ast/compiler/dumper.rs index 8b3f0ea..24c54a9 100644 --- a/src/ast/compiler/dumper.rs +++ b/src/ast/compiler/dumper.rs @@ -120,8 +120,8 @@ impl Dumper { self.indent -= 1; } - BoundKind::DefDestructure { pattern, value } => { - self.log("DefDestructure", node); + BoundKind::Destructure { pattern, value } => { + self.log("Destructure", node); self.indent += 1; self.write_indent(); self.output.push_str("Pattern:\n"); diff --git a/src/ast/compiler/optimizer.rs b/src/ast/compiler/optimizer.rs index 00443a3..6ac3822 100644 --- a/src/ast/compiler/optimizer.rs +++ b/src/ast/compiler/optimizer.rs @@ -95,6 +95,34 @@ impl Optimizer { current } + fn collect_pattern_usage(&self, node: &AnalyzedNode, info: &mut UsageInfo) { + match &node.kind { + BoundKind::Parameter { slot, .. } => { + info.assigned_locals.insert(*slot); + } + BoundKind::DefGlobal { global_index, .. } => { + info.assigned_globals.insert(*global_index); + } + BoundKind::Set { addr, .. } => { + match addr { + Address::Local(slot) => { + info.assigned_locals.insert(*slot); + } + Address::Global(idx) => { + info.assigned_globals.insert(*idx); + } + _ => {} + } + } + BoundKind::Tuple { elements } => { + for el in elements { + self.collect_pattern_usage(el, info); + } + } + _ => {} + } + } + fn visit_node( &self, node: AnalyzedNode, @@ -561,6 +589,31 @@ impl Optimizer { ) } + BoundKind::Destructure { + ref pattern, + ref value, + } => { + let val_opt = Box::new(self.visit_node((**value).clone(), sub, path)); + let pat_opt = Box::new(self.visit_node((**pattern).clone(), sub, path)); + + let mut info = UsageInfo::default(); + self.collect_pattern_usage(&pat_opt, &mut info); + for slot in info.assigned_locals { + sub.locals.remove(&slot); + } + for idx in info.assigned_globals { + sub.globals.remove(&idx); + } + + ( + BoundKind::Destructure { + pattern: pat_opt, + value: val_opt, + }, + node.ty.clone(), + ) + } + BoundKind::Tuple { elements } => { let elements = elements .into_iter() @@ -808,6 +861,10 @@ impl Optimizer { fn collect_usage(&self, node: &AnalyzedNode, info: &mut UsageInfo) { match &node.kind { + BoundKind::Destructure { pattern, value } => { + self.collect_pattern_usage(pattern, info); + self.collect_usage(value, info); + } BoundKind::Constant(v) => { if let Value::Object(obj) = v && let Some(closure) = obj.as_any().downcast_ref::() diff --git a/src/ast/compiler/tco.rs b/src/ast/compiler/tco.rs index dccdeb1..6ede986 100644 --- a/src/ast/compiler/tco.rs +++ b/src/ast/compiler/tco.rs @@ -118,7 +118,7 @@ impl TCO { global_index: *global_index, value: Box::new(Self::transform(Rc::new((**value).clone()), false)), }, - BoundKind::DefDestructure { pattern, value } => BoundKind::DefDestructure { + BoundKind::Destructure { pattern, value } => BoundKind::Destructure { pattern: Box::new(Self::transform(Rc::new((**pattern).clone()), false)), value: Box::new(Self::transform(Rc::new((**value).clone()), false)), }, diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index 7f1d7ec..fcf08b3 100644 --- a/src/ast/compiler/type_checker.rs +++ b/src/ast/compiler/type_checker.rs @@ -163,6 +163,23 @@ impl TypeChecker { specialized_ty.clone(), ) } + BoundKind::Set { addr, value: _value } => { + // In an assignment pattern, 'value' is just a Nop placeholder from the Binder. + // We update the type of the address (if it's a local or global) to match the specialized type. + // Note: For now, we assume assignments are compatible if types were already inferred, + // or we could add a check here against existing type. + ( + BoundKind::Set { + addr, + value: Box::new(Node { + identity: node.identity.clone(), + kind: BoundKind::Nop, + ty: specialized_ty.clone(), + }), + }, + specialized_ty.clone(), + ) + } BoundKind::Tuple { elements } => { let mut typed_elements = Vec::new(); let mut elem_types = Vec::new(); @@ -282,12 +299,12 @@ impl TypeChecker { ) } - BoundKind::DefDestructure { pattern, value } => { + BoundKind::Destructure { pattern, value } => { let val_typed = self.check_node(*value, ctx)?; let pat_typed = self.check_params(*pattern, &val_typed.ty, ctx)?; let ty = val_typed.ty.clone(); ( - BoundKind::DefDestructure { + BoundKind::Destructure { pattern: Box::new(pat_typed), value: Box::new(val_typed), }, diff --git a/src/ast/vm.rs b/src/ast/vm.rs index 4acbefa..a8ba6a4 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -281,7 +281,7 @@ impl VM { globals[idx] = val.clone(); Ok(val) } - BoundKind::DefDestructure { pattern, value } => { + BoundKind::Destructure { pattern, value } => { let val = self.eval_internal(obs, value)?; let mut offset = 0; @@ -289,7 +289,7 @@ impl VM { if let Some(vals) = val.as_slice() { self.unpack(pattern, vals, &mut offset)?; } else { - self.unpack(pattern, &[val.clone()], &mut offset)?; + self.unpack(pattern, std::slice::from_ref(&val), &mut offset)?; } Ok(val) } @@ -704,6 +704,11 @@ impl VM { globals[idx] = val; Ok(()) } + BoundKind::Set { addr, .. } => { + let val = values.get(*offset).cloned().unwrap_or(Value::Void); + *offset += 1; + self.set_value(*addr, val) + } BoundKind::Tuple { elements } => { if let Some(sub_values) = values.get(*offset).and_then(|v| v.as_slice()) { *offset += 1; diff --git a/src/integration_test.rs b/src/integration_test.rs index d4155b4..82cfc59 100644 --- a/src/integration_test.rs +++ b/src/integration_test.rs @@ -337,4 +337,36 @@ mod tests { panic!("Expected tuple return from def, got {:?}", res); } } + + #[test] + fn test_assign_destructuring() { + // 1. Simple assignment destructuring + { + let env = Environment::new(); + let source_simple = "(do (def a 0) (def b 0) (assign [a b] [10 20]) (+ a b))"; + assert_eq!(format!("{}", env.run_script(source_simple).unwrap()), "30"); + } + + // 2. Nested assignment destructuring + { + let env = Environment::new(); + let source_nested = + "(do (def a 0) (def b 0) (def c 0) (assign [a [b c]] [1 [2 3]]) (+ a (+ b c)))"; + assert_eq!(format!("{}", env.run_script(source_nested).unwrap()), "6"); + } + + // 3. Assignment returns the assigned value + { + let env = Environment::new(); + let source_return = "(do (def a 0) (def b 0) (assign [a b] [5 6]))"; + let res = env.run_script(source_return).unwrap(); + if let Value::Tuple(vals) = res { + assert_eq!(vals.len(), 2); + assert_eq!(format!("{}", vals[0]), "5"); + assert_eq!(format!("{}", vals[1]), "6"); + } else { + panic!("Expected tuple return from assign, got {:?}", res); + } + } + } }