diff --git a/src/ast/mod.rs b/src/ast/mod.rs index d69a2c6..9ee703c 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -236,8 +236,10 @@ pub enum Expr { Binary(BinaryOp, Box>, Box>), /// Unary operation Unary(UnaryOp, Box>), - /// Function call + /// Function call by name Call(String, Vec>), + /// Call expression: `expr(args)` - for calling closures + CallExpr(Box>, Vec>), /// Unit measurement: `expr measured in unit` UnitMeasurement(Box>, String), /// Gratitude literal: `thanks("name")` @@ -252,6 +254,8 @@ pub enum Expr { Oops(Box>), /// Unwrap result: `expr?` or `unwrap(expr)` Unwrap(Box>), + /// Lambda/closure: `|x, y| -> expr` or `|x, y| { ... }` + Lambda(LambdaExpr), } /// Binary operators @@ -288,6 +292,23 @@ pub enum Literal { Bool(bool), } +/// Lambda expression body +#[derive(Debug, Clone)] +pub enum LambdaBody { + /// Expression body: `|x| -> x + 1` + Expr(Box>), + /// Block body: `|x| { give back x + 1; }` + Block(Vec), +} + +/// Lambda/closure expression: `|x, y| -> expr` or `|x, y| { ... }` +#[derive(Debug, Clone)] +pub struct LambdaExpr { + pub params: Vec, + pub return_type: Option, + pub body: LambdaBody, +} + /// Emote tag: `@name(params)` #[derive(Debug, Clone)] pub struct EmoteTag { @@ -351,7 +372,7 @@ pub enum PragmaDirective { } /// Type annotation -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Type { /// Basic types: String, Int, Float, Bool, or custom Basic(String), @@ -361,6 +382,8 @@ pub enum Type { Optional(Box), /// Reference type: &T Reference(Box), + /// Function type: (T1, T2) -> R + Function(Vec, Box), } /// Type definition: `type Name = ...;` diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 7efab24..52c75a7 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -1,10 +1,12 @@ mod value; -pub use value::Value; +pub use value::{CapturedEnv, Closure, Value}; use crate::ast::*; +use std::cell::RefCell; use std::collections::HashMap; use std::io::{self, Write}; +use std::rc::Rc; use thiserror::Error; #[derive(Error, Debug)] @@ -474,7 +476,89 @@ impl Interpreter { other => Ok(other), // Non-result values pass through } } + Expr::Lambda(lambda) => { + // Capture the current environment + let captured = self.capture_environment(); + Ok(Value::Function(Closure { + params: lambda.params.clone(), + body: lambda.body.clone(), + env: Rc::new(RefCell::new(captured)), + })) + } + Expr::CallExpr(callee, args) => { + let callee_val = self.evaluate(callee)?; + let arg_values: Vec = args + .iter() + .map(|a| self.evaluate(a)) + .collect::>()?; + + match callee_val { + Value::Function(closure) => self.call_closure(&closure, arg_values), + _ => Err(RuntimeError::TypeError("Cannot call non-function value".into())), + } + } + } + } + + fn capture_environment(&self) -> CapturedEnv { + // Flatten all scopes into a single map for the closure + let mut bindings = HashMap::new(); + for scope in &self.env.scopes { + for (name, value) in scope { + bindings.insert(name.clone(), value.clone()); + } + } + CapturedEnv::from_map(bindings) + } + + fn call_closure(&mut self, closure: &Closure, args: Vec) -> Result { + if closure.params.len() != args.len() { + return Err(RuntimeError::ArityMismatch { + expected: closure.params.len(), + got: args.len(), + }); + } + + // Save current environment + let saved_env = self.env.clone(); + + // Create new environment with captured bindings + self.env = Environment::new(); + + // Add captured bindings + let captured = closure.env.borrow(); + for (name, value) in &captured.bindings { + self.env.define(name.clone(), value.clone()); + } + + // Push new scope for parameters + self.env.push_scope(); + for (param, arg) in closure.params.iter().zip(args) { + self.env.define(param.name.clone(), arg); } + + // Execute the closure body + let result = match &closure.body { + LambdaBody::Expr(expr) => self.evaluate(expr), + LambdaBody::Block(stmts) => { + let mut result = Value::Unit; + for stmt in stmts { + match self.execute_statement(stmt)? { + ControlFlow::Return(v) => { + result = v; + break; + } + ControlFlow::Continue => {} + } + } + Ok(result) + } + }; + + // Restore environment + self.env = saved_env; + + result } fn apply_index(&self, target: Value, index: Value) -> Result { @@ -605,6 +689,14 @@ impl Interpreter { } fn call_function(&mut self, name: &str, args: Vec) -> Result { + // First, check if name refers to a variable holding a closure + if let Some(value) = self.env.get(name).cloned() { + if let Value::Function(closure) = value { + return self.call_closure(&closure, args); + } + } + + // Otherwise, look up as a named function let func = self .functions .get(name) @@ -904,4 +996,70 @@ mod tests { "#; assert!(run_program(source).is_ok()); } + + #[test] + fn test_lambda_expression() { + let source = r#" + to main() { + remember add = |x, y| -> x + y; + remember result = add(3, 4); + print(result); + } + "#; + assert!(run_program(source).is_ok()); + } + + #[test] + fn test_lambda_block() { + let source = r#" + to main() { + remember greet = |name| { + give back "Hello, " + name; + }; + remember msg = greet("World"); + print(msg); + } + "#; + assert!(run_program(source).is_ok()); + } + + #[test] + fn test_closure_captures() { + let source = r#" + to main() { + remember multiplier = 10; + remember times_ten = |x| -> x * multiplier; + remember result = times_ten(5); + print(result); + } + "#; + assert!(run_program(source).is_ok()); + } + + #[test] + fn test_higher_order_function() { + let source = r#" + to apply(f, x: Int) -> Int { + give back f(x); + } + to main() { + remember double = |x| -> x * 2; + remember result = apply(double, 21); + print(result); + } + "#; + assert!(run_program(source).is_ok()); + } + + #[test] + fn test_lambda_no_params() { + let source = r#" + to main() { + remember get_five = || -> 5; + remember result = get_five(); + print(result); + } + "#; + assert!(run_program(source).is_ok()); + } } diff --git a/src/interpreter/value.rs b/src/interpreter/value.rs index 890e715..67e47ef 100644 --- a/src/interpreter/value.rs +++ b/src/interpreter/value.rs @@ -1,4 +1,47 @@ +use crate::ast::{LambdaBody, Parameter}; +use std::collections::HashMap; use std::fmt; +use std::rc::Rc; +use std::cell::RefCell; + +/// Captured environment for closures +#[derive(Debug, Clone)] +pub struct CapturedEnv { + pub bindings: HashMap, +} + +impl CapturedEnv { + pub fn new() -> Self { + Self { + bindings: HashMap::new(), + } + } + + pub fn from_map(bindings: HashMap) -> Self { + Self { bindings } + } +} + +impl Default for CapturedEnv { + fn default() -> Self { + Self::new() + } +} + +/// A closure captures its environment at creation time +#[derive(Debug, Clone)] +pub struct Closure { + pub params: Vec, + pub body: LambdaBody, + pub env: Rc>, +} + +impl PartialEq for Closure { + fn eq(&self, _other: &Self) -> bool { + // Closures are never equal (like function identity) + false + } +} /// Runtime value in WokeLang #[derive(Debug, Clone, PartialEq)] @@ -13,6 +56,8 @@ pub enum Value { Okay(Box), /// Result error: `Oops(message)` Oops(String), + /// First-class function/closure + Function(Closure), } impl Value { @@ -27,6 +72,7 @@ impl Value { Value::Unit => false, Value::Okay(_) => true, Value::Oops(_) => false, + Value::Function(_) => true, } } @@ -70,6 +116,10 @@ impl fmt::Display for Value { Value::Unit => write!(f, "()"), Value::Okay(v) => write!(f, "Okay({})", v), Value::Oops(e) => write!(f, "Oops(\"{}\")", e), + Value::Function(closure) => { + let param_names: Vec<_> = closure.params.iter().map(|p| p.name.as_str()).collect(); + write!(f, "|{}| -> ", param_names.join(", ")) + } } } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index e328714..aa3bfe5 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1026,6 +1026,24 @@ impl<'src> Parser<'src> { self.expect(Token::RBracket)?; let span = expr.span.start..self.previous_span().end; expr = Spanned::new(Expr::Index(Box::new(expr), Box::new(index)), span); + } else if self.check(&Token::LParen) { + // Call expression: expr(args) - for calling closures/lambdas + // Only if expr is not an identifier (those are handled in parse_primary) + if matches!(expr.node, Expr::Identifier(_)) { + break; // Let parse_primary handle named function calls + } + self.advance(); + let mut args = Vec::new(); + if !self.check(&Token::RParen) { + args.push(self.parse_expression()?); + while self.check(&Token::Comma) { + self.advance(); + args.push(self.parse_expression()?); + } + } + self.expect(Token::RParen)?; + let span = expr.span.start..self.previous_span().end; + expr = Spanned::new(Expr::CallExpr(Box::new(expr), args), span); } else if self.check(&Token::Measured) { // Unit measurement: expr measured in unit self.advance(); @@ -1139,10 +1157,65 @@ impl<'src> Parser<'src> { Ok(Spanned::new(Expr::Identifier(name), start..end)) } } + Some(Token::Pipe) => { + // Lambda expression: |x, y| -> expr or |x, y| { ... } + self.advance(); // consume first '|' + let params = self.parse_lambda_params()?; + self.expect(Token::Pipe)?; // consume closing '|' + + // Check for optional return type annotation + let return_type = if self.check(&Token::Colon) { + self.advance(); + Some(self.parse_type()?) + } else { + None + }; + + // Parse body: either -> expr or { statements } + let body = if self.check(&Token::Arrow) || self.check(&Token::AsciiArrow) { + self.advance(); + let expr = self.parse_expression()?; + LambdaBody::Expr(Box::new(expr)) + } else if self.check(&Token::LBrace) { + self.advance(); + let stmts = self.parse_statement_list()?; + self.expect(Token::RBrace)?; + LambdaBody::Block(stmts) + } else { + return Err(self.error("Expected -> or { after lambda parameters")); + }; + + let end = self.previous_span().end; + Ok(Spanned::new( + Expr::Lambda(LambdaExpr { + params, + return_type, + body, + }), + start..end, + )) + } _ => Err(self.error("Expected expression")), } } + fn parse_lambda_params(&mut self) -> Result, ParseError> { + let mut params = Vec::new(); + + // Empty params: || + if self.check(&Token::Pipe) { + return Ok(params); + } + + params.push(self.parse_parameter()?); + while self.check(&Token::Comma) { + self.advance(); + params.push(self.parse_parameter()?); + } + + Ok(params) + } + // === Helper Methods === fn peek(&self) -> Option<&Token> {