feat: Add StaticType and type checking
This commit introduces `StaticType` and enhances the `Binder` to perform
basic type checking during the binding phase.
Key changes include:
- **`StaticType` enum:** Represents static types such as `Any`, `Void`,
`Bool`, `Int`, `Float`, `Text`, `List`, `Record`, and `Function`.
- **`Node<K, T>`:** The generic `Node` now includes a `ty` field to
store its inferred static type.
- **Binder enhancements:**
- `CompilerScope` now stores `LocalInfo` containing the variable's
slot and `StaticType`.
- `FunctionCompiler` stores upvalues with their associated
`StaticType`.
- `Binder::bind` now returns `Node<BoundKind, StaticType>`,
propagating type information.
- Type checking is added for `If` conditions and `Assign`
operations.
- `Def` nodes now infer and store the type of the defined variable.
- **`Value::static_type()`:** A new method to determine the `StaticType`
of a `Value`.
- **Environment modifications:** `global_names` now stores `(u32,
StaticType)` to associate global variables with their types.
- **Parser modifications:** The `ty` field of nodes is initialized to
`()` by default.
- **VM modifications:** `VM::run` and `VM::eval` now operate on
`Node<BoundKind, StaticType>`.
- **Tests:** Added basic evaluation and type error tests.
feat: Add StaticType and type checking
Introduces `StaticType` enum and integrates it into the AST. The binder
now performs type checking during compilation, ensuring that expressions
conform to expected types, especially for control flow structures like
`if`. This lays the groundwork for static type analysis and error
reporting.
This commit is contained in:
+119
-65
@@ -2,11 +2,17 @@ use std::collections::HashMap;
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
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;
|
use crate::ast::types::{Identity, StaticType};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct LocalInfo {
|
||||||
|
slot: u32,
|
||||||
|
ty: StaticType,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct CompilerScope {
|
struct CompilerScope {
|
||||||
locals: HashMap<String, u32>,
|
locals: HashMap<String, LocalInfo>,
|
||||||
slot_count: u32,
|
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;
|
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;
|
self.slot_count += 1;
|
||||||
slot
|
slot
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(&self, name: &str) -> Option<u32> {
|
fn resolve(&self, name: &str) -> Option<LocalInfo> {
|
||||||
self.locals.get(name).copied()
|
self.locals.get(name).cloned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FunctionCompiler {
|
struct FunctionCompiler {
|
||||||
scope: CompilerScope,
|
scope: CompilerScope,
|
||||||
// Upvalues capture variables from the *immediate* enclosing function.
|
upvalues: Vec<(Address, StaticType)>,
|
||||||
// The address stored here refers to a Local/Upvalue in the Parent Scope.
|
|
||||||
upvalues: Vec<Address>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FunctionCompiler {
|
impl FunctionCompiler {
|
||||||
@@ -45,23 +49,23 @@ impl FunctionCompiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_upvalue(&mut self, addr: Address) -> u32 {
|
fn add_upvalue(&mut self, addr: Address, ty: StaticType) -> u32 {
|
||||||
if let Some(idx) = self.upvalues.iter().position(|&a| a == addr) {
|
if let Some(idx) = self.upvalues.iter().position(|(a, _)| *a == addr) {
|
||||||
return idx as u32;
|
return idx as u32;
|
||||||
}
|
}
|
||||||
let idx = self.upvalues.len() as u32;
|
let idx = self.upvalues.len() as u32;
|
||||||
self.upvalues.push(addr);
|
self.upvalues.push((addr, ty));
|
||||||
idx
|
idx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Binder {
|
pub struct Binder {
|
||||||
functions: Vec<FunctionCompiler>,
|
functions: Vec<FunctionCompiler>,
|
||||||
globals: Arc<Mutex<HashMap<String, u32>>>,
|
globals: Arc<Mutex<HashMap<String, (u32, StaticType)>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Binder {
|
impl Binder {
|
||||||
pub fn new(globals: Arc<Mutex<HashMap<String, u32>>>) -> Self {
|
pub fn new(globals: Arc<Mutex<HashMap<String, (u32, StaticType)>>>) -> Self {
|
||||||
let mut binder = Self {
|
let mut binder = Self {
|
||||||
functions: Vec::new(),
|
functions: Vec::new(),
|
||||||
globals,
|
globals,
|
||||||
@@ -70,53 +74,73 @@ impl Binder {
|
|||||||
binder
|
binder
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bind(&mut self, node: &Node<UntypedKind>) -> Result<Node<BoundKind>, String> {
|
pub fn bind(&mut self, node: &Node<UntypedKind>) -> Result<Node<BoundKind, StaticType>, String> {
|
||||||
match &node.kind {
|
match &node.kind {
|
||||||
UntypedKind::Nop => Ok(self.make_node(node.identity.clone(), BoundKind::Nop)),
|
UntypedKind::Nop => Ok(self.make_node(node.identity.clone(), BoundKind::Nop, StaticType::Void)),
|
||||||
UntypedKind::Constant(v) => Ok(self.make_node(node.identity.clone(), BoundKind::Constant(v.clone()))),
|
UntypedKind::Constant(v) => {
|
||||||
|
let ty = v.static_type();
|
||||||
|
Ok(self.make_node(node.identity.clone(), BoundKind::Constant(v.clone()), ty))
|
||||||
|
},
|
||||||
|
|
||||||
UntypedKind::Identifier(name) => {
|
UntypedKind::Identifier(name) => {
|
||||||
let addr = self.resolve_variable(name)?;
|
let (addr, ty) = self.resolve_variable(name)?;
|
||||||
Ok(self.make_node(node.identity.clone(), BoundKind::Get(addr)))
|
Ok(self.make_node(node.identity.clone(), BoundKind::Get(addr), ty))
|
||||||
},
|
},
|
||||||
|
|
||||||
UntypedKind::If { cond, then_br, else_br } => {
|
UntypedKind::If { cond, then_br, else_br } => {
|
||||||
let cond = Box::new(self.bind(cond)?);
|
let cond = self.bind(cond)?;
|
||||||
let then_br = Box::new(self.bind(then_br)?);
|
// Type check condition: Must be Bool or Any
|
||||||
let else_br = match else_br {
|
if cond.ty != StaticType::Bool && cond.ty != StaticType::Any {
|
||||||
Some(e) => Some(Box::new(self.bind(e)?)),
|
return Err(format!("Condition must be boolean, found {:?}", cond.ty));
|
||||||
None => None,
|
}
|
||||||
|
|
||||||
|
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 } => {
|
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 {
|
if self.functions.len() == 1 {
|
||||||
// Global Definition
|
|
||||||
let val_node = self.bind(value)?;
|
|
||||||
let mut globals = self.globals.lock().unwrap();
|
let mut globals = self.globals.lock().unwrap();
|
||||||
let idx = if let Some(&idx) = globals.get(name.as_ref()) {
|
let idx = if let Some((idx, _)) = globals.get(name.as_ref()) {
|
||||||
idx
|
*idx
|
||||||
} else {
|
} else {
|
||||||
let idx = globals.len() as u32;
|
let idx = globals.len() as u32;
|
||||||
globals.insert(name.to_string(), idx);
|
globals.insert(name.to_string(), (idx, ty.clone()));
|
||||||
idx
|
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 {
|
} 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 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 {
|
Ok(self.make_node(node.identity.clone(), BoundKind::Set {
|
||||||
addr: Address::Local(slot),
|
addr: Address::Local(slot),
|
||||||
value: Box::new(val_node)
|
value: Box::new(val_node)
|
||||||
}))
|
}, ty))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -124,8 +148,17 @@ impl Binder {
|
|||||||
let val_node = self.bind(value)?;
|
let val_node = self.bind(value)?;
|
||||||
|
|
||||||
if let UntypedKind::Identifier(name) = &target.kind {
|
if let UntypedKind::Identifier(name) = &target.kind {
|
||||||
let addr = self.resolve_variable(name)?;
|
let (addr, target_ty) = self.resolve_variable(name)?;
|
||||||
Ok(self.make_node(node.identity.clone(), BoundKind::Set { addr, value: Box::new(val_node) }))
|
|
||||||
|
// 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 {
|
} else {
|
||||||
Err("Assignment target must be an identifier".to_string())
|
Err("Assignment target must be an identifier".to_string())
|
||||||
}
|
}
|
||||||
@@ -134,40 +167,65 @@ impl Binder {
|
|||||||
UntypedKind::Lambda { params, body } => {
|
UntypedKind::Lambda { params, body } => {
|
||||||
self.functions.push(FunctionCompiler::new());
|
self.functions.push(FunctionCompiler::new());
|
||||||
|
|
||||||
|
let mut param_types = Vec::new();
|
||||||
{
|
{
|
||||||
let current_fn = self.functions.last_mut().unwrap();
|
let current_fn = self.functions.last_mut().unwrap();
|
||||||
for param in params {
|
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 body_bound = self.bind(body)?;
|
||||||
|
let ret_ty = body_bound.ty.clone();
|
||||||
|
|
||||||
let compiled_fn = self.functions.pop().unwrap();
|
let compiled_fn = self.functions.pop().unwrap();
|
||||||
let upvalues = compiled_fn.upvalues;
|
let upvalues: Vec<Address> = 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 {
|
Ok(self.make_node(node.identity.clone(), BoundKind::Lambda {
|
||||||
param_count: params.len() as u32,
|
param_count: params.len() as u32,
|
||||||
upvalues,
|
upvalues,
|
||||||
body: Arc::new(body_bound),
|
body: Arc::new(body_bound),
|
||||||
}))
|
}, fn_ty))
|
||||||
},
|
},
|
||||||
|
|
||||||
UntypedKind::Call { callee, args } => {
|
UntypedKind::Call { callee, args } => {
|
||||||
let callee = Box::new(self.bind(callee)?);
|
let callee = self.bind(callee)?;
|
||||||
let mut bound_args = Vec::new();
|
let mut bound_args = Vec::new();
|
||||||
|
let mut arg_types = Vec::new();
|
||||||
for arg in args {
|
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 } => {
|
UntypedKind::Block { exprs } => {
|
||||||
let mut bound_exprs = Vec::new();
|
let mut bound_exprs = Vec::new();
|
||||||
|
let mut last_ty = StaticType::Void;
|
||||||
for expr in exprs {
|
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(_) => {
|
UntypedKind::Extension(_) => {
|
||||||
@@ -176,41 +234,37 @@ impl Binder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_variable(&mut self, name: &str) -> Result<Address, String> {
|
fn resolve_variable(&mut self, name: &str) -> Result<(Address, StaticType), String> {
|
||||||
let current_fn_idx = self.functions.len() - 1;
|
let current_fn_idx = self.functions.len() - 1;
|
||||||
|
|
||||||
// 1. Try local in current function
|
// 1. Try local in current function
|
||||||
if let Some(slot) = self.functions[current_fn_idx].scope.resolve(name) {
|
if let Some(info) = self.functions[current_fn_idx].scope.resolve(name) {
|
||||||
return Ok(Address::Local(slot));
|
return Ok((Address::Local(info.slot), info.ty));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Try enclosing scopes (capture chain)
|
// 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() {
|
for i in (0..current_fn_idx).rev() {
|
||||||
let func = &self.functions[i];
|
if let Some(info) = self.functions[i].scope.resolve(name) {
|
||||||
if let Some(slot) = func.scope.resolve(name) {
|
let mut addr = Address::Local(info.slot);
|
||||||
// Found definition! It's a Local variable in function 'i'.
|
let ty = info.ty.clone();
|
||||||
let mut addr = Address::Local(slot);
|
|
||||||
|
|
||||||
// Now we must propagate this capture down through all functions from i+1 to current.
|
|
||||||
for k in (i + 1)..=current_fn_idx {
|
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
|
// 3. Try Global
|
||||||
let globals = self.globals.lock().unwrap();
|
let globals = self.globals.lock().unwrap();
|
||||||
if let Some(&idx) = globals.get(name) {
|
if let Some((idx, ty)) = globals.get(name) {
|
||||||
return Ok(Address::Global(idx));
|
return Ok((Address::Global(*idx), ty.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(format!("Undefined variable '{}'", name))
|
Err(format!("Undefined variable '{}'", name))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_node(&self, identity: Identity, kind: BoundKind) -> Node<BoundKind> {
|
fn make_node(&self, identity: Identity, kind: BoundKind, ty: StaticType) -> Node<BoundKind, StaticType> {
|
||||||
Node { identity, kind }
|
Node { identity, kind, ty }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use crate::ast::types::Value;
|
use crate::ast::types::{Value, StaticType};
|
||||||
use crate::ast::nodes::Node;
|
use crate::ast::nodes::Node;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
@@ -20,35 +20,35 @@ pub enum BoundKind {
|
|||||||
// Variable Update (Resolved)
|
// Variable Update (Resolved)
|
||||||
Set {
|
Set {
|
||||||
addr: Address,
|
addr: Address,
|
||||||
value: Box<Node<BoundKind>>,
|
value: Box<Node<BoundKind, StaticType>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
If {
|
If {
|
||||||
cond: Box<Node<BoundKind>>,
|
cond: Box<Node<BoundKind, StaticType>>,
|
||||||
then_br: Box<Node<BoundKind>>,
|
then_br: Box<Node<BoundKind, StaticType>>,
|
||||||
else_br: Option<Box<Node<BoundKind>>>,
|
else_br: Option<Box<Node<BoundKind, StaticType>>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Global Definition (Locals are implicit by stack position)
|
// Global Definition (Locals are implicit by stack position)
|
||||||
DefGlobal {
|
DefGlobal {
|
||||||
global_index: u32,
|
global_index: u32,
|
||||||
value: Box<Node<BoundKind>>,
|
value: Box<Node<BoundKind, StaticType>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
Lambda {
|
Lambda {
|
||||||
param_count: u32,
|
param_count: u32,
|
||||||
// The list of variables captured from enclosing scopes
|
// The list of variables captured from enclosing scopes
|
||||||
upvalues: Vec<Address>,
|
upvalues: Vec<Address>,
|
||||||
body: Arc<Node<BoundKind>>,
|
body: Arc<Node<BoundKind, StaticType>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
Call {
|
Call {
|
||||||
callee: Box<Node<BoundKind>>,
|
callee: Box<Node<BoundKind, StaticType>>,
|
||||||
args: Vec<Node<BoundKind>>,
|
args: Vec<Node<BoundKind, StaticType>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
Block {
|
Block {
|
||||||
exprs: Vec<Node<BoundKind>>,
|
exprs: Vec<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)
|
||||||
|
|||||||
+15
-13
@@ -1,15 +1,21 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use crate::ast::types::Value;
|
use crate::ast::types::{Value, StaticType};
|
||||||
use crate::ast::parser::Parser;
|
use crate::ast::parser::Parser;
|
||||||
use crate::ast::compiler::binder::Binder;
|
use crate::ast::compiler::binder::Binder;
|
||||||
use crate::ast::vm::VM;
|
use crate::ast::vm::VM;
|
||||||
|
|
||||||
pub struct Environment {
|
pub struct Environment {
|
||||||
pub global_names: Arc<Mutex<HashMap<String, u32>>>,
|
pub global_names: Arc<Mutex<HashMap<String, (u32, StaticType)>>>,
|
||||||
pub global_values: Arc<Mutex<Vec<Value>>>,
|
pub global_values: Arc<Mutex<Vec<Value>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Environment {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Environment {
|
impl Environment {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let env = Self {
|
let env = Self {
|
||||||
@@ -20,18 +26,18 @@ impl Environment {
|
|||||||
env
|
env
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_native(&self, name: &str, func: fn(Vec<Value>) -> Value) {
|
pub fn register_native(&self, name: &str, ty: StaticType, func: fn(Vec<Value>) -> Value) {
|
||||||
let mut names = self.global_names.lock().unwrap();
|
let mut names = self.global_names.lock().unwrap();
|
||||||
let mut values = self.global_values.lock().unwrap();
|
let mut values = self.global_values.lock().unwrap();
|
||||||
|
|
||||||
let idx = values.len() as u32;
|
let idx = values.len() as u32;
|
||||||
names.insert(name.to_string(), idx);
|
names.insert(name.to_string(), (idx, ty));
|
||||||
values.push(Value::Function(Arc::new(move |args| func(args))));
|
values.push(Value::Function(Arc::new(func)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_stdlib(&self) {
|
fn register_stdlib(&self) {
|
||||||
// Simple math for testing
|
// Simple math for testing
|
||||||
self.register_native("+", |args| {
|
self.register_native("+", StaticType::Any, |args| {
|
||||||
let mut acc = 0.0;
|
let mut acc = 0.0;
|
||||||
for arg in args {
|
for arg in args {
|
||||||
if let Value::Int(i) = arg { acc += i as f64; }
|
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) }
|
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; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
match (&args[0], &args[1]) {
|
match (&args[0], &args[1]) {
|
||||||
(Value::Int(a), Value::Int(b)) => Value::Bool(a > b),
|
(Value::Int(a), Value::Int(b)) => Value::Bool(a > b),
|
||||||
@@ -52,17 +58,13 @@ impl Environment {
|
|||||||
pub fn run_script(&self, source: &str) -> Result<Value, String> {
|
pub fn run_script(&self, source: &str) -> Result<Value, String> {
|
||||||
// 1. Parse
|
// 1. Parse
|
||||||
let mut parser = Parser::new(source)?;
|
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 mut binder = Binder::new(self.global_names.clone());
|
||||||
let bound_ast = binder.bind(&untyped_ast)?;
|
let bound_ast = binder.bind(&untyped_ast)?;
|
||||||
|
|
||||||
// 3. Execute
|
// 3. Execute
|
||||||
// Note: VM needs to borrow globals, not own them exclusively?
|
|
||||||
// My VM impl takes Vec<Value>, but here we have Arc<Mutex<Vec>>.
|
|
||||||
// I need to update VM to accept shared globals.
|
|
||||||
|
|
||||||
let mut vm = VM::new(self.global_values.clone());
|
let mut vm = VM::new(self.global_values.clone());
|
||||||
vm.run(&bound_ast)
|
vm.run(&bound_ast)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -67,7 +67,7 @@ impl<'a> Lexer<'a> {
|
|||||||
TokenKind::Keyword(Arc::from(id))
|
TokenKind::Keyword(Arc::from(id))
|
||||||
}
|
}
|
||||||
'"' => TokenKind::String(Arc::from(self.read_string()?)),
|
'"' => 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);
|
let mut num_str = String::from(c);
|
||||||
num_str.push_str(&self.read_while(|c| c.is_ascii_digit() || c == '.'));
|
num_str.push_str(&self.read_while(|c| c.is_ascii_digit() || c == '.'));
|
||||||
TokenKind::Number(num_str.parse().map_err(|_| "Invalid number format")?)
|
TokenKind::Number(num_str.parse().map_err(|_| "Invalid number format")?)
|
||||||
@@ -98,7 +98,7 @@ impl<'a> Lexer<'a> {
|
|||||||
}
|
}
|
||||||
self.input.next();
|
self.input.next();
|
||||||
} else if c == ';' {
|
} else if c == ';' {
|
||||||
while let Some(c) = self.input.next() {
|
for c in self.input.by_ref() {
|
||||||
if c == '\n' {
|
if c == '\n' {
|
||||||
self.line += 1;
|
self.line += 1;
|
||||||
self.col = 1;
|
self.col = 1;
|
||||||
|
|||||||
+15
-7
@@ -5,9 +5,10 @@ use crate::ast::types::{Identity, Value};
|
|||||||
|
|
||||||
/// A generic AST Node wrapper to preserve identity and metadata
|
/// A generic AST Node wrapper to preserve identity and metadata
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Node<K> {
|
pub struct Node<K, T = ()> {
|
||||||
pub identity: Identity,
|
pub identity: Identity,
|
||||||
pub kind: K,
|
pub kind: K,
|
||||||
|
pub ty: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The base for custom node types (extensions)
|
/// The base for custom node types (extensions)
|
||||||
@@ -63,6 +64,12 @@ pub struct Context {
|
|||||||
pub scope: Arc<Mutex<Scope>>,
|
pub scope: Arc<Mutex<Scope>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Context {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut root_scope = Scope::new(None);
|
let mut root_scope = Scope::new(None);
|
||||||
@@ -75,7 +82,7 @@ impl Context {
|
|||||||
|
|
||||||
fn register_stdlib(scope: &mut Scope) {
|
fn register_stdlib(scope: &mut Scope) {
|
||||||
macro_rules! bin_op {
|
macro_rules! bin_op {
|
||||||
($name:expr, $op:tt) => {
|
($name:expr, $op_name:ident, $op:tt) => {
|
||||||
scope.define($name, Value::Function(Arc::new(|args| {
|
scope.define($name, Value::Function(Arc::new(|args| {
|
||||||
if args.len() < 2 { return Value::Void; }
|
if args.len() < 2 { return Value::Void; }
|
||||||
let mut acc = match &args[0] {
|
let mut acc = match &args[0] {
|
||||||
@@ -89,7 +96,7 @@ fn register_stdlib(scope: &mut Scope) {
|
|||||||
Value::Float(f) => *f,
|
Value::Float(f) => *f,
|
||||||
_ => return Value::Void,
|
_ => return Value::Void,
|
||||||
};
|
};
|
||||||
acc = acc $op val;
|
acc.$op_name(val);
|
||||||
}
|
}
|
||||||
if acc.fract() == 0.0 {
|
if acc.fract() == 0.0 {
|
||||||
Value::Int(acc as i64)
|
Value::Int(acc as i64)
|
||||||
@@ -100,10 +107,11 @@ fn register_stdlib(scope: &mut Scope) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
bin_op!("+", +);
|
use std::ops::{AddAssign, SubAssign, MulAssign, DivAssign};
|
||||||
bin_op!("-", -);
|
bin_op!("+", add_assign, +=);
|
||||||
bin_op!("*", *);
|
bin_op!("-", sub_assign, -=);
|
||||||
bin_op!("/", /);
|
bin_op!("*", mul_assign, *=);
|
||||||
|
bin_op!("/", div_assign, /=);
|
||||||
|
|
||||||
scope.define(">", Value::Function(Arc::new(|args| {
|
scope.define(">", Value::Function(Arc::new(|args| {
|
||||||
if args.len() != 2 { return Value::Void; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
|
|||||||
+11
-1
@@ -39,6 +39,7 @@ impl<'a> Parser<'a> {
|
|||||||
callee: Box::new(self.make_id_node("quote", identity)),
|
callee: Box::new(self.make_id_node("quote", identity)),
|
||||||
args: vec![expr],
|
args: vec![expr],
|
||||||
},
|
},
|
||||||
|
ty: (),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => self.parse_atom(),
|
_ => self.parse_atom(),
|
||||||
@@ -65,7 +66,7 @@ impl<'a> Parser<'a> {
|
|||||||
_ => return Err(format!("Unexpected token in atom: {:?} at {:?}", token.kind, token.location)),
|
_ => 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<Node<UntypedKind>, String> {
|
fn parse_list(&mut self) -> Result<Node<UntypedKind>, String> {
|
||||||
@@ -107,6 +108,7 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(Node {
|
Ok(Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::If { cond, then_br, else_br },
|
kind: UntypedKind::If { cond, then_br, else_br },
|
||||||
|
ty: (),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +124,7 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(Node {
|
Ok(Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::Def { name, value },
|
kind: UntypedKind::Def { name, value },
|
||||||
|
ty: (),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +136,7 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(Node {
|
Ok(Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::Assign { target, value },
|
kind: UntypedKind::Assign { target, value },
|
||||||
|
ty: (),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +148,7 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(Node {
|
Ok(Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::Block { exprs },
|
kind: UntypedKind::Block { exprs },
|
||||||
|
ty: (),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,6 +162,7 @@ impl<'a> Parser<'a> {
|
|||||||
params,
|
params,
|
||||||
body: Arc::new(body),
|
body: Arc::new(body),
|
||||||
},
|
},
|
||||||
|
ty: (),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +194,7 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(Node {
|
Ok(Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::Call { callee: Box::new(callee), args },
|
kind: UntypedKind::Call { callee: Box::new(callee), args },
|
||||||
|
ty: (),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,6 +216,7 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(Node {
|
Ok(Node {
|
||||||
identity: Arc::new(NodeIdentity { location: token.location }),
|
identity: Arc::new(NodeIdentity { location: token.location }),
|
||||||
kind: UntypedKind::Constant(Value::List(Arc::new(elements))),
|
kind: UntypedKind::Constant(Value::List(Arc::new(elements))),
|
||||||
|
ty: (),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,6 +249,7 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(Node {
|
Ok(Node {
|
||||||
identity: Arc::new(NodeIdentity { location: token.location }),
|
identity: Arc::new(NodeIdentity { location: token.location }),
|
||||||
kind: UntypedKind::Constant(Value::Record(Arc::new(map))),
|
kind: UntypedKind::Constant(Value::Record(Arc::new(map))),
|
||||||
|
ty: (),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,6 +266,7 @@ impl<'a> Parser<'a> {
|
|||||||
Node {
|
Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::Identifier(Arc::from(name)),
|
kind: UntypedKind::Identifier(Arc::from(name)),
|
||||||
|
ty: (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-4
@@ -1,19 +1,19 @@
|
|||||||
|
use std::collections::{HashMap, BTreeMap};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
/// Simple source location
|
/// Simple source location
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct SourceLocation {
|
pub struct SourceLocation {
|
||||||
pub line: u32,
|
pub line: u32,
|
||||||
pub col: u32,
|
pub col: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shared identity for nodes (Location, etc.)
|
/// Shared identity for nodes (Location, etc.)
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct NodeIdentity {
|
pub struct NodeIdentity {
|
||||||
pub location: SourceLocation,
|
pub location: SourceLocation,
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ pub struct NodeIdentity {
|
|||||||
pub type Identity = Arc<NodeIdentity>;
|
pub type Identity = Arc<NodeIdentity>;
|
||||||
|
|
||||||
/// Interned string identifier
|
/// Interned string identifier
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct Keyword(pub u32);
|
pub struct Keyword(pub u32);
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
@@ -70,6 +70,24 @@ pub enum Value {
|
|||||||
Object(Arc<dyn Object>), // For compiled Closures and other opaque types
|
Object(Arc<dyn Object>), // 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<StaticType>),
|
||||||
|
Record(Arc<BTreeMap<Keyword, StaticType>>),
|
||||||
|
Function {
|
||||||
|
params: Vec<StaticType>,
|
||||||
|
ret: Box<StaticType>,
|
||||||
|
},
|
||||||
|
Object(&'static str),
|
||||||
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
pub fn is_truthy(&self) -> bool {
|
pub fn is_truthy(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
@@ -78,6 +96,21 @@ impl Value {
|
|||||||
_ => true,
|
_ => 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 {
|
impl fmt::Display for Value {
|
||||||
|
|||||||
+4
-4
@@ -2,11 +2,11 @@ use std::sync::{Arc, Mutex};
|
|||||||
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;
|
||||||
use crate::ast::types::{Value, Object};
|
use crate::ast::types::{Value, Object, StaticType};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Closure {
|
pub struct Closure {
|
||||||
pub function_node: Arc<Node<BoundKind>>,
|
pub function_node: Arc<Node<BoundKind, StaticType>>,
|
||||||
pub upvalues: Vec<Value>,
|
pub upvalues: Vec<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ impl VM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self, root: &Node<BoundKind>) -> Result<Value, String> {
|
pub fn run(&mut self, root: &Node<BoundKind, StaticType>) -> Result<Value, String> {
|
||||||
self.stack.clear();
|
self.stack.clear();
|
||||||
self.frames.clear();
|
self.frames.clear();
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ impl VM {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval(&mut self, node: &Node<BoundKind>) -> Result<Value, String> {
|
fn eval(&mut self, node: &Node<BoundKind, StaticType>) -> Result<Value, String> {
|
||||||
match &node.kind {
|
match &node.kind {
|
||||||
BoundKind::Nop => Ok(Value::Void),
|
BoundKind::Nop => Ok(Value::Void),
|
||||||
BoundKind::Constant(v) => Ok(v.clone()),
|
BoundKind::Constant(v) => Ok(v.clone()),
|
||||||
|
|||||||
+30
@@ -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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user