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:
+35
-19
@@ -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<String, Value>,
|
||||
@@ -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<str>),
|
||||
@@ -129,11 +115,15 @@ pub enum UntypedKind {
|
||||
then_br: 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 {
|
||||
callee: Box<Node<UntypedKind>>,
|
||||
args: Vec<Node<UntypedKind>>,
|
||||
},
|
||||
/// The "escape hatch" for decentralized node types
|
||||
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 } => {
|
||||
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)?);
|
||||
|
||||
Reference in New Issue
Block a user