Add tuple and map literals

This commit introduces support for tuple and map literals in the AST.
Tuples are now represented by `UntypedKind::Tuple` and maps by
`UntypedKind::Map`.
The `Binder` has been updated to correctly handle these new node types
and infer their types.
The `VM` now also supports evaluating tuple and map literals.
This commit is contained in:
Michael Schimmel
2026-02-17 13:07:17 +01:00
parent d55422272b
commit 9afde5a301
5 changed files with 92 additions and 177 deletions
+6 -155
View File
@@ -1,6 +1,4 @@
use std::rc::Rc;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt::Debug;
use crate::ast::types::{Identity, Value};
@@ -14,75 +12,9 @@ pub struct Node<K, T = ()> {
/// The base for custom node types (extensions)
pub trait CustomNode: Debug {
fn eval(&self, _ctx: &mut Context) -> Result<Value, String> {
Err("CustomNode eval not implemented".to_string())
}
fn display_name(&self) -> &'static str;
}
/// Dynamic Scope for the simple interpreter (Legacy / Macro expansion)
#[derive(Debug, Clone)]
pub struct Scope {
values: HashMap<String, Value>,
parent: Option<Rc<RefCell<Scope>>>,
}
impl Scope {
pub fn new(parent: Option<Rc<RefCell<Scope>>>) -> Self {
Self {
values: HashMap::new(),
parent,
}
}
pub fn define(&mut self, name: &str, value: Value) {
self.values.insert(name.to_string(), value);
}
pub fn assign(&mut self, name: &str, value: Value) -> Result<(), String> {
if self.values.contains_key(name) {
self.values.insert(name.to_string(), value);
return Ok(());
}
if let Some(parent) = &self.parent {
return parent.borrow_mut().assign(name, value);
}
Err(format!("Cannot assign to undefined variable '{}'", name))
}
pub fn resolve(&self, name: &str) -> Option<Value> {
if let Some(val) = self.values.get(name) {
return Some(val.clone());
}
if let Some(parent) = &self.parent {
return parent.borrow().resolve(name);
}
None
}
}
/// Evaluation Context (Scope management)
pub struct Context {
pub scope: Rc<RefCell<Scope>>,
}
impl Default for Context {
fn default() -> Self {
Self::new()
}
}
impl Context {
pub fn new() -> Self {
let root_scope = Scope::new(None);
// Registering stdlib directly in Scope for simple eval is skipped to keep it clean.
// If needed, we can re-add it, but VM is the primary execution engine.
Self {
scope: Rc::new(RefCell::new(root_scope)),
}
}
}
#[derive(Debug)]
pub enum UntypedKind {
Nop,
@@ -112,92 +44,11 @@ pub enum UntypedKind {
Block {
exprs: Vec<Node<UntypedKind>>,
},
Tuple {
elements: Vec<Node<UntypedKind>>,
},
Map {
entries: Vec<(Node<UntypedKind>, Node<UntypedKind>)>,
},
Extension(Box<dyn CustomNode>),
}
impl Node<UntypedKind> {
// This eval is a simple tree-walker, distinct from VM execution.
// It's useful for macros or constant folding.
pub fn eval(&self, ctx: &mut Context) -> Result<Value, String> {
match &self.kind {
UntypedKind::Nop => Ok(Value::Void),
UntypedKind::Constant(v) => Ok(v.clone()),
UntypedKind::Identifier(name) => {
let scope = ctx.scope.borrow();
scope.resolve(name)
.ok_or_else(|| format!("Undefined variable: '{}'", name))
},
UntypedKind::If { cond, then_br, else_br } => {
let cond_val = cond.eval(ctx)?;
if cond_val.is_truthy() {
then_br.eval(ctx)
} else if let Some(eb) = else_br {
eb.eval(ctx)
} else {
Ok(Value::Void)
}
},
UntypedKind::Def { name, value } => {
let val = value.eval(ctx)?;
let mut scope = ctx.scope.borrow_mut();
scope.define(name, val.clone());
Ok(val)
},
UntypedKind::Assign { target, value } => {
let val = value.eval(ctx)?;
if let UntypedKind::Identifier(name) = &target.kind {
let mut scope = ctx.scope.borrow_mut();
scope.assign(name, val.clone())?;
Ok(val)
} else {
Err("Assignment target must be an identifier".to_string())
}
},
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(Rc::new(move |args| {
let mut new_scope = Scope::new(Some(captured_scope.clone()));
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);
}
}
let mut sub_ctx = Context { scope: Rc::new(RefCell::new(new_scope)) };
body.eval(&mut sub_ctx).unwrap_or(Value::Void)
})))
},
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(ctx),
}
}
}