diff --git a/src/ast/compiler/binder.rs b/src/ast/compiler/binder.rs index c81f84c..d6db9f2 100644 --- a/src/ast/compiler/binder.rs +++ b/src/ast/compiler/binder.rs @@ -2,11 +2,17 @@ use std::collections::HashMap; use std::sync::{Arc, Mutex}; use crate::ast::nodes::{Node, UntypedKind}; use crate::ast::compiler::bound_nodes::{BoundKind, Address}; -use crate::ast::types::Identity; +use crate::ast::types::{Identity, StaticType}; + +#[derive(Debug, Clone)] +struct LocalInfo { + slot: u32, + ty: StaticType, +} #[derive(Debug, Clone)] struct CompilerScope { - locals: HashMap, + locals: HashMap, slot_count: u32, } @@ -18,23 +24,21 @@ impl CompilerScope { } } - fn define(&mut self, name: &str) -> u32 { + fn define(&mut self, name: &str, ty: StaticType) -> u32 { let slot = self.slot_count; - self.locals.insert(name.to_string(), slot); + self.locals.insert(name.to_string(), LocalInfo { slot, ty }); self.slot_count += 1; slot } - fn resolve(&self, name: &str) -> Option { - self.locals.get(name).copied() + fn resolve(&self, name: &str) -> Option { + self.locals.get(name).cloned() } } struct FunctionCompiler { scope: CompilerScope, - // Upvalues capture variables from the *immediate* enclosing function. - // The address stored here refers to a Local/Upvalue in the Parent Scope. - upvalues: Vec
, + upvalues: Vec<(Address, StaticType)>, } impl FunctionCompiler { @@ -45,23 +49,23 @@ impl FunctionCompiler { } } - fn add_upvalue(&mut self, addr: Address) -> u32 { - if let Some(idx) = self.upvalues.iter().position(|&a| a == addr) { + fn add_upvalue(&mut self, addr: Address, ty: StaticType) -> u32 { + if let Some(idx) = self.upvalues.iter().position(|(a, _)| *a == addr) { return idx as u32; } let idx = self.upvalues.len() as u32; - self.upvalues.push(addr); + self.upvalues.push((addr, ty)); idx } } pub struct Binder { functions: Vec, - globals: Arc>>, + globals: Arc>>, } impl Binder { - pub fn new(globals: Arc>>) -> Self { + pub fn new(globals: Arc>>) -> Self { let mut binder = Self { functions: Vec::new(), globals, @@ -70,53 +74,73 @@ impl Binder { binder } - pub fn bind(&mut self, node: &Node) -> Result, String> { + pub fn bind(&mut self, node: &Node) -> Result, String> { match &node.kind { - UntypedKind::Nop => Ok(self.make_node(node.identity.clone(), BoundKind::Nop)), - UntypedKind::Constant(v) => Ok(self.make_node(node.identity.clone(), BoundKind::Constant(v.clone()))), + UntypedKind::Nop => Ok(self.make_node(node.identity.clone(), BoundKind::Nop, StaticType::Void)), + UntypedKind::Constant(v) => { + let ty = v.static_type(); + Ok(self.make_node(node.identity.clone(), BoundKind::Constant(v.clone()), ty)) + }, UntypedKind::Identifier(name) => { - let addr = self.resolve_variable(name)?; - Ok(self.make_node(node.identity.clone(), BoundKind::Get(addr))) + let (addr, ty) = self.resolve_variable(name)?; + Ok(self.make_node(node.identity.clone(), BoundKind::Get(addr), ty)) }, UntypedKind::If { cond, then_br, else_br } => { - let cond = Box::new(self.bind(cond)?); - let then_br = Box::new(self.bind(then_br)?); - let else_br = match else_br { - Some(e) => Some(Box::new(self.bind(e)?)), - None => None, + let cond = self.bind(cond)?; + // Type check condition: Must be Bool or Any + if cond.ty != StaticType::Bool && cond.ty != StaticType::Any { + return Err(format!("Condition must be boolean, found {:?}", cond.ty)); + } + + let then_br = self.bind(then_br)?; + let mut else_br_bound = None; + let final_ty = if let Some(e) = else_br { + let eb = self.bind(e)?; + let ty = if then_br.ty == eb.ty { + then_br.ty.clone() + } else { + StaticType::Any // Common supertype logic could go here + }; + else_br_bound = Some(Box::new(eb)); + ty + } else { + StaticType::Void // If without else returns Void if false }; - Ok(self.make_node(node.identity.clone(), BoundKind::If { cond, then_br, else_br })) + + Ok(self.make_node(node.identity.clone(), BoundKind::If { + cond: Box::new(cond), + then_br: Box::new(then_br), + else_br: else_br_bound + }, final_ty)) }, UntypedKind::Def { name, value } => { - // Determine scope: Global (root) or Local (inside fn)? + let val_node = self.bind(value)?; + let ty = val_node.ty.clone(); + if self.functions.len() == 1 { - // Global Definition - let val_node = self.bind(value)?; let mut globals = self.globals.lock().unwrap(); - let idx = if let Some(&idx) = globals.get(name.as_ref()) { - idx + let idx = if let Some((idx, _)) = globals.get(name.as_ref()) { + *idx } else { let idx = globals.len() as u32; - globals.insert(name.to_string(), idx); + globals.insert(name.to_string(), (idx, ty.clone())); idx }; - Ok(self.make_node(node.identity.clone(), BoundKind::DefGlobal { global_index: idx, value: Box::new(val_node) })) + Ok(self.make_node(node.identity.clone(), BoundKind::DefGlobal { + global_index: idx, + value: Box::new(val_node) + }, ty)) } else { - // Local Variable Definition - // We bind the value *before* defining the name to avoid self-reference in init? - // Usually yes: (def x 10) -> bind(10), define(x). - let val_node = self.bind(value)?; let current_fn = self.functions.last_mut().unwrap(); - let slot = current_fn.scope.define(name); + let slot = current_fn.scope.define(name, ty.clone()); - // We treat local 'def' as a Set to the new slot Ok(self.make_node(node.identity.clone(), BoundKind::Set { addr: Address::Local(slot), value: Box::new(val_node) - })) + }, ty)) } }, @@ -124,8 +148,17 @@ impl Binder { let val_node = self.bind(value)?; if let UntypedKind::Identifier(name) = &target.kind { - let addr = self.resolve_variable(name)?; - Ok(self.make_node(node.identity.clone(), BoundKind::Set { addr, value: Box::new(val_node) })) + let (addr, target_ty) = self.resolve_variable(name)?; + + // Type check: value must be compatible with target + if target_ty != StaticType::Any && val_node.ty != StaticType::Any && target_ty != val_node.ty { + return Err(format!("Cannot assign {:?} to variable of type {:?}", val_node.ty, target_ty)); + } + + Ok(self.make_node(node.identity.clone(), BoundKind::Set { + addr, + value: Box::new(val_node) + }, target_ty)) } else { Err("Assignment target must be an identifier".to_string()) } @@ -134,40 +167,65 @@ impl Binder { UntypedKind::Lambda { params, body } => { self.functions.push(FunctionCompiler::new()); + let mut param_types = Vec::new(); { let current_fn = self.functions.last_mut().unwrap(); for param in params { - current_fn.scope.define(param); + // For now, lambda params are Any unless we have a way to specify them + current_fn.scope.define(param, StaticType::Any); + param_types.push(StaticType::Any); } } let body_bound = self.bind(body)?; + let ret_ty = body_bound.ty.clone(); let compiled_fn = self.functions.pop().unwrap(); - let upvalues = compiled_fn.upvalues; + let upvalues: Vec
= compiled_fn.upvalues.into_iter().map(|(a, _)| a).collect(); + + let fn_ty = StaticType::Function { + params: param_types, + ret: Box::new(ret_ty), + }; Ok(self.make_node(node.identity.clone(), BoundKind::Lambda { param_count: params.len() as u32, upvalues, body: Arc::new(body_bound), - })) + }, fn_ty)) }, UntypedKind::Call { callee, args } => { - let callee = Box::new(self.bind(callee)?); + let callee = self.bind(callee)?; let mut bound_args = Vec::new(); + let mut arg_types = Vec::new(); for arg in args { - bound_args.push(self.bind(arg)?); + let b = self.bind(arg)?; + arg_types.push(b.ty.clone()); + bound_args.push(b); } - Ok(self.make_node(node.identity.clone(), BoundKind::Call { callee, args: bound_args })) + + let ret_ty = match &callee.ty { + StaticType::Function { ret, .. } => *ret.clone(), + StaticType::Any => StaticType::Any, + _ => return Err(format!("Attempt to call non-function of type {:?}", callee.ty)), + }; + + Ok(self.make_node(node.identity.clone(), BoundKind::Call { + callee: Box::new(callee), + args: bound_args + }, ret_ty)) }, UntypedKind::Block { exprs } => { let mut bound_exprs = Vec::new(); + let mut last_ty = StaticType::Void; for expr in exprs { - bound_exprs.push(self.bind(expr)?); + let b = self.bind(expr)?; + last_ty = b.ty.clone(); + bound_exprs.push(b); } - Ok(self.make_node(node.identity.clone(), BoundKind::Block { exprs: bound_exprs })) + Ok(self.make_node(node.identity.clone(), BoundKind::Block { exprs: bound_exprs }, last_ty)) }, UntypedKind::Extension(_) => { @@ -176,41 +234,37 @@ impl Binder { } } - fn resolve_variable(&mut self, name: &str) -> Result { + fn resolve_variable(&mut self, name: &str) -> Result<(Address, StaticType), String> { let current_fn_idx = self.functions.len() - 1; // 1. Try local in current function - if let Some(slot) = self.functions[current_fn_idx].scope.resolve(name) { - return Ok(Address::Local(slot)); + if let Some(info) = self.functions[current_fn_idx].scope.resolve(name) { + return Ok((Address::Local(info.slot), info.ty)); } // 2. Try enclosing scopes (capture chain) - // Walk backwards from parent of current function up to root (0) - // We look for where the variable is DEFINED. for i in (0..current_fn_idx).rev() { - let func = &self.functions[i]; - if let Some(slot) = func.scope.resolve(name) { - // Found definition! It's a Local variable in function 'i'. - let mut addr = Address::Local(slot); + if let Some(info) = self.functions[i].scope.resolve(name) { + let mut addr = Address::Local(info.slot); + let ty = info.ty.clone(); - // Now we must propagate this capture down through all functions from i+1 to current. for k in (i + 1)..=current_fn_idx { - addr = Address::Upvalue(self.functions[k].add_upvalue(addr)); + addr = Address::Upvalue(self.functions[k].add_upvalue(addr, ty.clone())); } - return Ok(addr); + return Ok((addr, ty)); } } // 3. Try Global let globals = self.globals.lock().unwrap(); - if let Some(&idx) = globals.get(name) { - return Ok(Address::Global(idx)); + if let Some((idx, ty)) = globals.get(name) { + return Ok((Address::Global(*idx), ty.clone())); } Err(format!("Undefined variable '{}'", name)) } - fn make_node(&self, identity: Identity, kind: BoundKind) -> Node { - Node { identity, kind } + fn make_node(&self, identity: Identity, kind: BoundKind, ty: StaticType) -> Node { + Node { identity, kind, ty } } } diff --git a/src/ast/compiler/bound_nodes.rs b/src/ast/compiler/bound_nodes.rs index bca05f9..7dfefd5 100644 --- a/src/ast/compiler/bound_nodes.rs +++ b/src/ast/compiler/bound_nodes.rs @@ -1,5 +1,5 @@ use std::sync::Arc; -use crate::ast::types::Value; +use crate::ast::types::{Value, StaticType}; use crate::ast::nodes::Node; #[derive(Debug, Clone, Copy, PartialEq)] @@ -20,35 +20,35 @@ pub enum BoundKind { // Variable Update (Resolved) Set { addr: Address, - value: Box>, + value: Box>, }, If { - cond: Box>, - then_br: Box>, - else_br: Option>>, + cond: Box>, + then_br: Box>, + else_br: Option>>, }, // Global Definition (Locals are implicit by stack position) DefGlobal { global_index: u32, - value: Box>, + value: Box>, }, Lambda { param_count: u32, // The list of variables captured from enclosing scopes upvalues: Vec
, - body: Arc>, + body: Arc>, }, Call { - callee: Box>, - args: Vec>, + callee: Box>, + args: Vec>, }, Block { - exprs: Vec>, + exprs: Vec>, }, // Extension points (need to be adapted for bound nodes if they use variables) diff --git a/src/ast/environment.rs b/src/ast/environment.rs index 961fd97..0a1884d 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -1,15 +1,21 @@ use std::sync::{Arc, Mutex}; use std::collections::HashMap; -use crate::ast::types::Value; +use crate::ast::types::{Value, StaticType}; use crate::ast::parser::Parser; use crate::ast::compiler::binder::Binder; use crate::ast::vm::VM; pub struct Environment { - pub global_names: Arc>>, + pub global_names: Arc>>, pub global_values: Arc>>, } +impl Default for Environment { + fn default() -> Self { + Self::new() + } +} + impl Environment { pub fn new() -> Self { let env = Self { @@ -20,18 +26,18 @@ impl Environment { env } - pub fn register_native(&self, name: &str, func: fn(Vec) -> Value) { + pub fn register_native(&self, name: &str, ty: StaticType, func: fn(Vec) -> Value) { let mut names = self.global_names.lock().unwrap(); let mut values = self.global_values.lock().unwrap(); let idx = values.len() as u32; - names.insert(name.to_string(), idx); - values.push(Value::Function(Arc::new(move |args| func(args)))); + names.insert(name.to_string(), (idx, ty)); + values.push(Value::Function(Arc::new(func))); } fn register_stdlib(&self) { // Simple math for testing - self.register_native("+", |args| { + self.register_native("+", StaticType::Any, |args| { let mut acc = 0.0; for arg in args { if let Value::Int(i) = arg { acc += i as f64; } @@ -40,7 +46,7 @@ impl Environment { if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) } }); - self.register_native(">", |args| { + self.register_native(">", StaticType::Any, |args| { if args.len() != 2 { return Value::Void; } match (&args[0], &args[1]) { (Value::Int(a), Value::Int(b)) => Value::Bool(a > b), @@ -52,17 +58,13 @@ impl Environment { pub fn run_script(&self, source: &str) -> Result { // 1. Parse let mut parser = Parser::new(source)?; - let untyped_ast = parser.parse_expression()?; // Or loop for script + let untyped_ast = parser.parse_expression()?; - // 2. Bind + // 2. Bind & Type Check let mut binder = Binder::new(self.global_names.clone()); let bound_ast = binder.bind(&untyped_ast)?; // 3. Execute - // Note: VM needs to borrow globals, not own them exclusively? - // My VM impl takes Vec, but here we have Arc>. - // I need to update VM to accept shared globals. - let mut vm = VM::new(self.global_values.clone()); vm.run(&bound_ast) } diff --git a/src/ast/lexer.rs b/src/ast/lexer.rs index 8ef313e..acd8cd6 100644 --- a/src/ast/lexer.rs +++ b/src/ast/lexer.rs @@ -67,7 +67,7 @@ impl<'a> Lexer<'a> { TokenKind::Keyword(Arc::from(id)) } '"' => TokenKind::String(Arc::from(self.read_string()?)), - c if c.is_ascii_digit() || (c == '-' && self.peek().map_or(false, |p| p.is_ascii_digit())) => { + c if c.is_ascii_digit() || (c == '-' && self.input.peek().is_some_and(|p| p.is_ascii_digit())) => { let mut num_str = String::from(c); num_str.push_str(&self.read_while(|c| c.is_ascii_digit() || c == '.')); TokenKind::Number(num_str.parse().map_err(|_| "Invalid number format")?) @@ -98,7 +98,7 @@ impl<'a> Lexer<'a> { } self.input.next(); } else if c == ';' { - while let Some(c) = self.input.next() { + for c in self.input.by_ref() { if c == '\n' { self.line += 1; self.col = 1; diff --git a/src/ast/nodes.rs b/src/ast/nodes.rs index db2c645..4eb7c4b 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -5,9 +5,10 @@ use crate::ast::types::{Identity, Value}; /// A generic AST Node wrapper to preserve identity and metadata #[derive(Debug, Clone)] -pub struct Node { +pub struct Node { pub identity: Identity, pub kind: K, + pub ty: T, } /// The base for custom node types (extensions) @@ -63,6 +64,12 @@ pub struct Context { pub scope: Arc>, } +impl Default for Context { + fn default() -> Self { + Self::new() + } +} + impl Context { pub fn new() -> Self { let mut root_scope = Scope::new(None); @@ -75,7 +82,7 @@ impl Context { fn register_stdlib(scope: &mut Scope) { macro_rules! bin_op { - ($name:expr, $op:tt) => { + ($name:expr, $op_name:ident, $op:tt) => { scope.define($name, Value::Function(Arc::new(|args| { if args.len() < 2 { return Value::Void; } let mut acc = match &args[0] { @@ -89,7 +96,7 @@ fn register_stdlib(scope: &mut Scope) { Value::Float(f) => *f, _ => return Value::Void, }; - acc = acc $op val; + acc.$op_name(val); } if acc.fract() == 0.0 { Value::Int(acc as i64) @@ -100,10 +107,11 @@ fn register_stdlib(scope: &mut Scope) { }; } - bin_op!("+", +); - bin_op!("-", -); - bin_op!("*", *); - bin_op!("/", /); + use std::ops::{AddAssign, SubAssign, MulAssign, DivAssign}; + bin_op!("+", add_assign, +=); + bin_op!("-", sub_assign, -=); + bin_op!("*", mul_assign, *=); + bin_op!("/", div_assign, /=); scope.define(">", Value::Function(Arc::new(|args| { if args.len() != 2 { return Value::Void; } diff --git a/src/ast/parser.rs b/src/ast/parser.rs index 9720ba1..1ee7731 100644 --- a/src/ast/parser.rs +++ b/src/ast/parser.rs @@ -39,6 +39,7 @@ impl<'a> Parser<'a> { callee: Box::new(self.make_id_node("quote", identity)), args: vec![expr], }, + ty: (), }) } _ => self.parse_atom(), @@ -65,7 +66,7 @@ impl<'a> Parser<'a> { _ => return Err(format!("Unexpected token in atom: {:?} at {:?}", token.kind, token.location)), }; - Ok(Node { identity, kind }) + Ok(Node { identity, kind, ty: () }) } fn parse_list(&mut self) -> Result, String> { @@ -107,6 +108,7 @@ impl<'a> Parser<'a> { Ok(Node { identity, kind: UntypedKind::If { cond, then_br, else_br }, + ty: (), }) } @@ -122,6 +124,7 @@ impl<'a> Parser<'a> { Ok(Node { identity, kind: UntypedKind::Def { name, value }, + ty: (), }) } @@ -133,6 +136,7 @@ impl<'a> Parser<'a> { Ok(Node { identity, kind: UntypedKind::Assign { target, value }, + ty: (), }) } @@ -144,6 +148,7 @@ impl<'a> Parser<'a> { Ok(Node { identity, kind: UntypedKind::Block { exprs }, + ty: (), }) } @@ -157,6 +162,7 @@ impl<'a> Parser<'a> { params, body: Arc::new(body), }, + ty: (), }) } @@ -188,6 +194,7 @@ impl<'a> Parser<'a> { Ok(Node { identity, kind: UntypedKind::Call { callee: Box::new(callee), args }, + ty: (), }) } @@ -209,6 +216,7 @@ impl<'a> Parser<'a> { Ok(Node { identity: Arc::new(NodeIdentity { location: token.location }), kind: UntypedKind::Constant(Value::List(Arc::new(elements))), + ty: (), }) } @@ -241,6 +249,7 @@ impl<'a> Parser<'a> { Ok(Node { identity: Arc::new(NodeIdentity { location: token.location }), kind: UntypedKind::Constant(Value::Record(Arc::new(map))), + ty: (), }) } @@ -257,6 +266,7 @@ impl<'a> Parser<'a> { Node { identity, kind: UntypedKind::Identifier(Arc::from(name)), + ty: (), } } } diff --git a/src/ast/types.rs b/src/ast/types.rs index 9366c2a..51d8c5b 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -1,19 +1,19 @@ +use std::collections::{HashMap, BTreeMap}; use std::sync::Arc; -use std::collections::HashMap; use std::fmt; use std::sync::Mutex; use lazy_static::lazy_static; use std::any::Any; /// Simple source location -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct SourceLocation { pub line: u32, pub col: u32, } /// Shared identity for nodes (Location, etc.) -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct NodeIdentity { pub location: SourceLocation, } @@ -21,7 +21,7 @@ pub struct NodeIdentity { pub type Identity = Arc; /// Interned string identifier -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Keyword(pub u32); lazy_static! { @@ -70,6 +70,24 @@ pub enum Value { Object(Arc), // For compiled Closures and other opaque types } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum StaticType { + Any, + Void, + Bool, + Int, + Float, + Text, + Keyword, + List(Box), + Record(Arc>), + Function { + params: Vec, + ret: Box, + }, + Object(&'static str), +} + impl Value { pub fn is_truthy(&self) -> bool { match self { @@ -78,6 +96,21 @@ impl Value { _ => true, } } + + pub fn static_type(&self) -> StaticType { + match self { + Value::Void => StaticType::Void, + Value::Bool(_) => StaticType::Bool, + Value::Int(_) => StaticType::Int, + Value::Float(_) => StaticType::Float, + Value::Text(_) => StaticType::Text, + Value::Keyword(_) => StaticType::Keyword, + Value::List(_) => StaticType::List(Box::new(StaticType::Any)), + Value::Record(_) => StaticType::Record(Arc::new(BTreeMap::new())), // Empty for now + Value::Function { .. } => StaticType::Any, // Dynamic function + Value::Object(o) => StaticType::Object(o.type_name()), + } + } } impl fmt::Display for Value { diff --git a/src/ast/vm.rs b/src/ast/vm.rs index fc887e0..4912061 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -2,11 +2,11 @@ use std::sync::{Arc, Mutex}; use std::any::Any; use crate::ast::compiler::bound_nodes::{Address, BoundKind}; use crate::ast::nodes::Node; -use crate::ast::types::{Value, Object}; +use crate::ast::types::{Value, Object, StaticType}; #[derive(Debug, Clone)] pub struct Closure { - pub function_node: Arc>, + pub function_node: Arc>, pub upvalues: Vec, } @@ -40,7 +40,7 @@ impl VM { } } - pub fn run(&mut self, root: &Node) -> Result { + pub fn run(&mut self, root: &Node) -> Result { self.stack.clear(); self.frames.clear(); @@ -56,7 +56,7 @@ impl VM { result } - fn eval(&mut self, node: &Node) -> Result { + fn eval(&mut self, node: &Node) -> Result { match &node.kind { BoundKind::Nop => Ok(Value::Void), BoundKind::Constant(v) => Ok(v.clone()), diff --git a/src/main.rs b/src/main.rs index fde51a2..b8ee6e0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -117,3 +117,33 @@ impl eframe::App for CompilerApp { }); } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::types::Value; + + #[test] + fn test_basic_eval() { + let env = Environment::new(); + let result = env.run_script("(+ 1 2)").unwrap(); + if let Value::Int(i) = result { + assert_eq!(i, 3); + } else if let Value::Float(f) = result { + assert_eq!(f, 3.0); + } else { + panic!("Expected number result"); + } + } + + #[test] + fn test_type_error() { + let env = Environment::new(); + // This should fail because 'if' condition must be bool (and currently '+' returns Any/Float/Int) + // Wait, '+' returns Any currently in my registration. + // Let's try something that definitely fails type check. + let result = env.run_script("(if 1 2 3)"); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("Condition must be boolean")); + } +}