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:
@@ -1,9 +1,9 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, BTreeMap};
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use crate::ast::nodes::{Node, UntypedKind};
|
||||
use crate::ast::compiler::bound_nodes::{BoundKind, Address};
|
||||
use crate::ast::types::{Identity, StaticType};
|
||||
use crate::ast::types::{Identity, StaticType, Value};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct LocalInfo {
|
||||
@@ -229,6 +229,44 @@ impl Binder {
|
||||
}
|
||||
Ok(self.make_node(node.identity.clone(), BoundKind::Block { exprs: bound_exprs }, last_ty))
|
||||
},
|
||||
|
||||
UntypedKind::Tuple { elements } => {
|
||||
let mut bound_elems = Vec::new();
|
||||
for e in elements {
|
||||
bound_elems.push(self.bind(e)?);
|
||||
}
|
||||
// For now, tuple type is List(Any)
|
||||
let ty = StaticType::List(Box::new(StaticType::Any));
|
||||
Ok(self.make_node(node.identity.clone(), BoundKind::Tuple { elements: bound_elems }, ty))
|
||||
},
|
||||
|
||||
UntypedKind::Map { entries } => {
|
||||
let mut bound_entries = Vec::new();
|
||||
let mut key_types = BTreeMap::new(); // For Record type inference if keys are constant keywords
|
||||
|
||||
for (k, v) in entries {
|
||||
// Keys must be compile-time constants for now (for Record type),
|
||||
// or at least we enforce keywords.
|
||||
// But `bind` processes runtime expressions too.
|
||||
// Let's bind both.
|
||||
let bound_k = self.bind(k)?;
|
||||
let bound_v = self.bind(v)?;
|
||||
|
||||
// If key is a constant keyword, we can build a static record type.
|
||||
if let BoundKind::Constant(Value::Keyword(kw)) = &bound_k.kind {
|
||||
key_types.insert(*kw, bound_v.ty.clone());
|
||||
}
|
||||
|
||||
bound_entries.push((bound_k, bound_v));
|
||||
}
|
||||
|
||||
// If all keys are known, we produce a Record type. Else Map(Any, Any) which we don't have yet.
|
||||
// We default to Record(Any) if keys are dynamic (not supported well yet) or known.
|
||||
// Since our parser enforced keywords, we assume we have a Record.
|
||||
let ty = StaticType::Record(Rc::new(key_types));
|
||||
|
||||
Ok(self.make_node(node.identity.clone(), BoundKind::Map { entries: bound_entries }, ty))
|
||||
},
|
||||
|
||||
UntypedKind::Extension(_) => {
|
||||
Err("Custom extensions not supported in Binder yet".to_string())
|
||||
|
||||
@@ -51,6 +51,14 @@ pub enum BoundKind {
|
||||
exprs: Vec<Node<BoundKind, StaticType>>,
|
||||
},
|
||||
|
||||
// NEW
|
||||
Tuple {
|
||||
elements: Vec<Node<BoundKind, StaticType>>,
|
||||
},
|
||||
Map {
|
||||
entries: Vec<(Node<BoundKind, StaticType>, Node<BoundKind, StaticType>)>,
|
||||
},
|
||||
|
||||
// Extension points (need to be adapted for bound nodes if they use variables)
|
||||
// For now, we assume extensions are self-contained or handled dynamically.
|
||||
}
|
||||
|
||||
+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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-17
@@ -1,7 +1,7 @@
|
||||
use std::rc::Rc;
|
||||
use crate::ast::lexer::{Lexer, Token, TokenKind};
|
||||
use crate::ast::types::{Identity, NodeIdentity, Value, Keyword};
|
||||
use crate::ast::nodes::{Node, UntypedKind, Context};
|
||||
use crate::ast::nodes::{Node, UntypedKind};
|
||||
|
||||
pub struct Parser<'a> {
|
||||
lexer: Lexer<'a>,
|
||||
@@ -202,29 +202,24 @@ impl<'a> Parser<'a> {
|
||||
fn parse_vector_literal(&mut self) -> Result<Node<UntypedKind>, String> {
|
||||
let token = self.advance()?;
|
||||
let mut elements = Vec::new();
|
||||
let mut temp_ctx = Context::new();
|
||||
|
||||
while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF {
|
||||
let expr = self.parse_expression()?;
|
||||
match expr.eval(&mut temp_ctx) {
|
||||
Ok(val) => elements.push(val),
|
||||
Err(e) => return Err(format!("Vector literal error: {}", e)),
|
||||
}
|
||||
elements.push(expr);
|
||||
}
|
||||
|
||||
self.expect(TokenKind::RightBracket)?;
|
||||
|
||||
Ok(Node {
|
||||
identity: Rc::new(NodeIdentity { location: token.location }),
|
||||
kind: UntypedKind::Constant(Value::List(Rc::new(elements))),
|
||||
kind: UntypedKind::Tuple { elements },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_map_literal(&mut self) -> Result<Node<UntypedKind>, String> {
|
||||
let token = self.advance()?;
|
||||
let mut map = std::collections::HashMap::new();
|
||||
let mut temp_ctx = Context::new();
|
||||
let mut entries = Vec::new();
|
||||
|
||||
while *self.peek() != TokenKind::RightBrace {
|
||||
if *self.peek() == TokenKind::EOF {
|
||||
@@ -232,24 +227,26 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
|
||||
let key_node = self.parse_expression()?;
|
||||
let key = match key_node.eval(&mut temp_ctx)? {
|
||||
Value::Keyword(k) => k,
|
||||
_ => return Err("Map keys must be keywords".to_string()),
|
||||
};
|
||||
// We check for keyword kind here (syntactically) to avoid ambiguity, but
|
||||
// strictly we could allow any expression and check at runtime.
|
||||
// Delphi enforces keywords. We can do minimal check here.
|
||||
match &key_node.kind {
|
||||
UntypedKind::Constant(Value::Keyword(_)) => {},
|
||||
_ => return Err("Map keys must be keywords (syntactically)".to_string()),
|
||||
}
|
||||
|
||||
if *self.peek() == TokenKind::RightBrace {
|
||||
return Err("Map literal must have even number of forms".to_string());
|
||||
}
|
||||
let val_node = self.parse_expression()?;
|
||||
let val = val_node.eval(&mut temp_ctx)?;
|
||||
|
||||
map.insert(key, val);
|
||||
|
||||
entries.push((key_node, val_node));
|
||||
}
|
||||
self.expect(TokenKind::RightBrace)?;
|
||||
|
||||
Ok(Node {
|
||||
identity: Rc::new(NodeIdentity { location: token.location }),
|
||||
kind: UntypedKind::Constant(Value::Record(Rc::new(map))),
|
||||
kind: UntypedKind::Map { entries },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
+24
-3
@@ -1,5 +1,6 @@
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::any::Any;
|
||||
use crate::ast::compiler::bound_nodes::{Address, BoundKind};
|
||||
use crate::ast::nodes::Node;
|
||||
@@ -28,9 +29,6 @@ struct CallFrame {
|
||||
|
||||
pub struct VM {
|
||||
stack: Vec<Value>,
|
||||
// Globals are mutable shared state within the VM thread.
|
||||
// However, if we move to frozen roots, this might change to a read-only structure.
|
||||
// For now, mutable RefCell Vec is fine for single threaded execution.
|
||||
globals: Rc<RefCell<Vec<Value>>>,
|
||||
frames: Vec<CallFrame>,
|
||||
}
|
||||
@@ -151,6 +149,29 @@ impl VM {
|
||||
},
|
||||
_ => Err(format!("Attempt to call non-function: {}", func_val)),
|
||||
}
|
||||
},
|
||||
|
||||
BoundKind::Tuple { elements } => {
|
||||
let mut vals = Vec::with_capacity(elements.len());
|
||||
for e in elements {
|
||||
vals.push(self.eval(e)?);
|
||||
}
|
||||
Ok(Value::List(Rc::new(vals)))
|
||||
},
|
||||
|
||||
BoundKind::Map { entries } => {
|
||||
let mut map = HashMap::new();
|
||||
for (k, v) in entries {
|
||||
let key = self.eval(k)?;
|
||||
let val = self.eval(v)?;
|
||||
|
||||
if let Value::Keyword(kw) = key {
|
||||
map.insert(kw, val);
|
||||
} else {
|
||||
return Err(format!("Map key must be keyword, got {}", key));
|
||||
}
|
||||
}
|
||||
Ok(Value::Record(Rc::new(map)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user