From 874a6f39a460d8835730f2822f938840ed9f4151 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Tue, 17 Feb 2026 00:58:29 +0100 Subject: [PATCH] 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. --- src/ast/nodes.rs | 38 ++++++++++++++++++++++++++++++----- src/ast/parser.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/src/ast/nodes.rs b/src/ast/nodes.rs index b1ccd1f..db2c645 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -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 { if let Some(val) = self.values.get(name) { return Some(val.clone()); @@ -115,11 +127,15 @@ pub enum UntypedKind { then_br: Box>, else_br: Option>>, }, - // DEFINITION (new) Def { name: Arc, value: Box>, }, + // ASSIGN (Update existing variable) + Assign { + target: Box>, + value: Box>, + }, Lambda { params: Vec>, body: Arc>, @@ -128,7 +144,6 @@ pub enum UntypedKind { callee: Box>, args: Vec>, }, - // BLOCK (do ...) to evaluate multiple expressions Block { exprs: Vec>, }, @@ -158,15 +173,28 @@ impl Node { } }, - // --- 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 { diff --git a/src/ast/parser.rs b/src/ast/parser.rs index 6d39127..9720ba1 100644 --- a/src/ast/parser.rs +++ b/src/ast/parser.rs @@ -28,6 +28,7 @@ impl<'a> Parser<'a> { match self.peek() { TokenKind::LeftParen => self.parse_list(), TokenKind::LeftBracket => self.parse_vector_literal(), + TokenKind::LeftBrace => self.parse_map_literal(), TokenKind::Quote => { let identity = Arc::new(NodeIdentity { location: self.current_token.location }); self.advance()?; // consume ' @@ -82,6 +83,7 @@ impl<'a> Parser<'a> { "if" => self.parse_if(identity), "fn" => self.parse_fn(identity), "def" => self.parse_def(identity), + "assign" => self.parse_assign(identity), "do" => self.parse_do(identity), _ => self.parse_call(head, identity), } @@ -109,7 +111,6 @@ impl<'a> Parser<'a> { } fn parse_def(&mut self, identity: Identity) -> Result, String> { - // (def name value) let name_node = self.parse_expression()?; let name = match name_node.kind { UntypedKind::Identifier(s) => s, @@ -124,6 +125,17 @@ impl<'a> Parser<'a> { }) } + fn parse_assign(&mut self, identity: Identity) -> Result, String> { + // (assign target value) + let target = Box::new(self.parse_expression()?); + let value = Box::new(self.parse_expression()?); + + Ok(Node { + identity, + kind: UntypedKind::Assign { target, value }, + }) + } + fn parse_do(&mut self, identity: Identity) -> Result, String> { let mut exprs = Vec::new(); while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { @@ -152,7 +164,7 @@ impl<'a> Parser<'a> { if *self.peek() != TokenKind::LeftBracket { return Err(format!("Expected parameter vector [...] for fn, found {:?}", self.peek())); } - self.advance()?; // consume '[' + self.advance()?; let mut params = Vec::new(); while *self.peek() != TokenKind::RightBracket { @@ -180,7 +192,7 @@ impl<'a> Parser<'a> { } fn parse_vector_literal(&mut self) -> Result, String> { - let token = self.advance()?; // consume '[' + let token = self.advance()?; let mut elements = Vec::new(); let mut temp_ctx = Context::new(); @@ -200,6 +212,38 @@ impl<'a> Parser<'a> { }) } + fn parse_map_literal(&mut self) -> Result, String> { + let token = self.advance()?; + let mut map = std::collections::HashMap::new(); + let mut temp_ctx = Context::new(); + + while *self.peek() != TokenKind::RightBrace { + if *self.peek() == TokenKind::EOF { + return Err("Unexpected EOF in map literal".to_string()); + } + + let key_node = self.parse_expression()?; + let key = match key_node.eval(&mut temp_ctx)? { + Value::Keyword(k) => k, + _ => return Err("Map keys must be keywords".to_string()), + }; + + if *self.peek() == TokenKind::RightBrace { + return Err("Map literal must have even number of forms".to_string()); + } + let val_node = self.parse_expression()?; + let val = val_node.eval(&mut temp_ctx)?; + + map.insert(key, val); + } + self.expect(TokenKind::RightBrace)?; + + Ok(Node { + identity: Arc::new(NodeIdentity { location: token.location }), + kind: UntypedKind::Constant(Value::Record(Arc::new(map))), + }) + } + fn expect(&mut self, kind: TokenKind) -> Result<(), String> { let token = self.advance()?; if token.kind == kind {