diff --git a/examples/closure.myc b/examples/closure.myc
index bf02503..b44f791 100644
--- a/examples/closure.myc
+++ b/examples/closure.myc
@@ -1,5 +1,5 @@
;; Closure Scope Test
-;; Benchmark: 600ns
+;; Benchmark: 700ns
;; Output: 15
(do
(def make-adder (fn [x]
diff --git a/examples/data.myc b/examples/data.myc
index 916fcd2..c97be19 100644
--- a/examples/data.myc
+++ b/examples/data.myc
@@ -1,5 +1,5 @@
;; Complex Data Structure Test
-;; Benchmark: 49.4us
+;; Benchmark: 51.8us
;; Output: {:input 10, :name "Fibonacci", :output 55}
(do
(def fib (fn [n]
diff --git a/examples/extreme_capture.myc b/examples/extreme_capture.myc
index 9b80bfc..e86b19e 100644
--- a/examples/extreme_capture.myc
+++ b/examples/extreme_capture.myc
@@ -1,4 +1,4 @@
-;; Benchmark: 1.5us
+;; Benchmark: 1.6us
;; Output: 36
(do
;; Excessive capture test
diff --git a/examples/macro_power.myc b/examples/macro_power.myc
index c1c85b3..0b0b025 100644
--- a/examples/macro_power.myc
+++ b/examples/macro_power.myc
@@ -1,4 +1,4 @@
-;; Benchmark: 300ns
+;; Benchmark: 400ns
;; Nested Macro and Arithmetic example
;; Output: 81
diff --git a/examples/macro_splice.myc b/examples/macro_splice.myc
index a6f4a28..9a2a931 100644
--- a/examples/macro_splice.myc
+++ b/examples/macro_splice.myc
@@ -1,4 +1,4 @@
-;; Benchmark: 200ns
+;; Benchmark: 300ns
;; Macro Splicing example
;; Demonstrates unrolling a list into another list.
;; Output: [0 1 2 3 4]
diff --git a/examples/macro_unless.myc b/examples/macro_unless.myc
index dcd7f78..63d8e0e 100644
--- a/examples/macro_unless.myc
+++ b/examples/macro_unless.myc
@@ -1,4 +1,4 @@
-;; Benchmark: 100ns
+;; Benchmark: 200ns
;; Classic "unless" macro example
;; Demonstrates simple template substitution.
;; Output: 42
diff --git a/examples/tak.myc b/examples/tak.myc
index b46a2c7..568f61e 100644
--- a/examples/tak.myc
+++ b/examples/tak.myc
@@ -1,17 +1,16 @@
-;; Benchmark: 602.1us
;; Takeuchi Function Benchmark
-;; Benchmark: ~150ms (Specialized) vs ~1.5s (Dynamic)
;; heavily recursive, benefits significantly from specialization of integer arithmetic
;; and direct function calls (skipping dynamic dispatch).
-;; Output: 7
-
+;; Output: 4
+
;; Benchmark: 468.7us
+
(do
(def tak (fn [x y z]
(if (<= x y)
- y
+ z
(tak (tak (- x 1) y z)
(tak (- y 1) z x)
(tak (- z 1) x y)))))
- (tak 10 6 3)
+ (tak 12 8 4)
)
diff --git a/examples/tco_test.myc b/examples/tco_test.myc
index 2ac1006..02e98f0 100644
--- a/examples/tco_test.myc
+++ b/examples/tco_test.myc
@@ -1,4 +1,4 @@
-;; Benchmark: 2.7ms
+;; Benchmark: 2.8ms
;; Output: "done"
(do
(def count_down (fn [n]
diff --git a/src/ast/compiler/specializer.rs b/src/ast/compiler/specializer.rs
index c2ae00c..4d64fda 100644
--- a/src/ast/compiler/specializer.rs
+++ b/src/ast/compiler/specializer.rs
@@ -232,7 +232,7 @@ mod tests {
// Mock Registry
struct MockRegistry {
- functions: HashMap
,
+ functions: HashMap,
}
impl MockRegistry {
@@ -240,13 +240,13 @@ mod tests {
Self { functions: HashMap::new() }
}
- fn register(&mut self, addr: Address, node: TypedNode) {
+ fn register(&mut self, addr: Address, node: BoundNode) {
self.functions.insert(addr, node);
}
}
impl FunctionRegistry for MockRegistry {
- fn resolve(&self, addr: Address) -> Option {
+ fn resolve(&self, addr: Address) -> Option {
self.functions.get(&addr).cloned()
}
}
@@ -259,19 +259,19 @@ mod tests {
let name = Symbol::from("test_func");
// Def: (fn [x] x) -- generic identity
- let func_node = TypedNode {
+ let func_node = BoundNode {
identity: make_identity(),
kind: BoundKind::Lambda {
param_count: 1,
upvalues: vec![],
- body: Rc::new(TypedNode { identity: make_identity(), kind: BoundKind::Nop, ty: StaticType::Void })
+ body: Rc::new(BoundNode { identity: make_identity(), kind: BoundKind::Nop, ty: () })
},
- ty: StaticType::Void
+ ty: ()
};
registry.register(addr, func_node);
// Setup Compiler Mock
- let compiler: CompileFunc = Rc::new(|_node: TypedNode, _args: &[StaticType]| -> Result<(Value, StaticType), String> {
+ let compiler: CompileFunc = Rc::new(|_node: BoundNode, _args: &[StaticType]| -> Result<(Value, StaticType), String> {
// Return a specialized "compiled" value
Ok((Value::Int(12345), StaticType::Int))
});
diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs
index 59a42b7..02216d9 100644
--- a/src/ast/compiler/type_checker.rs
+++ b/src/ast/compiler/type_checker.rs
@@ -288,42 +288,54 @@ mod tests {
env.compile(source).unwrap()
}
+ fn get_ret_type(node: &TypedNode) -> StaticType {
+ if let StaticType::Function(sig) = &node.ty {
+ sig.ret.clone()
+ } else {
+ node.ty.clone()
+ }
+ }
+
#[test]
fn test_inference_constants() {
- assert_eq!(check_source("10").ty, StaticType::Int);
- assert_eq!(check_source("10.5").ty, StaticType::Float);
- assert_eq!(check_source("true").ty, StaticType::Bool);
- assert_eq!(check_source("\"hello\"").ty, StaticType::Text);
+ assert_eq!(get_ret_type(&check_source("10")), StaticType::Int);
+ assert_eq!(get_ret_type(&check_source("10.5")), StaticType::Float);
+ assert_eq!(get_ret_type(&check_source("true")), StaticType::Bool);
+ assert_eq!(get_ret_type(&check_source("\"hello\"")), StaticType::Text);
}
#[test]
fn test_inference_variable_propagation() {
// (do (def x 10) x) -> The last 'x' must be Int
let typed = check_source("(do (def x 10) x)");
- if let BoundKind::Block { exprs } = typed.kind {
- let last_expr = exprs.last().unwrap();
- assert_eq!(last_expr.ty, StaticType::Int, "Variable 'x' should be inferred as Int");
- } else { panic!("Expected block"); }
+ // Outer is Lambda, Body is Block
+ if let BoundKind::Lambda { body, .. } = &typed.kind {
+ if let BoundKind::Block { exprs } = &body.kind {
+ let last_expr = exprs.last().unwrap();
+ assert_eq!(last_expr.ty, StaticType::Int, "Variable 'x' should be inferred as Int");
+ } else { panic!("Expected block in lambda body"); }
+ } else { panic!("Expected Lambda wrapper"); }
}
#[test]
fn test_inference_block_type() {
// Block type = last expression type
- assert_eq!(check_source("(do 1 2.5)").ty, StaticType::Float);
- assert_eq!(check_source("(do 1.5 \"test\")").ty, StaticType::Text);
+ assert_eq!(get_ret_type(&check_source("(do 1 2.5)")), StaticType::Float);
+ assert_eq!(get_ret_type(&check_source("(do 1.5 \"test\")")), StaticType::Text);
}
#[test]
fn test_inference_lambda_return() {
// (fn [a] 10) -> fn(any) -> Int
+ // Since it's already a Lambda, it's NOT wrapped further.
let typed = check_source("(fn [a] 10)");
- if let StaticType::Function(sig) = typed.ty {
+ if let StaticType::Function(sig) = &typed.ty {
assert_eq!(sig.ret, StaticType::Int);
} else { panic!("Expected function type, got {:?}", typed.ty); }
// Nested: (fn [] (do 1 2.5)) -> fn() -> Float
let typed_nested = check_source("(fn [] (do 1 2.5))");
- if let StaticType::Function(sig) = typed_nested.ty {
+ if let StaticType::Function(sig) = &typed_nested.ty {
assert_eq!(sig.ret, StaticType::Float);
} else { panic!("Expected function type"); }
}
@@ -332,29 +344,31 @@ mod tests {
fn test_inference_assignment_updates_type() {
// (do (def x 10) (assign x 20.5) x) -> x becomes Float after assignment
let typed = check_source("(do (def x 10) (assign x 20.5) x)");
- if let BoundKind::Block { exprs } = typed.kind {
- let last_expr = exprs.last().unwrap();
- assert_eq!(last_expr.ty, StaticType::Float, "Variable 'x' should be specialized to Float after assignment");
- } else { panic!("Expected block"); }
+ if let BoundKind::Lambda { body, .. } = &typed.kind {
+ if let BoundKind::Block { exprs } = &body.kind {
+ let last_expr = exprs.last().unwrap();
+ assert_eq!(last_expr.ty, StaticType::Float, "Variable 'x' should be specialized to Float after assignment");
+ } else { panic!("Expected block"); }
+ } else { panic!("Expected Lambda"); }
}
#[test]
fn test_operator_overloading_inference() {
- assert_eq!(check_source("(+ 1 2)").ty, StaticType::Int);
- assert_eq!(check_source("(+ 1.0 2.0)").ty, StaticType::Float);
- assert_eq!(check_source("(+ \"a\" \"b\")").ty, StaticType::Text);
- assert_eq!(check_source("(/ 1 2)").ty, StaticType::Float);
+ assert_eq!(get_ret_type(&check_source("(+ 1 2)")), StaticType::Int);
+ assert_eq!(get_ret_type(&check_source("(+ 1.0 2.0)")), StaticType::Float);
+ assert_eq!(get_ret_type(&check_source("(+ \"a\" \"b\")")), StaticType::Text);
+ assert_eq!(get_ret_type(&check_source("(/ 1 2)")), StaticType::Float);
}
#[test]
fn test_datetime_inference() {
// date("2023-01-01") -> DateTime
- assert_eq!(check_source("(date \"2023-01-01\")").ty, StaticType::DateTime);
+ assert_eq!(get_ret_type(&check_source("(date \"2023-01-01\")")), StaticType::DateTime);
// DateTime + Int -> DateTime
- assert_eq!(check_source("(+ (date \"2023-01-01\") 86400000)").ty, StaticType::DateTime);
+ assert_eq!(get_ret_type(&check_source("(+ (date \"2023-01-01\") 86400000)")), StaticType::DateTime);
// DateTime - DateTime -> Int (Duration)
- assert_eq!(check_source("(- (date \"2023-01-02\") (date \"2023-01-01\"))").ty, StaticType::Int);
+ assert_eq!(get_ret_type(&check_source("(- (date \"2023-01-02\") (date \"2023-01-01\"))")), StaticType::Int);
// DateTime comparison -> Bool
- assert_eq!(check_source("(> (date \"2023-01-02\") (date \"2023-01-01\"))").ty, StaticType::Bool);
+ assert_eq!(get_ret_type(&check_source("(> (date \"2023-01-02\") (date \"2023-01-01\"))")), StaticType::Bool);
}
}
diff --git a/src/ast/environment.rs b/src/ast/environment.rs
index 9d77d68..f7ee9f2 100644
--- a/src/ast/environment.rs
+++ b/src/ast/environment.rs
@@ -45,10 +45,8 @@ struct RuntimeMacroEvaluator {
global_names: Rc>>,
global_types: Rc>>,
global_values: Rc>>,
- function_registry: Rc>>,
}
-
impl MacroEvaluator for RuntimeMacroEvaluator {
fn evaluate(&self, node: &Node, bindings: &HashMap, Node>) -> Result {
// 1. Check if it's a simple parameter substitution
@@ -97,7 +95,6 @@ impl Environment {
global_names: self.global_names.clone(),
global_types: self.global_types.clone(),
global_values: self.global_values.clone(),
- function_registry: self.function_registry.clone(),
};
MacroExpander::new(MacroRegistry::new(), evaluator)
}
@@ -234,7 +231,16 @@ impl Environment {
/// Runtime: Execute the linked AST in the VM
pub fn run(&self, node: &TypedNode) -> Result {
let mut vm = VM::new(self.global_values.clone());
- vm.run(node)
+ let result = vm.run(node)?;
+
+ if let Value::Object(obj) = &result {
+ if let Some(closure) = obj.as_any().downcast_ref::() {
+ // Execute the script body
+ return vm.run(&closure.function_node);
+ }
+ }
+
+ Ok(result)
}
pub fn run_script(&self, source: &str) -> Result {
@@ -258,8 +264,15 @@ impl Environment {
// Execute with TracingObserver
let mut vm = VM::new(self.global_values.clone());
let mut observer = TracingObserver::new();
- let result = vm.run_with_observer(&mut observer, &linked);
+ let mut result = vm.run_with_observer(&mut observer, &linked);
+ // If result is a closure (script entry), execute the body too
+ if let Ok(Value::Object(obj)) = &result {
+ if let Some(closure) = obj.as_any().downcast_ref::() {
+ result = vm.run_with_observer(&mut observer, &closure.function_node);
+ }
+ }
+
Ok((result, observer.logs))
}
}
diff --git a/src/ast/vm.rs b/src/ast/vm.rs
index 4826106..64aea82 100644
--- a/src/ast/vm.rs
+++ b/src/ast/vm.rs
@@ -288,10 +288,36 @@ impl VM {
closure: None,
});
- let result = self.eval(root);
-
+ let mut result = self.eval(root);
self.frames.pop();
- result
+
+ loop {
+ match result {
+ Ok(Value::TailCallRequest(payload)) => {
+ let (next_obj, next_args) = *payload;
+ if let Some(closure) = next_obj.as_any().downcast_ref::() {
+ let old_stack_top = 0; // Root is always base 0
+ let closure_rc = Rc::new(closure.clone());
+
+ // Reset stack for the next call (TCO)
+ self.stack.clear();
+ self.stack.extend(next_args);
+
+ self.frames.push(CallFrame {
+ stack_base: old_stack_top,
+ closure: Some(closure_rc),
+ });
+
+ result = self.eval(&closure.function_node);
+
+ self.frames.pop();
+ } else {
+ return Err(format!("Tail call target is not a closure: {}", next_obj.type_name()));
+ }
+ }
+ _ => return result,
+ }
+ }
}
pub fn run_with_observer(&mut self, observer: &mut O, root: &TypedNode) -> Result {
diff --git a/src/integration_test.rs b/src/integration_test.rs
index 529c2e6..a71ba30 100644
--- a/src/integration_test.rs
+++ b/src/integration_test.rs
@@ -1,10 +1,6 @@
#[cfg(test)]
mod tests {
- use std::rc::Rc;
- use std::cell::RefCell;
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;
use crate::ast::environment::Environment;
@@ -73,21 +69,10 @@ mod tests {
)
"#;
- let mut parser = Parser::new(source).expect("Failed to create parser");
- let ast = parser.parse_expression().expect("Failed to parse");
-
- let globals = Rc::new(RefCell::new(std::collections::HashMap::new()));
- let mut binder = Binder::new(globals.clone());
- let bound_ast = binder.bind(&ast).expect("Failed to bind");
-
- // Use the real TypeChecker instead of the dummy placeholder
- let global_types = Rc::new(RefCell::new(std::collections::HashMap::new()));
- let checker = crate::ast::compiler::TypeChecker::new(global_types);
- let typed_ast = checker.check(bound_ast).expect("Type check failed");
-
- let vm_globals = Rc::new(RefCell::new(Vec::new()));
- let mut vm = VM::new(vm_globals);
- let result = vm.run(&typed_ast);
+ let env = Environment::new();
+ let compiled = env.compile(source).expect("Failed to compile");
+ let linked = env.link(compiled);
+ let result = env.run(&linked);
match result {
Ok(Value::Int(val)) => assert_eq!(val, 20, "Closure should modify outer variable"),