Add def and do special forms

Introduces `def` for variable assignment and `do` for block expressions,
enabling multiple statements within a single expression.
This commit is contained in:
Michael Schimmel
2026-02-17 00:47:24 +01:00
parent 145e12b811
commit 21574520d5
2 changed files with 60 additions and 24 deletions
+30 -15
View File
@@ -115,15 +115,23 @@ pub enum UntypedKind {
then_br: Box<Node<UntypedKind>>,
else_br: Option<Box<Node<UntypedKind>>>,
},
// CLOSURE SUPPORT
// DEFINITION (new)
Def {
name: Arc<str>,
value: Box<Node<UntypedKind>>,
},
Lambda {
params: Vec<Arc<str>>,
body: Arc<Node<UntypedKind>>, // Arc for cheap cloning into closure
body: Arc<Node<UntypedKind>>,
},
Call {
callee: Box<Node<UntypedKind>>,
args: Vec<Node<UntypedKind>>,
},
// BLOCK (do ...) to evaluate multiple expressions
Block {
exprs: Vec<Node<UntypedKind>>,
},
Extension(Box<dyn CustomNode>),
}
@@ -148,19 +156,32 @@ impl Node<UntypedKind> {
} else {
Ok(Value::Void)
}
}
// --- CLOSURE IMPLEMENTATION ---
},
// --- DEF (Assignment) ---
UntypedKind::Def { name, value } => {
let val = value.eval(ctx)?;
let mut scope = ctx.scope.lock().unwrap();
scope.define(name, val.clone());
Ok(val) // Return value of definition
},
// --- BLOCK (do) ---
UntypedKind::Block { exprs } => {
let mut last_val = Value::Void;
for expr in exprs {
last_val = expr.eval(ctx)?;
}
Ok(last_val)
},
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());
@@ -168,10 +189,6 @@ impl Node<UntypedKind> {
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)
})))
@@ -179,17 +196,15 @@ impl Node<UntypedKind> {
UntypedKind::Call { callee, args } => {
let func_val = callee.eval(ctx)?;
let mut eval_args = Vec::with_capacity(args.len());
for arg in args {
eval_args.push(arg.eval(ctx)?);
}
match func_val {
Value::Function(f) => Ok(f(eval_args)),
_ => Err(format!("Attempt to call a non-function value: {}", func_val)),
}
}
},
UntypedKind::Extension(ext) => ext.eval(self, ctx),
}
+30 -9
View File
@@ -75,15 +75,15 @@ impl<'a> Parser<'a> {
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(identity),
"fn" => self.parse_fn(identity),
_ => self.parse_call(head, identity), // Pass head + identity
"def" => self.parse_def(identity),
"do" => self.parse_do(identity),
_ => self.parse_call(head, identity),
}
} else {
self.parse_call(head, identity)
@@ -94,7 +94,6 @@ impl<'a> Parser<'a> {
}
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 then_br = Box::new(self.parse_expression()?);
let mut else_br = None;
@@ -109,8 +108,34 @@ impl<'a> Parser<'a> {
})
}
fn parse_def(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
// (def name value)
let name_node = self.parse_expression()?;
let name = match name_node.kind {
UntypedKind::Identifier(s) => s,
_ => return Err("Expected identifier for def name".to_string()),
};
let value = Box::new(self.parse_expression()?);
Ok(Node {
identity,
kind: UntypedKind::Def { name, value },
})
}
fn parse_do(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
let mut exprs = Vec::new();
while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF {
exprs.push(self.parse_expression()?);
}
Ok(Node {
identity,
kind: UntypedKind::Block { exprs },
})
}
fn parse_fn(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
// 'fn' was consumed. Next must be [params] vector.
let params = self.parse_param_vector()?;
let body = self.parse_expression()?;
@@ -157,14 +182,10 @@ impl<'a> Parser<'a> {
fn parse_vector_literal(&mut self) -> Result<Node<UntypedKind>, 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 {
let expr = self.parse_expression()?;
// 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!("Vector literal error: {}", e)),