Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,10 @@ pub enum Expr {
Binary(BinaryOp, Box<Spanned<Expr>>, Box<Spanned<Expr>>),
/// Unary operation
Unary(UnaryOp, Box<Spanned<Expr>>),
/// Function call
/// Function call by name
Call(String, Vec<Spanned<Expr>>),
/// Call expression: `expr(args)` - for calling closures
CallExpr(Box<Spanned<Expr>>, Vec<Spanned<Expr>>),
/// Unit measurement: `expr measured in unit`
UnitMeasurement(Box<Spanned<Expr>>, String),
/// Gratitude literal: `thanks("name")`
Expand All @@ -252,6 +254,8 @@ pub enum Expr {
Oops(Box<Spanned<Expr>>),
/// Unwrap result: `expr?` or `unwrap(expr)`
Unwrap(Box<Spanned<Expr>>),
/// Lambda/closure: `|x, y| -> expr` or `|x, y| { ... }`
Lambda(LambdaExpr),
}

/// Binary operators
Expand Down Expand Up @@ -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<Spanned<Expr>>),
/// Block body: `|x| { give back x + 1; }`
Block(Vec<Statement>),
}

/// Lambda/closure expression: `|x, y| -> expr` or `|x, y| { ... }`
#[derive(Debug, Clone)]
pub struct LambdaExpr {
pub params: Vec<Parameter>,
pub return_type: Option<Type>,
pub body: LambdaBody,
}

/// Emote tag: `@name(params)`
#[derive(Debug, Clone)]
pub struct EmoteTag {
Expand Down Expand Up @@ -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),
Expand All @@ -361,6 +382,8 @@ pub enum Type {
Optional(Box<Type>),
/// Reference type: &T
Reference(Box<Type>),
/// Function type: (T1, T2) -> R
Function(Vec<Type>, Box<Type>),
}

/// Type definition: `type Name = ...;`
Expand Down
160 changes: 159 additions & 1 deletion src/interpreter/mod.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -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<Value> = args
.iter()
.map(|a| self.evaluate(a))
.collect::<Result<_>>()?;

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<Value>) -> Result<Value> {
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<Value> {
Expand Down Expand Up @@ -605,6 +689,14 @@ impl Interpreter {
}

fn call_function(&mut self, name: &str, args: Vec<Value>) -> Result<Value> {
// 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)
Expand Down Expand Up @@ -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());
}
}
50 changes: 50 additions & 0 deletions src/interpreter/value.rs
Original file line number Diff line number Diff line change
@@ -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<String, Value>,
}

impl CapturedEnv {
pub fn new() -> Self {
Self {
bindings: HashMap::new(),
}
}

pub fn from_map(bindings: HashMap<String, Value>) -> 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<Parameter>,
pub body: LambdaBody,
pub env: Rc<RefCell<CapturedEnv>>,
}

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)]
Expand All @@ -13,6 +56,8 @@ pub enum Value {
Okay(Box<Value>),
/// Result error: `Oops(message)`
Oops(String),
/// First-class function/closure
Function(Closure),
}

impl Value {
Expand All @@ -27,6 +72,7 @@ impl Value {
Value::Unit => false,
Value::Okay(_) => true,
Value::Oops(_) => false,
Value::Function(_) => true,
}
}

Expand Down Expand Up @@ -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, "|{}| -> <closure>", param_names.join(", "))
}
}
}
}
Loading
Loading