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:
+6
-155
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user