Add support for destructuring def
This commit introduces the `DefDestructure` bound kind and modifies the binder, analyzer, type checker, and VM to support destructuring in `def` statements. This allows for pattern matching on the right-hand side of a `def` to bind multiple variables. The parser has been updated to accept patterns in `def` statements. The binder now handles `UntypedKind::Def` with a `target` pattern, rather than a simple `name`. This enables destructuring. The `Gemini.md` documentation has been updated to include a new rule for incremental development.
This commit is contained in:
@@ -178,6 +178,18 @@ impl<'a> Analyzer<'a> {
|
||||
)
|
||||
}
|
||||
|
||||
BoundKind::DefDestructure { pattern, value } => {
|
||||
let pat_m = self.visit(Rc::new((**pattern).clone()));
|
||||
let val_m = self.visit(Rc::new((**value).clone()));
|
||||
(
|
||||
BoundKind::DefDestructure {
|
||||
pattern: Box::new(pat_m),
|
||||
value: Box::new(val_m),
|
||||
},
|
||||
Purity::Impure,
|
||||
)
|
||||
}
|
||||
|
||||
BoundKind::Call { callee, args } => {
|
||||
let callee_m = self.visit(Rc::new((**callee).clone()));
|
||||
let args_m = self.visit(Rc::new((**args).clone()));
|
||||
|
||||
+104
-56
@@ -128,23 +128,8 @@ impl Binder {
|
||||
))
|
||||
}
|
||||
|
||||
UntypedKind::Parameter(sym) => {
|
||||
// Parameters are ONLY allowed if we are inside a function (Binder stack > 1)
|
||||
if self.functions.len() <= 1 {
|
||||
return Err(format!(
|
||||
"Parameter '{}' is not allowed in root scope.",
|
||||
sym.name
|
||||
));
|
||||
}
|
||||
let current_fn = self.functions.last_mut().unwrap();
|
||||
let slot = current_fn.scope.define(sym)?;
|
||||
Ok(self.make_node(
|
||||
node.identity.clone(),
|
||||
BoundKind::Parameter {
|
||||
name: sym.clone(),
|
||||
slot,
|
||||
},
|
||||
))
|
||||
UntypedKind::Parameter(_) => {
|
||||
Err("Unexpected 'Parameter' node in general binder context. This should be handled via 'bind_pattern'.".to_string())
|
||||
}
|
||||
|
||||
UntypedKind::If {
|
||||
@@ -169,52 +154,66 @@ impl Binder {
|
||||
))
|
||||
}
|
||||
|
||||
UntypedKind::Def { name, value } => {
|
||||
// 1. Pre-declare name to support recursion
|
||||
let slot_or_idx = if self.functions.len() == 1 {
|
||||
let mut globals = self.globals.borrow_mut();
|
||||
if globals.contains_key(name) {
|
||||
return Err(format!(
|
||||
"Global variable '{}' is already defined.",
|
||||
name.name
|
||||
));
|
||||
UntypedKind::Def { target, value } => {
|
||||
// Special case: Single identifier (to support recursion)
|
||||
if let UntypedKind::Parameter(ref name) = target.kind {
|
||||
let slot_or_idx = if self.functions.len() == 1 {
|
||||
let mut globals = self.globals.borrow_mut();
|
||||
if globals.contains_key(name) {
|
||||
return Err(format!(
|
||||
"Global variable '{}' is already defined.",
|
||||
name.name
|
||||
));
|
||||
}
|
||||
let idx = globals.len() as u32;
|
||||
globals.insert(name.clone(), idx);
|
||||
idx
|
||||
} else {
|
||||
let current_fn = self.functions.last_mut().unwrap();
|
||||
current_fn.scope.define(name)?
|
||||
};
|
||||
|
||||
let val_node = self.bind(value)?;
|
||||
|
||||
if self.functions.len() == 1 {
|
||||
Ok(self.make_node(
|
||||
node.identity.clone(),
|
||||
BoundKind::DefGlobal {
|
||||
name: name.clone(),
|
||||
global_index: slot_or_idx,
|
||||
value: Box::new(val_node),
|
||||
},
|
||||
))
|
||||
} else {
|
||||
let captured_by = self
|
||||
.capture_map
|
||||
.get(&target.identity)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
Ok(self.make_node(
|
||||
node.identity.clone(),
|
||||
BoundKind::DefLocal {
|
||||
name: name.clone(),
|
||||
slot: slot_or_idx,
|
||||
value: Box::new(val_node),
|
||||
captured_by,
|
||||
},
|
||||
))
|
||||
}
|
||||
let idx = globals.len() as u32;
|
||||
globals.insert(name.clone(), idx);
|
||||
idx
|
||||
} else {
|
||||
let current_fn = self.functions.last_mut().unwrap();
|
||||
current_fn.scope.define(name)?
|
||||
};
|
||||
// Complex Destructuring Pattern
|
||||
// NOTE: Destructuring definitions are NOT recursive by default
|
||||
// (the variables are only available AFTER the definition)
|
||||
let val_node = self.bind(value)?;
|
||||
let target_node = self.bind_pattern(target)?;
|
||||
|
||||
// 2. Bind Value (now 'name' is visible)
|
||||
let val_node = self.bind(value)?;
|
||||
|
||||
// 3. Return Node
|
||||
if self.functions.len() == 1 {
|
||||
Ok(self.make_node(
|
||||
node.identity.clone(),
|
||||
BoundKind::DefGlobal {
|
||||
name: name.clone(),
|
||||
global_index: slot_or_idx,
|
||||
BoundKind::DefDestructure {
|
||||
pattern: Box::new(target_node),
|
||||
value: Box::new(val_node),
|
||||
},
|
||||
))
|
||||
} else {
|
||||
let captured_by = self
|
||||
.capture_map
|
||||
.get(&node.identity)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
Ok(self.make_node(
|
||||
node.identity.clone(),
|
||||
BoundKind::DefLocal {
|
||||
name: name.clone(),
|
||||
slot: slot_or_idx,
|
||||
value: Box::new(val_node),
|
||||
captured_by,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +239,7 @@ impl Binder {
|
||||
self.functions.push(FunctionCompiler::new());
|
||||
|
||||
// 1. Bind the parameter pattern/tuple
|
||||
let params_bound = self.bind(params)?;
|
||||
let params_bound = self.bind_pattern(params)?;
|
||||
|
||||
// 2. Bind the body
|
||||
let body_bound = self.bind(body)?;
|
||||
@@ -405,6 +404,55 @@ impl Binder {
|
||||
Err(format!("Undefined variable '{}'", sym.name))
|
||||
}
|
||||
|
||||
fn bind_pattern(&mut self, node: &Node<UntypedKind>) -> Result<BoundNode, String> {
|
||||
match &node.kind {
|
||||
UntypedKind::Parameter(sym) => {
|
||||
if self.functions.len() == 1 {
|
||||
// Global Definition in pattern
|
||||
let mut globals = self.globals.borrow_mut();
|
||||
if globals.contains_key(sym) {
|
||||
return Err(format!("Global variable '{}' is already defined.", sym.name));
|
||||
}
|
||||
let idx = globals.len() as u32;
|
||||
globals.insert(sym.clone(), idx);
|
||||
// For Global Destructuring, we return a DefGlobal with Nop value as it's a pattern part
|
||||
Ok(self.make_node(
|
||||
node.identity.clone(),
|
||||
BoundKind::DefGlobal {
|
||||
name: sym.clone(),
|
||||
global_index: idx,
|
||||
value: Box::new(self.make_node(node.identity.clone(), BoundKind::Nop)),
|
||||
},
|
||||
))
|
||||
} else {
|
||||
// Local Parameter/Definition in pattern
|
||||
let current_fn = self.functions.last_mut().unwrap();
|
||||
let slot = current_fn.scope.define(sym)?;
|
||||
Ok(self.make_node(
|
||||
node.identity.clone(),
|
||||
BoundKind::Parameter {
|
||||
name: sym.clone(),
|
||||
slot,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
UntypedKind::Tuple { elements } => {
|
||||
let mut bound_elems = Vec::new();
|
||||
for e in elements {
|
||||
bound_elems.push(self.bind_pattern(e)?);
|
||||
}
|
||||
Ok(self.make_node(
|
||||
node.identity.clone(),
|
||||
BoundKind::Tuple {
|
||||
elements: bound_elems,
|
||||
},
|
||||
))
|
||||
}
|
||||
_ => Err(format!("Invalid node in pattern: {:?}", node.kind)),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_node(&self, identity: Identity, kind: BoundKind<()>) -> BoundNode {
|
||||
Node {
|
||||
identity,
|
||||
|
||||
@@ -82,6 +82,12 @@ pub enum BoundKind<T = ()> {
|
||||
value: Box<BoundNode<T>>,
|
||||
},
|
||||
|
||||
/// A destructuring definition (can be local or global depending on the pattern)
|
||||
DefDestructure {
|
||||
pattern: Box<BoundNode<T>>,
|
||||
value: Box<BoundNode<T>>,
|
||||
},
|
||||
|
||||
Lambda {
|
||||
params: Rc<BoundNode<T>>,
|
||||
// The list of variables captured from enclosing scopes
|
||||
@@ -187,6 +193,16 @@ where
|
||||
value: vb,
|
||||
},
|
||||
) => na == nb && ga == gb && va == vb,
|
||||
(
|
||||
BoundKind::DefDestructure {
|
||||
pattern: pa,
|
||||
value: va,
|
||||
},
|
||||
BoundKind::DefDestructure {
|
||||
pattern: pb,
|
||||
value: vb,
|
||||
},
|
||||
) => pa == pb && va == vb,
|
||||
(
|
||||
BoundKind::Lambda {
|
||||
params: pa,
|
||||
@@ -249,6 +265,7 @@ impl<T> BoundKind<T> {
|
||||
BoundKind::DefGlobal {
|
||||
name, global_index, ..
|
||||
} => format!("DEF_GLOBAL({}, Idx:{})", name.name, global_index),
|
||||
BoundKind::DefDestructure { .. } => "DEF_DESTRUCTURE".to_string(),
|
||||
BoundKind::Lambda {
|
||||
params, upvalues, ..
|
||||
} => {
|
||||
|
||||
@@ -120,6 +120,18 @@ impl Dumper {
|
||||
self.indent -= 1;
|
||||
}
|
||||
|
||||
BoundKind::DefDestructure { pattern, value } => {
|
||||
self.log("DefDestructure", node);
|
||||
self.indent += 1;
|
||||
self.write_indent();
|
||||
self.output.push_str("Pattern:\n");
|
||||
self.visit(pattern);
|
||||
self.write_indent();
|
||||
self.output.push_str("Value:\n");
|
||||
self.visit(value);
|
||||
self.indent -= 1;
|
||||
}
|
||||
|
||||
BoundKind::If {
|
||||
cond,
|
||||
then_br,
|
||||
|
||||
@@ -186,12 +186,13 @@ impl<E: MacroEvaluator> MacroExpander<E> {
|
||||
})
|
||||
}
|
||||
|
||||
UntypedKind::Def { name, value } => {
|
||||
UntypedKind::Def { target, value } => {
|
||||
let target = self.expand_recursive(*target)?;
|
||||
let value = self.expand_recursive(*value)?;
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Def {
|
||||
name,
|
||||
target: Box::new(target),
|
||||
value: Box::new(value),
|
||||
},
|
||||
ty: (),
|
||||
@@ -379,13 +380,13 @@ impl<E: MacroEvaluator> MacroExpander<E> {
|
||||
Ok(self.value_to_node(val, node.identity))
|
||||
}
|
||||
|
||||
UntypedKind::Def { mut name, value } => {
|
||||
name.context = Some(state.expansion_id.clone());
|
||||
UntypedKind::Def { target, value } => {
|
||||
let target = self.expand_template(*target, state)?;
|
||||
let val = self.expand_template(*value, state)?;
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Def {
|
||||
name,
|
||||
target: Box::new(target),
|
||||
value: Box::new(val),
|
||||
},
|
||||
ty: (),
|
||||
@@ -613,9 +614,13 @@ mod tests {
|
||||
call,
|
||||
} = &exprs[1].kind
|
||||
{
|
||||
if let UntypedKind::Def { name, .. } = &result.kind {
|
||||
assert_eq!(name.context, Some(call.identity.clone()));
|
||||
assert_eq!(name.name.as_ref(), "y");
|
||||
if let UntypedKind::Def { target, .. } = &result.kind {
|
||||
if let UntypedKind::Parameter(sym) = &target.kind {
|
||||
assert_eq!(sym.context, Some(call.identity.clone()));
|
||||
assert_eq!(sym.name.as_ref(), "y");
|
||||
} else {
|
||||
panic!("Expected Parameter target, got {:?}", target.kind);
|
||||
}
|
||||
} else {
|
||||
panic!("Expected Def result, got {:?}", result.kind);
|
||||
}
|
||||
|
||||
@@ -118,6 +118,10 @@ impl TCO {
|
||||
global_index: *global_index,
|
||||
value: Box::new(Self::transform(Rc::new((**value).clone()), false)),
|
||||
},
|
||||
BoundKind::DefDestructure { pattern, value } => BoundKind::DefDestructure {
|
||||
pattern: Box::new(Self::transform(Rc::new((**pattern).clone()), false)),
|
||||
value: Box::new(Self::transform(Rc::new((**value).clone()), false)),
|
||||
},
|
||||
BoundKind::Record { fields } => {
|
||||
let new_fields = fields
|
||||
.iter()
|
||||
|
||||
@@ -144,6 +144,27 @@ impl TypeChecker {
|
||||
ctx.set_local_type(slot, specialized_ty.clone());
|
||||
(BoundKind::Parameter { name, slot }, specialized_ty.clone())
|
||||
}
|
||||
BoundKind::DefGlobal {
|
||||
name,
|
||||
global_index,
|
||||
..
|
||||
} => {
|
||||
self.global_types
|
||||
.borrow_mut()
|
||||
.insert(global_index, specialized_ty.clone());
|
||||
(
|
||||
BoundKind::DefGlobal {
|
||||
name,
|
||||
global_index,
|
||||
value: Box::new(Node {
|
||||
identity: node.identity.clone(),
|
||||
kind: BoundKind::Nop,
|
||||
ty: StaticType::Void,
|
||||
}),
|
||||
},
|
||||
specialized_ty.clone(),
|
||||
)
|
||||
}
|
||||
BoundKind::Tuple { elements } => {
|
||||
let mut typed_elements = Vec::new();
|
||||
let mut elem_types = Vec::new();
|
||||
@@ -263,6 +284,19 @@ impl TypeChecker {
|
||||
)
|
||||
}
|
||||
|
||||
BoundKind::DefDestructure { 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 {
|
||||
pattern: Box::new(pat_typed),
|
||||
value: Box::new(val_typed),
|
||||
},
|
||||
ty,
|
||||
)
|
||||
}
|
||||
|
||||
BoundKind::If {
|
||||
cond,
|
||||
then_br,
|
||||
|
||||
@@ -58,10 +58,10 @@ impl UpvalueAnalyzer {
|
||||
}
|
||||
}
|
||||
}
|
||||
UntypedKind::Def { name, value } => {
|
||||
UntypedKind::Def { target, value } => {
|
||||
Self::visit(value, scopes, capture_map, current_lambda.clone());
|
||||
if let Some(current) = scopes.last_mut() {
|
||||
current.insert(name.clone(), node.identity.clone());
|
||||
Self::visit_params(target, current);
|
||||
}
|
||||
}
|
||||
UntypedKind::Lambda { params, body } => {
|
||||
|
||||
Reference in New Issue
Block a user