From 145e12b8114fde299d6aba173998c98e3c625fc9 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Tue, 17 Feb 2026 00:43:09 +0100 Subject: [PATCH] Add lambda expression support Introduces `UntypedKind::Lambda` to represent function closures. Adds `parse_fn` to the parser to handle lambda syntax. Updates `Node::eval` to correctly evaluate lambda expressions, creating new scopes for each invocation with captured variables. Improves handling of call expressions by passing the AST node's identity. Removes unnecessary comments and simplifies some error handling. --- src/ast/nodes.rs | 54 +++++++++++++++++---------- src/ast/parser.rs | 93 ++++++++++++++++++++++++++++++----------------- 2 files changed, 94 insertions(+), 53 deletions(-) diff --git a/src/ast/nodes.rs b/src/ast/nodes.rs index b2b644c..31fe040 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -16,7 +16,7 @@ pub trait CustomNode: Debug + Send + Sync { fn display_name(&self) -> &'static str; } -/// Dynamic Scope for the interpreter (Temporary solution) +/// Dynamic Scope for the interpreter #[derive(Debug, Clone)] pub struct Scope { values: HashMap, @@ -54,10 +54,7 @@ pub struct Context { impl Context { pub fn new() -> Self { let mut root_scope = Scope::new(None); - - // Register standard library (built-ins) register_stdlib(&mut root_scope); - Self { scope: Arc::new(Mutex::new(root_scope)), } @@ -65,19 +62,15 @@ impl Context { } fn register_stdlib(scope: &mut Scope) { - // Helper macro to reduce boilerplate macro_rules! bin_op { ($name:expr, $op:tt) => { scope.define($name, Value::Function(Arc::new(|args| { - if args.len() < 2 { return Value::Void; } // Error handling later - - // Fold allow multi-arg: (+ 1 2 3) -> 6 + if args.len() < 2 { return Value::Void; } let mut acc = match &args[0] { Value::Int(i) => *i as f64, Value::Float(f) => *f, _ => return Value::Void, }; - for arg in &args[1..] { let val = match arg { Value::Int(i) => *i as f64, @@ -86,8 +79,6 @@ fn register_stdlib(scope: &mut Scope) { }; acc = acc $op val; } - - // Return Int if result is integer-like (simplified) if acc.fract() == 0.0 { Value::Int(acc as i64) } else { @@ -102,11 +93,9 @@ fn register_stdlib(scope: &mut Scope) { bin_op!("*", *); bin_op!("/", /); - // Logic scope.define(">", Value::Function(Arc::new(|args| { if args.len() != 2 { return Value::Void; } - let (v1, v2) = (&args[0], &args[1]); - match (v1, v2) { + match (&args[0], &args[1]) { (Value::Int(a), Value::Int(b)) => Value::Bool(a > b), (Value::Float(a), Value::Float(b)) => Value::Bool(a > b), (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) > *b), @@ -116,11 +105,8 @@ fn register_stdlib(scope: &mut Scope) { }))); } - -/// The core AST variant enum for performance and structure #[derive(Debug)] pub enum UntypedKind { - /// The "..." placeholder for incomplete code Nop, Constant(Value), Identifier(Arc), @@ -129,11 +115,15 @@ pub enum UntypedKind { then_br: Box>, else_br: Option>>, }, + // CLOSURE SUPPORT + Lambda { + params: Vec>, + body: Arc>, // Arc for cheap cloning into closure + }, Call { callee: Box>, args: Vec>, }, - /// The "escape hatch" for decentralized node types Extension(Box), } @@ -160,10 +150,36 @@ impl Node { } } + // --- CLOSURE IMPLEMENTATION --- + UntypedKind::Lambda { params, body } => { + let captured_scope = ctx.scope.clone(); + let params = params.clone(); + let body = body.clone(); + + Ok(Value::Function(Arc::new(move |args| { + // 1. New Scope with captured parent + let mut new_scope = Scope::new(Some(captured_scope.clone())); + + // 2. Bind Args + for (i, param) in params.iter().enumerate() { + if i < args.len() { + new_scope.define(param, args[i].clone()); + } else { + new_scope.define(param, Value::Void); + } + } + + // 3. Eval Body + // Note: We swallow errors here because Value::Function signature is simple. + // In a full VM, we'd return Result. + let mut sub_ctx = Context { scope: Arc::new(Mutex::new(new_scope)) }; + body.eval(&mut sub_ctx).unwrap_or(Value::Void) + }))) + }, + UntypedKind::Call { callee, args } => { let func_val = callee.eval(ctx)?; - // Evaluate all arguments first (strict evaluation) let mut eval_args = Vec::with_capacity(args.len()); for arg in args { eval_args.push(arg.eval(ctx)?); diff --git a/src/ast/parser.rs b/src/ast/parser.rs index 53b1a84..44066e7 100644 --- a/src/ast/parser.rs +++ b/src/ast/parser.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use crate::ast::lexer::{Lexer, Token, TokenKind}; use crate::ast::types::{Identity, NodeIdentity, Value, Keyword}; -use crate::ast::nodes::{Node, UntypedKind}; +use crate::ast::nodes::{Node, UntypedKind, Context}; pub struct Parser<'a> { lexer: Lexer<'a>, @@ -27,13 +27,17 @@ impl<'a> Parser<'a> { pub fn parse_expression(&mut self) -> Result, String> { match self.peek() { TokenKind::LeftParen => self.parse_list(), - TokenKind::LeftBracket => self.parse_vector(), - // TokenKind::LeftBrace => self.parse_map(), // TODO + TokenKind::LeftBracket => self.parse_vector_literal(), TokenKind::Quote => { let identity = Arc::new(NodeIdentity { location: self.current_token.location }); - self.parse_reader_macro(UntypedKind::Call { - callee: Box::new(self.make_id_node("quote", identity)), - args: vec![], // will be filled + self.advance()?; // consume ' + let expr = self.parse_expression()?; + Ok(Node { + identity: identity.clone(), + kind: UntypedKind::Call { + callee: Box::new(self.make_id_node("quote", identity)), + args: vec![expr], + }, }) } _ => self.parse_atom(), @@ -65,21 +69,24 @@ impl<'a> Parser<'a> { fn parse_list(&mut self) -> Result, String> { let start_loc = self.advance()?.location; // consume '(' + let identity = Arc::new(NodeIdentity { location: start_loc }); if *self.peek() == TokenKind::RightParen { return Err(format!("Empty list () is not a valid expression at {:?}", start_loc)); } // Peek at head to check for special forms + // We parse the first expression to check if it's an identifier "if", "fn", etc. let head = self.parse_expression()?; let result = if let UntypedKind::Identifier(name) = &head.kind { match name.as_ref() { - "if" => self.parse_if(head.identity.clone()), - _ => self.parse_call(head), + "if" => self.parse_if(identity), + "fn" => self.parse_fn(identity), + _ => self.parse_call(head, identity), // Pass head + identity } } else { - self.parse_call(head) + self.parse_call(head, identity) }; self.expect(TokenKind::RightParen)?; @@ -87,6 +94,7 @@ impl<'a> Parser<'a> { } fn parse_if(&mut self, identity: Identity) -> Result, String> { + // 'if' was already consumed as head let cond = Box::new(self.parse_expression()?); let then_br = Box::new(self.parse_expression()?); let mut else_br = None; @@ -101,10 +109,41 @@ impl<'a> Parser<'a> { }) } - fn parse_call(&mut self, callee: Node) -> Result, String> { - let mut args = Vec::new(); - let identity = callee.identity.clone(); + fn parse_fn(&mut self, identity: Identity) -> Result, String> { + // 'fn' was consumed. Next must be [params] vector. + let params = self.parse_param_vector()?; + let body = self.parse_expression()?; + + Ok(Node { + identity, + kind: UntypedKind::Lambda { + params, + body: Arc::new(body), + }, + }) + } + fn parse_param_vector(&mut self) -> Result>, String> { + if *self.peek() != TokenKind::LeftBracket { + return Err(format!("Expected parameter vector [...] for fn, found {:?}", self.peek())); + } + self.advance()?; // consume '[' + + let mut params = Vec::new(); + while *self.peek() != TokenKind::RightBracket { + let token = self.advance()?; + match token.kind { + TokenKind::Identifier(name) => params.push(name), + _ => return Err(format!("Expected identifier in param vector, found {:?}", token.kind)), + } + } + self.expect(TokenKind::RightBracket)?; + Ok(params) + } + + fn parse_call(&mut self, callee: Node, identity: Identity) -> Result, String> { + let mut args = Vec::new(); + while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { args.push(self.parse_expression()?); } @@ -115,17 +154,20 @@ impl<'a> Parser<'a> { }) } - fn parse_vector(&mut self) -> Result, String> { + fn parse_vector_literal(&mut self) -> Result, String> { let token = self.advance()?; // consume '[' let mut elements = Vec::new(); + // Temporary evaluation context for literals + let mut temp_ctx = Context::new(); + while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF { - // Vector elements in Myc are usually just constants in a list let expr = self.parse_expression()?; - // Simple direct eval for literals (Context::new creates fresh scope) - match expr.eval(&mut crate::ast::nodes::Context::new()) { + // Simple direct eval for literals inside vectors (e.g. [1 2 3]) + // In a full compiler, we would return a VectorNode here instead of evaluating immediately. + match expr.eval(&mut temp_ctx) { Ok(val) => elements.push(val), - Err(e) => return Err(format!("Error evaluating vector element: {}", e)), + Err(e) => return Err(format!("Vector literal error: {}", e)), } } @@ -137,23 +179,6 @@ impl<'a> Parser<'a> { }) } - fn parse_reader_macro(&mut self, _template: UntypedKind) -> Result, String> { - let token = self.advance()?; // consume ' - let expr = self.parse_expression()?; - - // 'x -> (quote x) - let identity = Arc::new(NodeIdentity { location: token.location }); - let quote_id = self.make_id_node("quote", identity.clone()); - - Ok(Node { - identity, - kind: UntypedKind::Call { - callee: Box::new(quote_id), - args: vec![expr], - }, - }) - } - fn expect(&mut self, kind: TokenKind) -> Result<(), String> { let token = self.advance()?; if token.kind == kind {