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 } => {
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ pub enum UntypedKind {
|
||||
else_br: Option<Box<Node<UntypedKind>>>,
|
||||
},
|
||||
Def {
|
||||
name: Symbol,
|
||||
target: Box<Node<UntypedKind>>,
|
||||
value: Box<Node<UntypedKind>>,
|
||||
},
|
||||
Assign {
|
||||
|
||||
+41
-41
@@ -185,17 +185,12 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
|
||||
fn parse_def(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
|
||||
let name_node = self.parse_expression()?;
|
||||
let name = match name_node.kind {
|
||||
UntypedKind::Identifier(sym) => sym,
|
||||
_ => return Err("Expected identifier for def name".to_string()),
|
||||
};
|
||||
|
||||
let target = Box::new(self.parse_pattern()?);
|
||||
let value = Box::new(self.parse_expression()?);
|
||||
|
||||
Ok(Node {
|
||||
identity,
|
||||
kind: UntypedKind::Def { name, value },
|
||||
kind: UntypedKind::Def { target, value },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
@@ -264,44 +259,49 @@ impl<'a> Parser<'a> {
|
||||
self.peek()
|
||||
));
|
||||
}
|
||||
let token = self.advance()?;
|
||||
let identity = Rc::new(NodeIdentity {
|
||||
location: token.location,
|
||||
});
|
||||
self.parse_pattern()
|
||||
}
|
||||
|
||||
let mut elements = Vec::new();
|
||||
while *self.peek() != TokenKind::RightBracket {
|
||||
let next_peek = self.peek();
|
||||
match next_peek {
|
||||
TokenKind::Identifier(name) => {
|
||||
let name = name.clone();
|
||||
let token = self.advance()?;
|
||||
let p_identity = Rc::new(NodeIdentity {
|
||||
fn parse_pattern(&mut self) -> Result<Node<UntypedKind>, String> {
|
||||
let next = self.peek();
|
||||
match next {
|
||||
TokenKind::Identifier(_) => {
|
||||
let token = self.advance()?;
|
||||
let sym: Symbol = match token.kind {
|
||||
TokenKind::Identifier(s) => s.into(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Ok(Node {
|
||||
identity: Rc::new(NodeIdentity {
|
||||
location: token.location,
|
||||
});
|
||||
elements.push(Node {
|
||||
identity: p_identity,
|
||||
kind: UntypedKind::Parameter(name.into()),
|
||||
ty: (),
|
||||
});
|
||||
}
|
||||
TokenKind::LeftBracket => {
|
||||
elements.push(self.parse_param_vector()?);
|
||||
}
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Expected identifier or nested parameter vector, found {:?}",
|
||||
next_peek
|
||||
));
|
||||
}
|
||||
}),
|
||||
kind: UntypedKind::Parameter(sym),
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
TokenKind::LeftBracket => {
|
||||
let token = self.advance()?;
|
||||
let identity = Rc::new(NodeIdentity {
|
||||
location: token.location,
|
||||
});
|
||||
|
||||
let mut elements = Vec::new();
|
||||
while *self.peek() != TokenKind::RightBracket {
|
||||
elements.push(self.parse_pattern()?);
|
||||
}
|
||||
self.expect(TokenKind::RightBracket)?;
|
||||
|
||||
Ok(Node {
|
||||
identity,
|
||||
kind: UntypedKind::Tuple { elements },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
_ => Err(format!(
|
||||
"Expected identifier or pattern vector [...] for definition, found {:?}",
|
||||
next
|
||||
)),
|
||||
}
|
||||
self.expect(TokenKind::RightBracket)?;
|
||||
Ok(Node {
|
||||
identity,
|
||||
kind: UntypedKind::Tuple { elements },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_call(
|
||||
|
||||
+25
-9
@@ -281,6 +281,18 @@ impl VM {
|
||||
globals[idx] = val.clone();
|
||||
Ok(val)
|
||||
}
|
||||
BoundKind::DefDestructure { pattern, value } => {
|
||||
let val = self.eval_internal(obs, value)?;
|
||||
let mut offset = 0;
|
||||
|
||||
// Destructuring works on tuples/vectors, or single values wrapped in a slice
|
||||
if let Some(vals) = val.as_slice() {
|
||||
self.unpack(pattern, vals, &mut offset)?;
|
||||
} else {
|
||||
self.unpack(pattern, &[val.clone()], &mut offset)?;
|
||||
}
|
||||
Ok(val)
|
||||
}
|
||||
BoundKind::Get { addr, .. } => self.get_value(*addr),
|
||||
BoundKind::Set { addr, value } => {
|
||||
let val = self.eval_internal(obs, value)?;
|
||||
@@ -622,9 +634,7 @@ impl VM {
|
||||
|
||||
fn flatten_value(val: Value, into: &mut Vec<Value>) {
|
||||
if let Some(values) = val.as_slice() {
|
||||
for item in values.iter() {
|
||||
Self::flatten_value(item.clone(), into);
|
||||
}
|
||||
into.extend_from_slice(values);
|
||||
} else {
|
||||
into.push(val);
|
||||
}
|
||||
@@ -654,12 +664,7 @@ impl VM {
|
||||
into: &mut Vec<Value>,
|
||||
) -> Result<(), String> {
|
||||
for e in elements {
|
||||
match &e.kind {
|
||||
BoundKind::Tuple { elements: sub } => {
|
||||
self.eval_and_flatten_internal(obs, sub, into)?
|
||||
}
|
||||
_ => into.push(self.eval_internal(obs, e)?),
|
||||
}
|
||||
into.push(self.eval_internal(obs, e)?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -685,6 +690,17 @@ impl VM {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
BoundKind::DefGlobal { global_index, .. } => {
|
||||
let val = values.get(*offset).cloned().unwrap_or(Value::Void);
|
||||
*offset += 1;
|
||||
let idx = *global_index as usize;
|
||||
let mut globals = self.globals.borrow_mut();
|
||||
if idx >= globals.len() {
|
||||
globals.resize(idx + 1, Value::Void);
|
||||
}
|
||||
globals[idx] = val;
|
||||
Ok(())
|
||||
}
|
||||
BoundKind::Tuple { elements } => {
|
||||
if let Some(sub_values) = values.get(*offset).and_then(|v| v.as_slice()) {
|
||||
*offset += 1;
|
||||
|
||||
@@ -312,4 +312,28 @@ mod tests {
|
||||
dump_record
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_def_destructuring() {
|
||||
let env = Environment::new();
|
||||
|
||||
// 1. Global destructuring
|
||||
let source_global = "(do (def [a b] [1 2]) (+ a b))";
|
||||
assert_eq!(format!("{}", env.run_script(source_global).unwrap()), "3");
|
||||
|
||||
// 2. Local nested destructuring inside a function
|
||||
let source_local = "((fn [x] (do (def [a [[b c] d]] x) (+ a (+ b (+ c d))))) [1 [[2 3] 4]])";
|
||||
assert_eq!(format!("{}", env.run_script(source_local).unwrap()), "10");
|
||||
|
||||
// 3. Verify 'def' returns the assigned value
|
||||
let source_return = "(def [x y] [7 8])";
|
||||
let res = env.run_script(source_return).unwrap();
|
||||
if let Value::Tuple(vals) = res {
|
||||
assert_eq!(vals.len(), 2);
|
||||
assert_eq!(format!("{}", vals[0]), "7");
|
||||
assert_eq!(format!("{}", vals[1]), "8");
|
||||
} else {
|
||||
panic!("Expected tuple return from def, got {:?}", res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user