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::rc::Rc;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use crate::ast::nodes::{Node, UntypedKind};
|
use crate::ast::nodes::{Node, UntypedKind};
|
||||||
use crate::ast::compiler::bound_nodes::{BoundKind, Address};
|
use crate::ast::compiler::bound_nodes::{BoundKind, Address};
|
||||||
use crate::ast::types::{Identity, StaticType};
|
use crate::ast::types::{Identity, StaticType, Value};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct LocalInfo {
|
struct LocalInfo {
|
||||||
@@ -229,6 +229,44 @@ impl Binder {
|
|||||||
}
|
}
|
||||||
Ok(self.make_node(node.identity.clone(), BoundKind::Block { exprs: bound_exprs }, last_ty))
|
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(_) => {
|
UntypedKind::Extension(_) => {
|
||||||
Err("Custom extensions not supported in Binder yet".to_string())
|
Err("Custom extensions not supported in Binder yet".to_string())
|
||||||
|
|||||||
@@ -51,6 +51,14 @@ pub enum BoundKind {
|
|||||||
exprs: Vec<Node<BoundKind, StaticType>>,
|
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)
|
// Extension points (need to be adapted for bound nodes if they use variables)
|
||||||
// For now, we assume extensions are self-contained or handled dynamically.
|
// For now, we assume extensions are self-contained or handled dynamically.
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-155
@@ -1,6 +1,4 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use crate::ast::types::{Identity, Value};
|
use crate::ast::types::{Identity, Value};
|
||||||
|
|
||||||
@@ -14,75 +12,9 @@ pub struct Node<K, T = ()> {
|
|||||||
|
|
||||||
/// The base for custom node types (extensions)
|
/// The base for custom node types (extensions)
|
||||||
pub trait CustomNode: Debug {
|
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;
|
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)]
|
#[derive(Debug)]
|
||||||
pub enum UntypedKind {
|
pub enum UntypedKind {
|
||||||
Nop,
|
Nop,
|
||||||
@@ -112,92 +44,11 @@ pub enum UntypedKind {
|
|||||||
Block {
|
Block {
|
||||||
exprs: Vec<Node<UntypedKind>>,
|
exprs: Vec<Node<UntypedKind>>,
|
||||||
},
|
},
|
||||||
|
Tuple {
|
||||||
|
elements: Vec<Node<UntypedKind>>,
|
||||||
|
},
|
||||||
|
Map {
|
||||||
|
entries: Vec<(Node<UntypedKind>, Node<UntypedKind>)>,
|
||||||
|
},
|
||||||
Extension(Box<dyn CustomNode>),
|
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 std::rc::Rc;
|
||||||
use crate::ast::lexer::{Lexer, Token, TokenKind};
|
use crate::ast::lexer::{Lexer, Token, TokenKind};
|
||||||
use crate::ast::types::{Identity, NodeIdentity, Value, Keyword};
|
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> {
|
pub struct Parser<'a> {
|
||||||
lexer: Lexer<'a>,
|
lexer: Lexer<'a>,
|
||||||
@@ -202,29 +202,24 @@ impl<'a> Parser<'a> {
|
|||||||
fn parse_vector_literal(&mut self) -> Result<Node<UntypedKind>, String> {
|
fn parse_vector_literal(&mut self) -> Result<Node<UntypedKind>, String> {
|
||||||
let token = self.advance()?;
|
let token = self.advance()?;
|
||||||
let mut elements = Vec::new();
|
let mut elements = Vec::new();
|
||||||
let mut temp_ctx = Context::new();
|
|
||||||
|
|
||||||
while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF {
|
while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF {
|
||||||
let expr = self.parse_expression()?;
|
let expr = self.parse_expression()?;
|
||||||
match expr.eval(&mut temp_ctx) {
|
elements.push(expr);
|
||||||
Ok(val) => elements.push(val),
|
|
||||||
Err(e) => return Err(format!("Vector literal error: {}", e)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.expect(TokenKind::RightBracket)?;
|
self.expect(TokenKind::RightBracket)?;
|
||||||
|
|
||||||
Ok(Node {
|
Ok(Node {
|
||||||
identity: Rc::new(NodeIdentity { location: token.location }),
|
identity: Rc::new(NodeIdentity { location: token.location }),
|
||||||
kind: UntypedKind::Constant(Value::List(Rc::new(elements))),
|
kind: UntypedKind::Tuple { elements },
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_map_literal(&mut self) -> Result<Node<UntypedKind>, String> {
|
fn parse_map_literal(&mut self) -> Result<Node<UntypedKind>, String> {
|
||||||
let token = self.advance()?;
|
let token = self.advance()?;
|
||||||
let mut map = std::collections::HashMap::new();
|
let mut entries = Vec::new();
|
||||||
let mut temp_ctx = Context::new();
|
|
||||||
|
|
||||||
while *self.peek() != TokenKind::RightBrace {
|
while *self.peek() != TokenKind::RightBrace {
|
||||||
if *self.peek() == TokenKind::EOF {
|
if *self.peek() == TokenKind::EOF {
|
||||||
@@ -232,24 +227,26 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let key_node = self.parse_expression()?;
|
let key_node = self.parse_expression()?;
|
||||||
let key = match key_node.eval(&mut temp_ctx)? {
|
// We check for keyword kind here (syntactically) to avoid ambiguity, but
|
||||||
Value::Keyword(k) => k,
|
// strictly we could allow any expression and check at runtime.
|
||||||
_ => return Err("Map keys must be keywords".to_string()),
|
// 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 {
|
if *self.peek() == TokenKind::RightBrace {
|
||||||
return Err("Map literal must have even number of forms".to_string());
|
return Err("Map literal must have even number of forms".to_string());
|
||||||
}
|
}
|
||||||
let val_node = self.parse_expression()?;
|
let val_node = self.parse_expression()?;
|
||||||
let val = val_node.eval(&mut temp_ctx)?;
|
|
||||||
|
entries.push((key_node, val_node));
|
||||||
map.insert(key, val);
|
|
||||||
}
|
}
|
||||||
self.expect(TokenKind::RightBrace)?;
|
self.expect(TokenKind::RightBrace)?;
|
||||||
|
|
||||||
Ok(Node {
|
Ok(Node {
|
||||||
identity: Rc::new(NodeIdentity { location: token.location }),
|
identity: Rc::new(NodeIdentity { location: token.location }),
|
||||||
kind: UntypedKind::Constant(Value::Record(Rc::new(map))),
|
kind: UntypedKind::Map { entries },
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-3
@@ -1,5 +1,6 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use crate::ast::compiler::bound_nodes::{Address, BoundKind};
|
use crate::ast::compiler::bound_nodes::{Address, BoundKind};
|
||||||
use crate::ast::nodes::Node;
|
use crate::ast::nodes::Node;
|
||||||
@@ -28,9 +29,6 @@ struct CallFrame {
|
|||||||
|
|
||||||
pub struct VM {
|
pub struct VM {
|
||||||
stack: Vec<Value>,
|
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>>>,
|
globals: Rc<RefCell<Vec<Value>>>,
|
||||||
frames: Vec<CallFrame>,
|
frames: Vec<CallFrame>,
|
||||||
}
|
}
|
||||||
@@ -151,6 +149,29 @@ impl VM {
|
|||||||
},
|
},
|
||||||
_ => Err(format!("Attempt to call non-function: {}", func_val)),
|
_ => 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