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.
This commit is contained in:
Michael Schimmel
2026-02-17 00:43:09 +01:00
parent 647fc79d28
commit 145e12b811
2 changed files with 94 additions and 53 deletions
+35 -19
View File
@@ -16,7 +16,7 @@ pub trait CustomNode: Debug + Send + Sync {
fn display_name(&self) -> &'static str; fn display_name(&self) -> &'static str;
} }
/// Dynamic Scope for the interpreter (Temporary solution) /// Dynamic Scope for the interpreter
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Scope { pub struct Scope {
values: HashMap<String, Value>, values: HashMap<String, Value>,
@@ -54,10 +54,7 @@ pub struct Context {
impl Context { impl Context {
pub fn new() -> Self { pub fn new() -> Self {
let mut root_scope = Scope::new(None); let mut root_scope = Scope::new(None);
// Register standard library (built-ins)
register_stdlib(&mut root_scope); register_stdlib(&mut root_scope);
Self { Self {
scope: Arc::new(Mutex::new(root_scope)), scope: Arc::new(Mutex::new(root_scope)),
} }
@@ -65,19 +62,15 @@ impl Context {
} }
fn register_stdlib(scope: &mut Scope) { fn register_stdlib(scope: &mut Scope) {
// Helper macro to reduce boilerplate
macro_rules! bin_op { macro_rules! bin_op {
($name:expr, $op:tt) => { ($name:expr, $op:tt) => {
scope.define($name, Value::Function(Arc::new(|args| { scope.define($name, Value::Function(Arc::new(|args| {
if args.len() < 2 { return Value::Void; } // Error handling later if args.len() < 2 { return Value::Void; }
// Fold allow multi-arg: (+ 1 2 3) -> 6
let mut acc = match &args[0] { let mut acc = match &args[0] {
Value::Int(i) => *i as f64, Value::Int(i) => *i as f64,
Value::Float(f) => *f, Value::Float(f) => *f,
_ => return Value::Void, _ => return Value::Void,
}; };
for arg in &args[1..] { for arg in &args[1..] {
let val = match arg { let val = match arg {
Value::Int(i) => *i as f64, Value::Int(i) => *i as f64,
@@ -86,8 +79,6 @@ fn register_stdlib(scope: &mut Scope) {
}; };
acc = acc $op val; acc = acc $op val;
} }
// Return Int if result is integer-like (simplified)
if acc.fract() == 0.0 { if acc.fract() == 0.0 {
Value::Int(acc as i64) Value::Int(acc as i64)
} else { } else {
@@ -102,11 +93,9 @@ fn register_stdlib(scope: &mut Scope) {
bin_op!("*", *); bin_op!("*", *);
bin_op!("/", /); bin_op!("/", /);
// Logic
scope.define(">", Value::Function(Arc::new(|args| { scope.define(">", Value::Function(Arc::new(|args| {
if args.len() != 2 { return Value::Void; } if args.len() != 2 { return Value::Void; }
let (v1, v2) = (&args[0], &args[1]); match (&args[0], &args[1]) {
match (v1, v2) {
(Value::Int(a), Value::Int(b)) => Value::Bool(a > b), (Value::Int(a), Value::Int(b)) => Value::Bool(a > b),
(Value::Float(a), Value::Float(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), (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)] #[derive(Debug)]
pub enum UntypedKind { pub enum UntypedKind {
/// The "..." placeholder for incomplete code
Nop, Nop,
Constant(Value), Constant(Value),
Identifier(Arc<str>), Identifier(Arc<str>),
@@ -129,11 +115,15 @@ pub enum UntypedKind {
then_br: Box<Node<UntypedKind>>, then_br: Box<Node<UntypedKind>>,
else_br: Option<Box<Node<UntypedKind>>>, else_br: Option<Box<Node<UntypedKind>>>,
}, },
// CLOSURE SUPPORT
Lambda {
params: Vec<Arc<str>>,
body: Arc<Node<UntypedKind>>, // Arc for cheap cloning into closure
},
Call { Call {
callee: Box<Node<UntypedKind>>, callee: Box<Node<UntypedKind>>,
args: Vec<Node<UntypedKind>>, args: Vec<Node<UntypedKind>>,
}, },
/// The "escape hatch" for decentralized node types
Extension(Box<dyn CustomNode>), Extension(Box<dyn CustomNode>),
} }
@@ -160,10 +150,36 @@ impl Node<UntypedKind> {
} }
} }
// --- 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<Value, Error>.
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 } => { UntypedKind::Call { callee, args } => {
let func_val = callee.eval(ctx)?; let func_val = callee.eval(ctx)?;
// Evaluate all arguments first (strict evaluation)
let mut eval_args = Vec::with_capacity(args.len()); let mut eval_args = Vec::with_capacity(args.len());
for arg in args { for arg in args {
eval_args.push(arg.eval(ctx)?); eval_args.push(arg.eval(ctx)?);
+59 -34
View File
@@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use crate::ast::lexer::{Lexer, Token, TokenKind}; use crate::ast::lexer::{Lexer, Token, TokenKind};
use crate::ast::types::{Identity, NodeIdentity, Value, Keyword}; 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> { pub struct Parser<'a> {
lexer: Lexer<'a>, lexer: Lexer<'a>,
@@ -27,13 +27,17 @@ impl<'a> Parser<'a> {
pub fn parse_expression(&mut self) -> Result<Node<UntypedKind>, String> { pub fn parse_expression(&mut self) -> Result<Node<UntypedKind>, String> {
match self.peek() { match self.peek() {
TokenKind::LeftParen => self.parse_list(), TokenKind::LeftParen => self.parse_list(),
TokenKind::LeftBracket => self.parse_vector(), TokenKind::LeftBracket => self.parse_vector_literal(),
// TokenKind::LeftBrace => self.parse_map(), // TODO
TokenKind::Quote => { TokenKind::Quote => {
let identity = Arc::new(NodeIdentity { location: self.current_token.location }); let identity = Arc::new(NodeIdentity { location: self.current_token.location });
self.parse_reader_macro(UntypedKind::Call { self.advance()?; // consume '
callee: Box::new(self.make_id_node("quote", identity)), let expr = self.parse_expression()?;
args: vec![], // will be filled Ok(Node {
identity: identity.clone(),
kind: UntypedKind::Call {
callee: Box::new(self.make_id_node("quote", identity)),
args: vec![expr],
},
}) })
} }
_ => self.parse_atom(), _ => self.parse_atom(),
@@ -65,21 +69,24 @@ impl<'a> Parser<'a> {
fn parse_list(&mut self) -> Result<Node<UntypedKind>, String> { fn parse_list(&mut self) -> Result<Node<UntypedKind>, String> {
let start_loc = self.advance()?.location; // consume '(' let start_loc = self.advance()?.location; // consume '('
let identity = Arc::new(NodeIdentity { location: start_loc });
if *self.peek() == TokenKind::RightParen { if *self.peek() == TokenKind::RightParen {
return Err(format!("Empty list () is not a valid expression at {:?}", start_loc)); return Err(format!("Empty list () is not a valid expression at {:?}", start_loc));
} }
// Peek at head to check for special forms // 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 head = self.parse_expression()?;
let result = if let UntypedKind::Identifier(name) = &head.kind { let result = if let UntypedKind::Identifier(name) = &head.kind {
match name.as_ref() { match name.as_ref() {
"if" => self.parse_if(head.identity.clone()), "if" => self.parse_if(identity),
_ => self.parse_call(head), "fn" => self.parse_fn(identity),
_ => self.parse_call(head, identity), // Pass head + identity
} }
} else { } else {
self.parse_call(head) self.parse_call(head, identity)
}; };
self.expect(TokenKind::RightParen)?; self.expect(TokenKind::RightParen)?;
@@ -87,6 +94,7 @@ impl<'a> Parser<'a> {
} }
fn parse_if(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> { fn parse_if(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
// 'if' was already consumed as head
let cond = Box::new(self.parse_expression()?); let cond = Box::new(self.parse_expression()?);
let then_br = Box::new(self.parse_expression()?); let then_br = Box::new(self.parse_expression()?);
let mut else_br = None; let mut else_br = None;
@@ -101,10 +109,41 @@ impl<'a> Parser<'a> {
}) })
} }
fn parse_call(&mut self, callee: Node<UntypedKind>) -> Result<Node<UntypedKind>, String> { fn parse_fn(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
let mut args = Vec::new(); // 'fn' was consumed. Next must be [params] vector.
let identity = callee.identity.clone(); 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<Vec<Arc<str>>, 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<UntypedKind>, identity: Identity) -> Result<Node<UntypedKind>, String> {
let mut args = Vec::new();
while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF {
args.push(self.parse_expression()?); args.push(self.parse_expression()?);
} }
@@ -115,17 +154,20 @@ impl<'a> Parser<'a> {
}) })
} }
fn parse_vector(&mut self) -> Result<Node<UntypedKind>, String> { fn parse_vector_literal(&mut self) -> Result<Node<UntypedKind>, String> {
let token = self.advance()?; // consume '[' let token = self.advance()?; // consume '['
let mut elements = Vec::new(); 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 { 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()?; let expr = self.parse_expression()?;
// Simple direct eval for literals (Context::new creates fresh scope) // Simple direct eval for literals inside vectors (e.g. [1 2 3])
match expr.eval(&mut crate::ast::nodes::Context::new()) { // 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), 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<Node<UntypedKind>, 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> { fn expect(&mut self, kind: TokenKind) -> Result<(), String> {
let token = self.advance()?; let token = self.advance()?;
if token.kind == kind { if token.kind == kind {