Introduce Value::Cell for mutable upvalues, allowing closures to
modify captured variables. Implement `capture_upvalue` and `get_value`/`set_value` methods in the `VM` to handle these mutable upvalues. Refactor number parsing to support integers and floats Separate the `TokenKind::Number` into `TokenKind::Integer` and `TokenKind::Float`. Update the lexer to distinguish between integer and float literals. Update the parser to correctly map these new token kinds to their respective `Value` types. Add integration tests for parsing integers and floats, and for closure modification of captured variables.
This commit is contained in:
+16
-3
@@ -11,7 +11,8 @@ pub enum TokenKind {
|
|||||||
Quote, Backtick, Tilde, At,
|
Quote, Backtick, Tilde, At,
|
||||||
Identifier(Arc<str>),
|
Identifier(Arc<str>),
|
||||||
Keyword(Arc<str>),
|
Keyword(Arc<str>),
|
||||||
Number(f64),
|
Integer(i64),
|
||||||
|
Float(f64),
|
||||||
String(Arc<str>),
|
String(Arc<str>),
|
||||||
EOF,
|
EOF,
|
||||||
}
|
}
|
||||||
@@ -69,8 +70,20 @@ impl<'a> Lexer<'a> {
|
|||||||
'"' => TokenKind::String(Arc::from(self.read_string()?)),
|
'"' => TokenKind::String(Arc::from(self.read_string()?)),
|
||||||
c if c.is_ascii_digit() || (c == '-' && self.input.peek().is_some_and(|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 == '.'));
|
while let Some(&next) = self.peek() {
|
||||||
TokenKind::Number(num_str.parse().map_err(|_| "Invalid number format")?)
|
if next.is_ascii_digit() || next == '.' {
|
||||||
|
num_str.push(self.input.next().unwrap());
|
||||||
|
self.col += 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if num_str.contains('.') {
|
||||||
|
TokenKind::Float(num_str.parse().map_err(|_| "Invalid float format")?)
|
||||||
|
} else {
|
||||||
|
TokenKind::Integer(num_str.parse().map_err(|_| "Invalid integer format")?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
c if is_ident_start(c) => {
|
c if is_ident_start(c) => {
|
||||||
let mut id = String::from(c);
|
let mut id = String::from(c);
|
||||||
|
|||||||
+2
-1
@@ -51,7 +51,8 @@ impl<'a> Parser<'a> {
|
|||||||
let identity = Arc::new(NodeIdentity { location: token.location });
|
let identity = Arc::new(NodeIdentity { location: token.location });
|
||||||
|
|
||||||
let kind = match token.kind {
|
let kind = match token.kind {
|
||||||
TokenKind::Number(n) => UntypedKind::Constant(Value::Float(n)),
|
TokenKind::Integer(n) => UntypedKind::Constant(Value::Int(n)),
|
||||||
|
TokenKind::Float(n) => UntypedKind::Constant(Value::Float(n)),
|
||||||
TokenKind::String(s) => UntypedKind::Constant(Value::Text(s)),
|
TokenKind::String(s) => UntypedKind::Constant(Value::Text(s)),
|
||||||
TokenKind::Keyword(k) => UntypedKind::Constant(Value::Keyword(Keyword::intern(&k))),
|
TokenKind::Keyword(k) => UntypedKind::Constant(Value::Keyword(Keyword::intern(&k))),
|
||||||
TokenKind::Identifier(id) => {
|
TokenKind::Identifier(id) => {
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ pub enum Value {
|
|||||||
Record(Arc<HashMap<Keyword, Value>>),
|
Record(Arc<HashMap<Keyword, Value>>),
|
||||||
Function(Arc<dyn Fn(Vec<Value>) -> Value + Send + Sync>),
|
Function(Arc<dyn Fn(Vec<Value>) -> Value + Send + Sync>),
|
||||||
Object(Arc<dyn Object>), // For compiled Closures and other opaque types
|
Object(Arc<dyn Object>), // For compiled Closures and other opaque types
|
||||||
|
Cell(Arc<Mutex<Value>>), // Boxed value for captures
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
@@ -93,6 +94,7 @@ impl Value {
|
|||||||
match self {
|
match self {
|
||||||
Value::Void => false,
|
Value::Void => false,
|
||||||
Value::Bool(b) => *b,
|
Value::Bool(b) => *b,
|
||||||
|
Value::Cell(c) => c.lock().unwrap().is_truthy(),
|
||||||
_ => true,
|
_ => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,6 +111,7 @@ impl Value {
|
|||||||
Value::Record(_) => StaticType::Record(Arc::new(BTreeMap::new())), // Empty for now
|
Value::Record(_) => StaticType::Record(Arc::new(BTreeMap::new())), // Empty for now
|
||||||
Value::Function { .. } => StaticType::Any, // Dynamic function
|
Value::Function { .. } => StaticType::Any, // Dynamic function
|
||||||
Value::Object(o) => StaticType::Object(o.type_name()),
|
Value::Object(o) => StaticType::Object(o.type_name()),
|
||||||
|
Value::Cell(c) => c.lock().unwrap().static_type(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,6 +129,7 @@ impl fmt::Display for Value {
|
|||||||
Value::Record(r) => write!(f, "<record[{}]>", r.len()),
|
Value::Record(r) => write!(f, "<record[{}]>", r.len()),
|
||||||
Value::Function(_) => write!(f, "<native fn>"),
|
Value::Function(_) => write!(f, "<native fn>"),
|
||||||
Value::Object(o) => write!(f, "<{}>", o.type_name()),
|
Value::Object(o) => write!(f, "<{}>", o.type_name()),
|
||||||
|
Value::Cell(c) => write!(f, "{}", c.lock().unwrap()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+174
-12
@@ -7,7 +7,7 @@ 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, StaticType>>,
|
pub function_node: Arc<Node<BoundKind, StaticType>>,
|
||||||
pub upvalues: Vec<Value>,
|
pub upvalues: Vec<Arc<Mutex<Value>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Object for Closure {
|
impl Object for Closure {
|
||||||
@@ -102,7 +102,7 @@ impl VM {
|
|||||||
BoundKind::Lambda { param_count: _, upvalues, body } => {
|
BoundKind::Lambda { param_count: _, upvalues, body } => {
|
||||||
let mut captured = Vec::with_capacity(upvalues.len());
|
let mut captured = Vec::with_capacity(upvalues.len());
|
||||||
for addr in upvalues {
|
for addr in upvalues {
|
||||||
captured.push(self.get_value(*addr)?);
|
captured.push(self.capture_upvalue(*addr)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let closure = Closure {
|
let closure = Closure {
|
||||||
@@ -151,13 +151,53 @@ impl VM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn capture_upvalue(&mut self, addr: Address) -> Result<Arc<Mutex<Value>>, String> {
|
||||||
|
match addr {
|
||||||
|
Address::Local(idx) => {
|
||||||
|
let frame = self.frames.last().ok_or("No call frame")?;
|
||||||
|
let abs_index = frame.stack_base + (idx as usize);
|
||||||
|
if abs_index < self.stack.len() {
|
||||||
|
// Check if already boxed
|
||||||
|
if let Value::Cell(cell) = &self.stack[abs_index] {
|
||||||
|
Ok(cell.clone())
|
||||||
|
} else {
|
||||||
|
// Box it
|
||||||
|
let val = self.stack[abs_index].clone();
|
||||||
|
let cell = Arc::new(Mutex::new(val));
|
||||||
|
self.stack[abs_index] = Value::Cell(cell.clone());
|
||||||
|
Ok(cell)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(format!("Stack underflow capture local {}", idx))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Address::Upvalue(idx) => {
|
||||||
|
let frame = self.frames.last().ok_or("No call frame")?;
|
||||||
|
if let Some(closure) = &frame.closure {
|
||||||
|
let idx = idx as usize;
|
||||||
|
if idx < closure.upvalues.len() {
|
||||||
|
Ok(closure.upvalues[idx].clone())
|
||||||
|
} else {
|
||||||
|
Err(format!("Upvalue access out of bounds capture {}", idx))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err("Current frame has no closure".to_string())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Address::Global(_) => Err("Cannot capture global directly".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_value(&self, addr: Address) -> Result<Value, String> {
|
fn get_value(&self, addr: Address) -> Result<Value, String> {
|
||||||
match addr {
|
match addr {
|
||||||
Address::Local(idx) => {
|
Address::Local(idx) => {
|
||||||
let frame = self.frames.last().ok_or("No call frame")?;
|
let frame = self.frames.last().ok_or("No call frame")?;
|
||||||
let abs_index = frame.stack_base + (idx as usize);
|
let abs_index = frame.stack_base + (idx as usize);
|
||||||
if abs_index < self.stack.len() {
|
if abs_index < self.stack.len() {
|
||||||
Ok(self.stack[abs_index].clone())
|
match &self.stack[abs_index] {
|
||||||
|
Value::Cell(cell) => Ok(cell.lock().unwrap().clone()),
|
||||||
|
val => Ok(val.clone()),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(format!("Stack underflow access local {}", idx))
|
Err(format!("Stack underflow access local {}", idx))
|
||||||
}
|
}
|
||||||
@@ -176,7 +216,7 @@ impl VM {
|
|||||||
if let Some(closure) = &frame.closure {
|
if let Some(closure) = &frame.closure {
|
||||||
let idx = idx as usize;
|
let idx = idx as usize;
|
||||||
if idx < closure.upvalues.len() {
|
if idx < closure.upvalues.len() {
|
||||||
Ok(closure.upvalues[idx].clone())
|
Ok(closure.upvalues[idx].lock().unwrap().clone())
|
||||||
} else {
|
} else {
|
||||||
Err(format!("Upvalue access out of bounds {}", idx))
|
Err(format!("Upvalue access out of bounds {}", idx))
|
||||||
}
|
}
|
||||||
@@ -192,14 +232,16 @@ impl VM {
|
|||||||
Address::Local(idx) => {
|
Address::Local(idx) => {
|
||||||
let frame = self.frames.last().ok_or("No call frame")?;
|
let frame = self.frames.last().ok_or("No call frame")?;
|
||||||
let abs_index = frame.stack_base + (idx as usize);
|
let abs_index = frame.stack_base + (idx as usize);
|
||||||
if abs_index >= self.stack.len() {
|
if abs_index < self.stack.len() {
|
||||||
if abs_index == self.stack.len() {
|
if let Value::Cell(cell) = &self.stack[abs_index] {
|
||||||
self.stack.push(value);
|
*cell.lock().unwrap() = value;
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("Stack gap write local {}", idx));
|
self.stack[abs_index] = value;
|
||||||
}
|
}
|
||||||
|
} else if abs_index == self.stack.len() {
|
||||||
|
self.stack.push(value);
|
||||||
} else {
|
} else {
|
||||||
self.stack[abs_index] = value;
|
return Err(format!("Stack gap write local {}", idx));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
@@ -212,7 +254,127 @@ impl VM {
|
|||||||
globals[idx] = value;
|
globals[idx] = value;
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
Address::Upvalue(_) => Err("Cannot assign to upvalue (Closure Upvalues are immutable)".to_string()),
|
Address::Upvalue(idx) => {
|
||||||
|
let frame = self.frames.last().ok_or("No call frame")?;
|
||||||
|
if let Some(closure) = &frame.closure {
|
||||||
|
let idx = idx as usize;
|
||||||
|
if idx < closure.upvalues.len() {
|
||||||
|
*closure.upvalues[idx].lock().unwrap() = value;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("Upvalue assignment out of bounds {}", idx))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err("Current frame has no closure".to_string())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::ast::types::{SourceLocation, NodeIdentity};
|
||||||
|
|
||||||
|
fn make_dummy_identity() -> Arc<NodeIdentity> {
|
||||||
|
Arc::new(NodeIdentity {
|
||||||
|
location: SourceLocation { line: 0, col: 0 },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_capture_boxing_modification() {
|
||||||
|
// Goal:
|
||||||
|
// var x = 10; (Local 0)
|
||||||
|
// var f = lambda { x = 20; }; (Local 1)
|
||||||
|
// f();
|
||||||
|
// return x; -> Should be 20
|
||||||
|
|
||||||
|
let id = make_dummy_identity();
|
||||||
|
|
||||||
|
// 1. Define Lambda Body: x = 20 (Upvalue 0)
|
||||||
|
let lambda_body = Node {
|
||||||
|
identity: id.clone(),
|
||||||
|
ty: StaticType::Void,
|
||||||
|
kind: BoundKind::Set {
|
||||||
|
addr: Address::Upvalue(0),
|
||||||
|
value: Box::new(Node {
|
||||||
|
identity: id.clone(),
|
||||||
|
ty: StaticType::Int,
|
||||||
|
kind: BoundKind::Constant(Value::Int(20)),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. Define Main Script
|
||||||
|
let root = Node {
|
||||||
|
identity: id.clone(),
|
||||||
|
ty: StaticType::Int,
|
||||||
|
kind: BoundKind::Block {
|
||||||
|
exprs: vec![
|
||||||
|
// x = 10 (Local 0) - simulate definition by assignment to stack gap
|
||||||
|
Node {
|
||||||
|
identity: id.clone(),
|
||||||
|
ty: StaticType::Int,
|
||||||
|
kind: BoundKind::Set {
|
||||||
|
addr: Address::Local(0),
|
||||||
|
value: Box::new(Node {
|
||||||
|
identity: id.clone(),
|
||||||
|
ty: StaticType::Int,
|
||||||
|
kind: BoundKind::Constant(Value::Int(10)),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// f = lambda capturing x (Local 1)
|
||||||
|
Node {
|
||||||
|
identity: id.clone(),
|
||||||
|
ty: StaticType::Any,
|
||||||
|
kind: BoundKind::Set {
|
||||||
|
addr: Address::Local(1),
|
||||||
|
value: Box::new(Node {
|
||||||
|
identity: id.clone(),
|
||||||
|
ty: StaticType::Any,
|
||||||
|
kind: BoundKind::Lambda {
|
||||||
|
param_count: 0,
|
||||||
|
upvalues: vec![Address::Local(0)], // Capture x
|
||||||
|
body: Arc::new(lambda_body),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// f()
|
||||||
|
Node {
|
||||||
|
identity: id.clone(),
|
||||||
|
ty: StaticType::Void,
|
||||||
|
kind: BoundKind::Call {
|
||||||
|
callee: Box::new(Node {
|
||||||
|
identity: id.clone(),
|
||||||
|
ty: StaticType::Any,
|
||||||
|
kind: BoundKind::Get(Address::Local(1)),
|
||||||
|
}),
|
||||||
|
args: vec![],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// return x (Local 0)
|
||||||
|
Node {
|
||||||
|
identity: id.clone(),
|
||||||
|
ty: StaticType::Int,
|
||||||
|
kind: BoundKind::Get(Address::Local(0)),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let globals = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let mut vm = VM::new(globals);
|
||||||
|
|
||||||
|
let result = vm.run(&root);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(Value::Int(val)) => assert_eq!(val, 20, "Captured variable should be modified to 20"),
|
||||||
|
Ok(val) => panic!("Expected Int(20), got {:?}", val),
|
||||||
|
Err(e) => panic!("VM Error: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use crate::ast::parser::Parser;
|
||||||
|
use crate::ast::compiler::binder::Binder;
|
||||||
|
use crate::ast::vm::VM;
|
||||||
|
use crate::ast::types::Value;
|
||||||
|
|
||||||
|
use crate::ast::nodes::UntypedKind;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_integer_constant() {
|
||||||
|
let source = "123";
|
||||||
|
let mut parser = Parser::new(source).expect("Failed to create parser");
|
||||||
|
let ast = parser.parse_expression().expect("Failed to parse");
|
||||||
|
|
||||||
|
if let UntypedKind::Constant(Value::Int(val)) = ast.kind {
|
||||||
|
assert_eq!(val, 123);
|
||||||
|
} else {
|
||||||
|
panic!("Expected Integer constant, got {:?}", ast.kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_negative_integer() {
|
||||||
|
let source = "-42";
|
||||||
|
let mut parser = Parser::new(source).expect("Failed to create parser");
|
||||||
|
let ast = parser.parse_expression().expect("Failed to parse");
|
||||||
|
|
||||||
|
if let UntypedKind::Constant(Value::Int(val)) = ast.kind {
|
||||||
|
assert_eq!(val, -42);
|
||||||
|
} else {
|
||||||
|
panic!("Expected Integer constant, got {:?}", ast.kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_float_constant() {
|
||||||
|
let source = "123.45";
|
||||||
|
let mut parser = Parser::new(source).expect("Failed to create parser");
|
||||||
|
let ast = parser.parse_expression().expect("Failed to parse");
|
||||||
|
|
||||||
|
if let UntypedKind::Constant(Value::Float(val)) = ast.kind {
|
||||||
|
assert_eq!(val, 123.45);
|
||||||
|
} else {
|
||||||
|
panic!("Expected Float constant, got {:?}", ast.kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_negative_float() {
|
||||||
|
let source = "-10.5";
|
||||||
|
let mut parser = Parser::new(source).expect("Failed to create parser");
|
||||||
|
let ast = parser.parse_expression().expect("Failed to parse");
|
||||||
|
|
||||||
|
if let UntypedKind::Constant(Value::Float(val)) = ast.kind {
|
||||||
|
assert_eq!(val, -10.5);
|
||||||
|
} else {
|
||||||
|
panic!("Expected Float constant, got {:?}", ast.kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_closure_modification_from_source() {
|
||||||
|
let source = r#"
|
||||||
|
(do
|
||||||
|
(def x 10)
|
||||||
|
(def f (fn [] (assign x 20)))
|
||||||
|
(f)
|
||||||
|
x
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let mut parser = Parser::new(source).expect("Failed to create parser");
|
||||||
|
let ast = parser.parse_expression().expect("Failed to parse");
|
||||||
|
|
||||||
|
let globals = Arc::new(Mutex::new(std::collections::HashMap::new()));
|
||||||
|
let mut binder = Binder::new(globals.clone());
|
||||||
|
let bound_ast = binder.bind(&ast).expect("Failed to bind");
|
||||||
|
|
||||||
|
let vm_globals = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let mut vm = VM::new(vm_globals);
|
||||||
|
let result = vm.run(&bound_ast);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(Value::Int(val)) => assert_eq!(val, 20, "Closure should modify outer variable"),
|
||||||
|
Ok(val) => panic!("Expected Int(20), got {:?}", val),
|
||||||
|
Err(e) => panic!("VM Error: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ use eframe::egui;
|
|||||||
use crate::ast::environment::Environment;
|
use crate::ast::environment::Environment;
|
||||||
|
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod integration_test;
|
||||||
|
|
||||||
fn main() -> eframe::Result {
|
fn main() -> eframe::Result {
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
|
|||||||
Reference in New Issue
Block a user