feat: Add assignment operator and recursive scope assignment
Introduces the `Assign` AST node and a corresponding `assign` method on the `Scope` struct. This allows for updating existing variables within the current scope or any parent scope. The `assign` method recursively traverses the scope chain until it finds the variable or determines it's undefined. This enables reassigning variables defined in outer scopes.
This commit is contained in:
+33
-5
@@ -35,6 +35,18 @@ impl Scope {
|
||||
self.values.insert(name.to_string(), value);
|
||||
}
|
||||
|
||||
/// Recursively finds and updates an existing variable
|
||||
pub fn assign(&mut self, name: &str, value: Value) -> Result<(), String> {
|
||||
if self.values.contains_key(name) {
|
||||
self.values.insert(name.to_string(), value);
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(parent) = &self.parent {
|
||||
return parent.lock().unwrap().assign(name, value);
|
||||
}
|
||||
Err(format!("Cannot assign to undefined variable '{}'", name))
|
||||
}
|
||||
|
||||
pub fn resolve(&self, name: &str) -> Option<Value> {
|
||||
if let Some(val) = self.values.get(name) {
|
||||
return Some(val.clone());
|
||||
@@ -115,11 +127,15 @@ pub enum UntypedKind {
|
||||
then_br: Box<Node<UntypedKind>>,
|
||||
else_br: Option<Box<Node<UntypedKind>>>,
|
||||
},
|
||||
// DEFINITION (new)
|
||||
Def {
|
||||
name: Arc<str>,
|
||||
value: Box<Node<UntypedKind>>,
|
||||
},
|
||||
// ASSIGN (Update existing variable)
|
||||
Assign {
|
||||
target: Box<Node<UntypedKind>>,
|
||||
value: Box<Node<UntypedKind>>,
|
||||
},
|
||||
Lambda {
|
||||
params: Vec<Arc<str>>,
|
||||
body: Arc<Node<UntypedKind>>,
|
||||
@@ -128,7 +144,6 @@ pub enum UntypedKind {
|
||||
callee: Box<Node<UntypedKind>>,
|
||||
args: Vec<Node<UntypedKind>>,
|
||||
},
|
||||
// BLOCK (do ...) to evaluate multiple expressions
|
||||
Block {
|
||||
exprs: Vec<Node<UntypedKind>>,
|
||||
},
|
||||
@@ -158,15 +173,28 @@ impl Node<UntypedKind> {
|
||||
}
|
||||
},
|
||||
|
||||
// --- DEF (Assignment) ---
|
||||
UntypedKind::Def { name, value } => {
|
||||
let val = value.eval(ctx)?;
|
||||
let mut scope = ctx.scope.lock().unwrap();
|
||||
scope.define(name, val.clone());
|
||||
Ok(val) // Return value of definition
|
||||
Ok(val)
|
||||
},
|
||||
|
||||
// --- ASSIGN IMPLEMENTATION ---
|
||||
UntypedKind::Assign { target, value } => {
|
||||
let val = value.eval(ctx)?;
|
||||
|
||||
// We currently only support assigning to identifiers: (assign x 10)
|
||||
if let UntypedKind::Identifier(name) = &target.kind {
|
||||
let mut scope = ctx.scope.lock().unwrap();
|
||||
scope.assign(name, val.clone())?;
|
||||
Ok(val)
|
||||
} else {
|
||||
// Later: Support (assign (get arr 0) val) via set-element logic
|
||||
Err("Assignment target must be an identifier".to_string())
|
||||
}
|
||||
},
|
||||
|
||||
// --- BLOCK (do) ---
|
||||
UntypedKind::Block { exprs } => {
|
||||
let mut last_val = Value::Void;
|
||||
for expr in exprs {
|
||||
|
||||
Reference in New Issue
Block a user