Compare commits

4 Commits

20 changed files with 1483 additions and 440 deletions
+12 -8
View File
@@ -1,6 +1,6 @@
# Aster # Aster
Aster 是一门使用 Rust 编写的动态类型脚本语言,采用**基于栈的字节码 VM** 执行。无外部依赖,纯标准库。 Aster 是一门使用 Rust 编写的动态类型脚本语言解释器,支持两种执行模式:**树遍历(tree-walker** 和 **字节码 VM**。无外部依赖,纯标准库。
## 快速开始 ## 快速开始
@@ -11,9 +11,12 @@ cargo build
# 启动 REPL(交互式) # 启动 REPL(交互式)
cargo run cargo run
# 执行脚本文件 # 执行脚本文件(树遍历模式)
cargo run -- examples/script.ast cargo run -- examples/script.ast
# 执行脚本文件(字节码 VM 模式,性能更优)
cargo run -- --vm examples/script.ast
# 运行测试 # 运行测试
cargo test cargo test
``` ```
@@ -159,7 +162,8 @@ let t = os.clock(); // 模块化调用
## 架构 ## 架构
``` ```
源码文本 → Lexer → Tokens → Parser → AST → Compiler → Bytecode → VM → 输出 源码文本 → Lexer → Tokens → Parser → AST ──→ Interpreter(树遍历)→ 输出
└─→ Compiler → Bytecode → VM → 输出
``` ```
| 模块 | 职责 | | 模块 | 职责 |
@@ -167,17 +171,17 @@ let t = os.clock(); // 模块化调用
| `lexer/` | 词法分析。`Lexer::tokenize()` 将源码转为 `(Vec<Token>, Vec<Error>)`,Token 记录行列号用于错误报告,支持 `//` 单行注释和 `/* */` 块注释 | | `lexer/` | 词法分析。`Lexer::tokenize()` 将源码转为 `(Vec<Token>, Vec<Error>)`,Token 记录行列号用于错误报告,支持 `//` 单行注释和 `/* */` 块注释 |
| `parser/` | 递归下降 + Pratt 解析器。`Parser::parse()` 返回 `(Vec<Stmt>, Vec<Error>)`,遇到语法错误通过 `synchronize()` 跳过至下一条语句边界继续解析 | | `parser/` | 递归下降 + Pratt 解析器。`Parser::parse()` 返回 `(Vec<Stmt>, Vec<Error>)`,遇到语法错误通过 `synchronize()` 跳过至下一条语句边界继续解析 |
| `ast/` | AST 节点定义。`Expr`(表达式)涵盖字面量、变量、赋值、属性/索引访问、一元/二元/逻辑运算、函数调用、lambda 和对象/数组字面量。`Stmt`(语句)涵盖 let、表达式语句、块、if/while/for、函数、return/break/continue | | `ast/` | AST 节点定义。`Expr`(表达式)涵盖字面量、变量、赋值、属性/索引访问、一元/二元/逻辑运算、函数调用、lambda 和对象/数组字面量。`Stmt`(语句)涵盖 let、表达式语句、块、if/while/for、函数、return/break/continue |
| `runtime/` | 运行时类型:`Value` 枚举、`FunctionProto`(编译后的函数蓝图)、`Closure`(函数原型 + 捕获的 upvalue)、`Runtime` trait、`NativeFn` 类型。`builtins/` 按功能分组注册原生函数 | | `interpreter/` | 树遍历求值器。`Env` 是基于 `Rc<RefCell<>>` 的链式作用域。`Signal` 枚举通过调用栈传播 `Return`/`Break`/`Continue``builtins/``io``os` 模块组织原生函数,同时注册为全局函数以方便使用 |
| `vm/` | 字节码 VM。`Compiler` 将 AST 编译为基于栈的字节码44 条指令)`Vm` 执行字节码。支持闭包 upvalue 捕获、3 层嵌套闭包、`require()` 模块加载 | | `vm/` | 字节码 VM。`Compiler` 将 AST 编译为基于栈的字节码,`Vm` 执行字节码指令。支持闭包 upvalue 捕获、3 层嵌套闭包、`require()` 模块加载。通过 `--vm` 标志启用 |
| `error/` | 三种错误:`LexError`(行列号)、`ParseError`Token)、`RuntimeError`(可选 Token | | `error/` | 三种错误:`LexError`(行列号)、`ParseError`Token)、`RuntimeError`(可选 Token |
### 关键设计决策 ### 关键设计决策
- **零外部依赖** — 全部基于 Rust 标准库构建 - **零外部依赖** — 全部基于 Rust 标准库构建
- **基于栈的字节码 VM** — AST 先编译为字节码再执行,单一执行路径,无语义漂移 - **双执行模式** — 树遍历模式(默认,适合交互和调试)和字节码 VM 模式(`--vm`,约 40% 性能提升),共享同一 AST 和运行时语义
- **错误容忍解析** — 词法分析器和解析器均将错误收集到 `Vec` 中,单次运行可报告多个诊断信息,而非在第一个错误处就中止 - **错误容忍解析** — 词法分析器和解析器均将错误收集到 `Vec` 中,单次运行可报告多个诊断信息,而非在第一个错误处就中止
- **`Rc<RefCell<>>` 共享所有权** — 用于 `Value::Object``Value::Array``UpvalueObj`,提供动态可变语义。闭包通过 upvalue 机制捕获外层局部变量 - **`Rc<RefCell<>>` 共享所有权** — 用于 `Env` 链、`Value::Object``Value::Array``Value::Function`,提供动态可变语义
- **词法作用域闭包** — 编译时解析 upvalue 捕获,通过增量链路支持任意深度嵌套;`resolve_upvalue` 中显式处理 3 层穿透(本地→父级→祖级),更深层级通过逐层编译自然建立 - **词法作用域闭包** — `Function` 在定义时捕获 `Env`,lambda 表达式同理;VM 通过 upvalue 机制支持最多 3 层传递式捕获
## 许可证 ## 许可证
@@ -1,7 +1,7 @@
//! 标准库:corelen, typeof, push, pop //! 标准库:corelen, typeof, push, pop
use crate::error::RuntimeError; use crate::error::RuntimeError;
use crate::runtime::{Runtime, Value}; use crate::interpreter::{Runtime, Value};
pub fn len(_runtime: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> { pub fn len(_runtime: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() { if args.is_empty() {
@@ -1,7 +1,7 @@
//! 标准库:ioprint, input //! 标准库:ioprint, input
use crate::error::RuntimeError; use crate::error::RuntimeError;
use crate::runtime::{Runtime, Value}; use crate::interpreter::{Runtime, Value};
pub fn print(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> { pub fn print(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
for arg in args.iter() { for arg in args.iter() {
@@ -0,0 +1,52 @@
//! 内置函数模块:按功能分组注册,便于扩展和维护。
pub mod core;
pub mod io;
pub mod os;
pub mod string;
use crate::interpreter::{Env, Value};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
/// 向全局环境注册所有标准库(io、os 等)
pub fn register_all(env: &Rc<RefCell<Env>>) {
let mut e = env.borrow_mut();
// io
let mut io = HashMap::new();
io.insert("print".into(), Value::NativeFunction(Rc::new(io::print)));
io.insert("input".into(), Value::NativeFunction(Rc::new(io::input)));
e.define("io".into(), Value::Object(Rc::new(RefCell::new(io))), true);
// input and print can be used as global functions for convenience
e.define("print".into(), Value::NativeFunction(Rc::new(io::print)), true);
e.define("input".into(), Value::NativeFunction(Rc::new(io::input)), true);
// os
let mut os = HashMap::new();
os.insert("clock".into(), Value::NativeFunction(Rc::new(os::clock)));
e.define("os".into(), Value::Object(Rc::new(RefCell::new(os))), true);
// clock can also be used as a global function for convenience
e.define("clock".into(), Value::NativeFunction(Rc::new(os::clock)), true);
// core — len, typeof, push, pop
e.define("len".into(), Value::NativeFunction(Rc::new(core::len)), true);
e.define("typeof".into(), Value::NativeFunction(Rc::new(core::typeof_fn)), true);
e.define("push".into(), Value::NativeFunction(Rc::new(core::push)), true);
e.define("pop".into(), Value::NativeFunction(Rc::new(core::pop)), true);
// require — module loader
e.define("require".into(), Value::NativeFunction(Rc::new(super::module::require_fn)), true);
// string — split, trim, substring, replace, contains, upper, lower, starts_with, ends_with
e.define("split".into(), Value::NativeFunction(Rc::new(string::split)), true);
e.define("trim".into(), Value::NativeFunction(Rc::new(string::trim)), true);
e.define("substring".into(), Value::NativeFunction(Rc::new(string::substring)), true);
e.define("replace".into(), Value::NativeFunction(Rc::new(string::replace)), true);
e.define("contains".into(), Value::NativeFunction(Rc::new(string::contains)), true);
e.define("upper".into(), Value::NativeFunction(Rc::new(string::upper)), true);
e.define("lower".into(), Value::NativeFunction(Rc::new(string::lower)), true);
e.define("starts_with".into(), Value::NativeFunction(Rc::new(string::starts_with)), true);
e.define("ends_with".into(), Value::NativeFunction(Rc::new(string::ends_with)), true);
}
@@ -1,7 +1,7 @@
//! 标准库:osclock //! 标准库:osclock
use crate::error::RuntimeError; use crate::error::RuntimeError;
use crate::runtime::{Runtime, Value}; use crate::interpreter::{Runtime, Value};
pub fn clock(_interp: &mut dyn Runtime, _args: Vec<Value>) -> Result<Value, RuntimeError> { pub fn clock(_interp: &mut dyn Runtime, _args: Vec<Value>) -> Result<Value, RuntimeError> {
Ok(Value::Number( Ok(Value::Number(
@@ -1,7 +1,7 @@
//! 标准库:stringsplit, trim, substring, replace, contains, upper, lower, starts_with, ends_with //! 标准库:stringsplit, trim, substring, replace, contains, upper, lower, starts_with, ends_with
use crate::error::RuntimeError; use crate::error::RuntimeError;
use crate::runtime::{Runtime, Value}; use crate::interpreter::{Runtime, Value};
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
+50
View File
@@ -0,0 +1,50 @@
use super::Value;
use std::collections::HashMap;
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug)]
pub struct Env {
/// (value, is_mutable) — `is_mutable` is true for `let`, false for `const`.
pub values: HashMap<String, (Value, bool)>,
pub parent: Option<Rc<RefCell<Env>>>,
}
impl Env {
pub fn new(parent: Option<Rc<RefCell<Env>>>) -> Self {
Self {
values: HashMap::new(),
parent,
}
}
pub fn define(&mut self, name: String, val: Value, mutable: bool) {
self.values.insert(name, (val, mutable));
}
/// Assign a new value to an existing binding. Returns `Err(msg)` if the
/// binding exists but was declared with `const` (immutable).
pub fn assign(&mut self, name: &str, val: Value) -> Result<bool, String> {
if let Some((_, false)) = self.values.get(name) {
return Err(format!("Cannot reassign constant '{}'", name));
}
if self.values.contains_key(name) {
self.values.insert(name.to_string(), (val, true));
Ok(true)
} else if let Some(parent) = &self.parent {
parent.borrow_mut().assign(name, val)
} else {
Ok(false)
}
}
pub fn get(&self, name: &str) -> Option<Value> {
if let Some((val, _)) = self.values.get(name) {
Some(val.clone())
} else if let Some(parent) = &self.parent {
parent.borrow().get(name)
} else {
None
}
}
}
+624
View File
@@ -0,0 +1,624 @@
use crate::ast::*;
use crate::ast::expr::{Literal, UnaryOp, BinaryOp, LogicalOp, AssignOp};
use crate::error::RuntimeError;
use crate::interpreter::Signal;
use super::{Value, Env, Function};
use std::collections::HashMap;
use std::rc::Rc;
use std::cell::RefCell;
impl super::Interpreter {
// ========================================================================
// evaluate — dispatcher
// ========================================================================
pub fn evaluate(&mut self, expr: Expr) -> Result<Value, RuntimeError> {
match expr {
Expr::Literal(lit) => self.eval_literal(lit),
Expr::Variable(name) => self.eval_variable(name),
Expr::Assign { name, op, value } => self.eval_assign(name, op, *value),
Expr::Get { object, name } => self.eval_get(*object, name),
Expr::Set { object, name, op, value } => self.eval_set(*object, name, op, *value),
Expr::IndexGet { array, index } => self.eval_index_get(*array, *index),
Expr::IndexSet { array, index, op, value } => self.eval_index_set(*array, *index, op, *value),
Expr::ObjectLiteral { properties } => self.eval_object_literal(properties),
Expr::ArrayLiteral { elements } => self.eval_array_literal(elements),
Expr::Unary { op, right } => self.eval_unary(op, *right),
Expr::Binary { left, op, right } => self.eval_binary(*left, op, *right),
Expr::Logical { left, op, right } => self.eval_logical(*left, op, *right),
Expr::Ternary { condition, then_branch, else_branch } => {
self.eval_ternary(*condition, *then_branch, *else_branch)
}
Expr::Call { callee, arguments } => self.eval_call(*callee, arguments),
Expr::Lambda { params, body } => self.eval_lambda(params, body),
}
}
// ========================================================================
// eval_* methods
// ========================================================================
fn eval_literal(&mut self, lit: Literal) -> Result<Value, RuntimeError> {
Ok(match lit {
Literal::Number(n) => Value::Number(n),
Literal::String(s) => Value::String(s),
Literal::Bool(b) => Value::Bool(b),
Literal::Nil => Value::Nil,
})
}
fn eval_variable(&mut self, name: String) -> Result<Value, RuntimeError> {
match self.env.borrow().get(&name) {
Some(val) => Ok(val),
None => Err(RuntimeError::RuntimeError {
message: format!("Undefined variable '{}'", name),
token: None,
}),
}
}
fn eval_assign(&mut self, name: String, op: AssignOp, value: Expr) -> Result<Value, RuntimeError> {
let rhs = self.evaluate(value)?;
let val = match op {
AssignOp::Equal => rhs,
_ => {
let current = self.env.borrow().get(&name).ok_or_else(|| RuntimeError::RuntimeError {
message: format!("Undefined variable '{}'", name),
token: None,
})?;
self.apply_assign_op(current.clone(), rhs, op)?
}
};
match self.env.borrow_mut().assign(&name, val.clone()) {
Ok(true) => {}
Ok(false) => {
return Err(RuntimeError::RuntimeError {
message: format!("Undefined variable '{}'", name),
token: None,
});
}
Err(msg) => {
return Err(RuntimeError::RuntimeError {
message: msg,
token: None,
});
}
}
Ok(val)
}
fn eval_get(&mut self, object: Expr, name: String) -> Result<Value, RuntimeError> {
let obj = self.evaluate(object)?;
match obj {
Value::Object(_) => {
match obj.get(&name) {
Some(val) => Ok(val),
None => Err(RuntimeError::RuntimeError {
message: format!("Undefined property '{}'", name),
token: None,
}),
}
}
Value::Array(arr) => match name.as_str() {
"length" => Ok(Value::Number(arr.borrow().len() as f64)),
"push" => {
let arr = Rc::clone(&arr);
Ok(Value::NativeFunction(Rc::new(move |_runtime: &mut dyn super::Runtime, mut args: Vec<Value>| {
let val = args.pop().unwrap_or(Value::Nil);
arr.borrow_mut().push(val.clone());
Ok(val)
})))
}
"pop" => {
let arr = Rc::clone(&arr);
Ok(Value::NativeFunction(Rc::new(move |_runtime: &mut dyn super::Runtime, _args: Vec<Value>| {
arr.borrow_mut()
.pop()
.ok_or_else(|| RuntimeError::RuntimeError {
message: "pop() on empty array".into(),
token: None,
})
})))
}
_ => Err(RuntimeError::RuntimeError {
message: format!("Array has no property '{}'", name),
token: None,
}),
},
Value::String(s) => match name.as_str() {
"length" => Ok(Value::Number(s.chars().count() as f64)),
"upper" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::upper(runtime, all_args)
})))
}
"lower" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::lower(runtime, all_args)
})))
}
"trim" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::trim(runtime, all_args)
})))
}
"substring" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::substring(runtime, all_args)
})))
}
"replace" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::replace(runtime, all_args)
})))
}
"contains" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::contains(runtime, all_args)
})))
}
"starts_with" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::starts_with(runtime, all_args)
})))
}
"ends_with" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::ends_with(runtime, all_args)
})))
}
"split" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::split(runtime, all_args)
})))
}
_ => Err(RuntimeError::RuntimeError {
message: format!("String has no property '{}'", name),
token: None,
}),
},
_ => Err(RuntimeError::RuntimeError {
message: "Only objects have properties".to_string(),
token: None,
}),
}
}
fn eval_set(&mut self, object: Expr, name: String, op: AssignOp, value: Expr) -> Result<Value, RuntimeError> {
let obj = self.evaluate(object)?;
let rhs = self.evaluate(value)?;
match obj {
Value::Object(_) => {
let val = match op {
AssignOp::Equal => rhs,
_ => {
let current = obj.get(&name).ok_or_else(|| RuntimeError::RuntimeError {
message: format!("Property '{}' does not exist", name),
token: None,
})?;
self.apply_assign_op(current, rhs, op)?
}
};
obj.set(&name, val.clone())?;
Ok(val)
}
_ => Err(RuntimeError::RuntimeError {
message: "Only objects have properties".to_string(),
token: None,
}),
}
}
fn eval_index_get(&mut self, array: Expr, index: Expr) -> Result<Value, RuntimeError> {
let target = self.evaluate(array)?;
let idx_val = self.evaluate(index)?;
// Object index access: obj["key"]
if let Value::Object(_) = &target {
let key = match &idx_val {
Value::String(s) => s.clone(),
_ => return Err(RuntimeError::RuntimeError {
message: "Object index must be a string".into(),
token: None,
}),
};
return target.get(&key).ok_or_else(|| RuntimeError::RuntimeError {
message: format!("Undefined property '{}'", key),
token: None,
});
}
let i = self.as_array_index(&idx_val)?;
match target {
Value::Array(vec) => {
let vec = vec.borrow();
vec.get(i).cloned().ok_or_else(|| RuntimeError::RuntimeError {
message: format!("Index {} out of bounds (len {})", i, vec.len()),
token: None,
})
}
Value::String(s) => {
let chars: Vec<char> = s.chars().collect();
chars.get(i).map(|&c| Value::String(c.to_string())).ok_or_else(|| RuntimeError::RuntimeError {
message: format!("Index {} out of bounds (len {})", i, chars.len()),
token: None,
})
}
_ => Err(RuntimeError::RuntimeError {
message: "Index access on non-array, non-string, non-object value".to_string(),
token: None,
}),
}
}
fn eval_index_set(&mut self, array: Expr, index: Expr, op: AssignOp, value: Expr) -> Result<Value, RuntimeError> {
let target = self.evaluate(array)?;
let idx_val = self.evaluate(index)?;
let rhs = self.evaluate(value)?;
// Object index assignment: obj["key"] = value
if let Value::Object(_) = &target {
let key = match &idx_val {
Value::String(s) => s.clone(),
_ => return Err(RuntimeError::RuntimeError {
message: "Object index must be a string".into(),
token: None,
}),
};
let val = match op {
AssignOp::Equal => rhs,
_ => {
let current = target.get(&key).ok_or_else(|| RuntimeError::RuntimeError {
message: format!("Property '{}' does not exist", key),
token: None,
})?;
self.apply_assign_op(current, rhs, op)?
}
};
target.set(&key, val.clone())?;
return Ok(val);
}
let i = self.as_array_index(&idx_val)?;
match target {
Value::Array(vec) => {
let mut vec = vec.borrow_mut();
if i >= vec.len() {
return Err(RuntimeError::RuntimeError {
message: format!("Index {} out of bounds (len {})", i, vec.len()),
token: None,
});
}
let val = match op {
AssignOp::Equal => rhs,
_ => {
let current = vec[i].clone();
self.apply_assign_op(current, rhs, op)?
}
};
vec[i] = val.clone();
Ok(val)
}
_ => Err(RuntimeError::RuntimeError {
message: "Index assignment on non-array, non-object value".to_string(),
token: None,
}),
}
}
fn eval_object_literal(&mut self, properties: Vec<(String, Expr)>) -> Result<Value, RuntimeError> {
let mut map = HashMap::new();
for (key, value_expr) in properties {
let value = self.evaluate(value_expr)?;
map.insert(key, value);
}
Ok(Value::Object(Rc::new(RefCell::new(map))))
}
fn eval_array_literal(&mut self, elements: Vec<Expr>) -> Result<Value, RuntimeError> {
let mut arr = Vec::new();
for e in elements {
arr.push(self.evaluate(e)?);
}
Ok(Value::Array(Rc::new(RefCell::new(arr))))
}
fn eval_unary(&mut self, op: UnaryOp, right: Expr) -> Result<Value, RuntimeError> {
let val = self.evaluate(right)?;
match op {
UnaryOp::Negate => match val {
Value::Number(n) => Ok(Value::Number(-n)),
_ => Err(RuntimeError::RuntimeError {
message: "Unary '-' on non-number".to_string(),
token: None,
}),
},
UnaryOp::Not => Ok(Value::Bool(!self.is_truthy(&val))),
}
}
fn eval_binary(&mut self, left: Expr, op: BinaryOp, right: Expr) -> Result<Value, RuntimeError> {
let l = self.evaluate(left)?;
let r = self.evaluate(right)?;
match op {
BinaryOp::Add => self.eval_binary_add(l, r),
BinaryOp::Sub => self.eval_binary_arith(l, r, |a, b| a - b, "-"),
BinaryOp::Mul => self.eval_binary_arith(l, r, |a, b| a * b, "*"),
BinaryOp::Div => self.eval_binary_div(l, r),
BinaryOp::Mod => self.eval_binary_mod(l, r),
BinaryOp::Greater => Ok(Value::Bool(self.as_number(&l)? > self.as_number(&r)?)),
BinaryOp::GreaterEqual => Ok(Value::Bool(self.as_number(&l)? >= self.as_number(&r)?)),
BinaryOp::Less => Ok(Value::Bool(self.as_number(&l)? < self.as_number(&r)?)),
BinaryOp::LessEqual => Ok(Value::Bool(self.as_number(&l)? <= self.as_number(&r)?)),
BinaryOp::Equal => Ok(Value::Bool(self.is_equal(&l, &r))),
BinaryOp::NotEqual => Ok(Value::Bool(!self.is_equal(&l, &r))),
}
}
fn eval_binary_add(&mut self, l: Value, r: Value) -> Result<Value, RuntimeError> {
if matches!(l, Value::String(_)) || matches!(r, Value::String(_)) {
Ok(Value::String(format!("{}{}", l, r)))
} else {
match (l, r) {
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)),
_ => Err(RuntimeError::RuntimeError {
message: "Invalid '+' operands".to_string(),
token: None,
}),
}
}
}
fn eval_binary_arith(
&mut self,
l: Value,
r: Value,
op_fn: fn(f64, f64) -> f64,
name: &str,
) -> Result<Value, RuntimeError> {
match (l, r) {
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(op_fn(a, b))),
_ => Err(RuntimeError::RuntimeError {
message: format!("Invalid '{}' operands", name),
token: None,
}),
}
}
fn eval_binary_div(&mut self, l: Value, r: Value) -> Result<Value, RuntimeError> {
match (l, r) {
(Value::Number(_), Value::Number(b)) if b == 0.0 => Err(RuntimeError::RuntimeError {
message: "Division by zero".to_string(),
token: None,
}),
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(a / b)),
_ => Err(RuntimeError::RuntimeError {
message: "Invalid '/' operands".to_string(),
token: None,
}),
}
}
fn eval_binary_mod(&mut self, l: Value, r: Value) -> Result<Value, RuntimeError> {
match (l, r) {
(Value::Number(_), Value::Number(b)) if b == 0.0 => Err(RuntimeError::RuntimeError {
message: "Modulo by zero".to_string(),
token: None,
}),
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(a % b)),
_ => Err(RuntimeError::RuntimeError {
message: "Invalid '%' operands".to_string(),
token: None,
}),
}
}
fn eval_logical(&mut self, left: Expr, op: LogicalOp, right: Expr) -> Result<Value, RuntimeError> {
let l = self.evaluate(left)?;
match op {
LogicalOp::And => {
Ok(if !self.is_truthy(&l) { l } else { self.evaluate(right)? })
}
LogicalOp::Or => {
Ok(if self.is_truthy(&l) { l } else { self.evaluate(right)? })
}
}
}
fn eval_ternary(&mut self, condition: Expr, then_branch: Expr, else_branch: Expr) -> Result<Value, RuntimeError> {
let cond = self.evaluate(condition)?;
if self.is_truthy(&cond) {
self.evaluate(then_branch)
} else {
self.evaluate(else_branch)
}
}
fn eval_call(&mut self, callee: Expr, arguments: Vec<Expr>) -> Result<Value, RuntimeError> {
let func = self.evaluate(callee)?;
let mut args = Vec::new();
for e in arguments {
args.push(self.evaluate(e)?);
}
self.call_function(func, args)
}
fn eval_lambda(&mut self, params: Vec<String>, body: Vec<Stmt>) -> Result<Value, RuntimeError> {
Ok(Value::Function(Rc::new(Function {
params,
body,
env: Rc::clone(&self.env),
name: None,
})))
}
// ========================================================================
// call_function
// ========================================================================
pub fn call_function(&mut self, func_val: Value, args: Vec<Value>) -> Result<Value, RuntimeError> {
match func_val {
Value::NativeFunction(native_fn) => native_fn(self as &mut dyn super::Runtime, args),
Value::Function(f) => self.call_user_function(f, args),
_ => Err(RuntimeError::RuntimeError {
message: "Attempt to call non-function".to_string(),
token: None,
}),
}
}
fn call_user_function(&mut self, f: Rc<Function>, args: Vec<Value>) -> Result<Value, RuntimeError> {
let env = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&f.env)))));
if let Some(name) = &f.name {
env.borrow_mut().define(name.clone(), Value::Function(Rc::clone(&f)), true);
}
for (i, param) in f.params.iter().enumerate() {
let val = args.get(i).cloned().unwrap_or(Value::Nil);
env.borrow_mut().define(param.clone(), val, true);
}
let previous = Rc::clone(&self.env);
self.env = env;
let mut ret = Value::Nil;
for stmt in &f.body {
match self.execute(stmt.clone())? {
Signal::Return(val) => { ret = val; break; }
Signal::None => {}
Signal::Break | Signal::Continue => {
return Err(RuntimeError::RuntimeError {
message: "break/continue outside of loop".to_string(),
token: None,
});
}
}
}
self.env = previous;
Ok(ret)
}
// ========================================================================
// Helpers
// ========================================================================
pub fn is_truthy(&self, val: &Value) -> bool {
match val {
Value::Nil => false,
Value::Bool(b) => *b,
_ => true,
}
}
pub fn is_equal(&self, a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Nil, Value::Nil) => true,
(Value::Bool(x), Value::Bool(y)) => x == y,
(Value::Number(x), Value::Number(y)) => x == y,
(Value::String(x), Value::String(y)) => x == y,
(Value::Array(x), Value::Array(y)) => {
let x = x.borrow();
let y = y.borrow();
if x.len() != y.len() { return false; }
x.iter().zip(y.iter()).all(|(a, b)| self.is_equal(a, b))
}
(Value::Object(x), Value::Object(y)) => {
let x = x.borrow();
let y = y.borrow();
if x.len() != y.len() { return false; }
x.iter().all(|(k, v)| {
y.get(k).map_or(false, |yv| self.is_equal(v, yv))
})
}
(Value::Function(x), Value::Function(y)) => Rc::ptr_eq(x, y),
(Value::NativeFunction(x), Value::NativeFunction(y)) => Rc::ptr_eq(x, y),
_ => false,
}
}
pub fn apply_assign_op(&self, left: Value, right: Value, op: AssignOp) -> Result<Value, RuntimeError> {
match op {
AssignOp::Equal => Ok(right),
AssignOp::PlusEqual => {
if let Value::String(s) = &left {
Ok(Value::String(format!("{}{}", s, right)))
} else {
let left_num = self.as_number(&left)?;
let right_num = self.as_number(&right)?;
Ok(Value::Number(left_num + right_num))
}
}
AssignOp::MinusEqual => {
let left_num = self.as_number(&left)?;
let right_num = self.as_number(&right)?;
Ok(Value::Number(left_num - right_num))
}
AssignOp::StarEqual => {
let left_num = self.as_number(&left)?;
let right_num = self.as_number(&right)?;
Ok(Value::Number(left_num * right_num))
}
AssignOp::SlashEqual => {
let left_num = self.as_number(&left)?;
let right_num = self.as_number(&right)?;
Ok(Value::Number(left_num / right_num))
}
AssignOp::PercentEqual => {
let left_num = self.as_number(&left)?;
let right_num = self.as_number(&right)?;
Ok(Value::Number(left_num % right_num))
}
}
}
pub fn as_number(&self, val: &Value) -> Result<f64, RuntimeError> {
if let Value::Number(n) = val {
Ok(*n)
} else {
Err(RuntimeError::RuntimeError {
message: "Expected number".to_string(),
token: None,
})
}
}
pub fn as_array_index(&self, val: &Value) -> Result<usize, RuntimeError> {
if let Value::Number(n) = val {
if *n < 0.0 || n.fract() != 0.0 {
return Err(RuntimeError::RuntimeError {
message: format!("Index must be a non-negative integer, got {}", n),
token: None,
});
}
Ok(*n as usize)
} else {
Err(RuntimeError::RuntimeError {
message: "Index must be a number".to_string(),
token: None,
})
}
}
}
+192
View File
@@ -0,0 +1,192 @@
use crate::ast::*;
use crate::error::RuntimeError;
use crate::interpreter::Signal;
use super::{Value, Env, Function};
use std::rc::Rc;
use std::cell::RefCell;
impl super::Interpreter {
// ========================================================================
// execute — dispatcher
// ========================================================================
pub fn execute(&mut self, stmt: Stmt) -> Result<Signal, RuntimeError> {
match stmt {
Stmt::Let { name, initializer, mutable } => self.exec_let(name, initializer, mutable),
Stmt::ExprStmt(expr) => self.exec_expr_stmt(expr),
Stmt::Block(stmts) => self.exec_block(stmts),
Stmt::If { condition, then_branch, else_branch } => {
self.exec_if(condition, *then_branch, else_branch.map(|b| *b))
}
Stmt::While { condition, body } => self.exec_while(condition, *body),
Stmt::For { initializer, condition, step, body } => {
self.exec_for(initializer, condition, step, *body)
}
Stmt::ForIn { var_name, iterable, body } => self.exec_for_in(var_name, iterable, *body),
Stmt::Function { name, params, body } => self.exec_function(name, params, body),
Stmt::Return(expr_opt) => self.exec_return(expr_opt),
Stmt::Break => Ok(Signal::Break),
Stmt::Continue => Ok(Signal::Continue),
}
}
// ========================================================================
// exec_* methods
// ========================================================================
fn exec_let(&mut self, name: String, initializer: Expr, mutable: bool) -> Result<Signal, RuntimeError> {
let val = self.evaluate(initializer)?;
self.env.borrow_mut().define(name, val, mutable);
Ok(Signal::None)
}
fn exec_expr_stmt(&mut self, expr: Expr) -> Result<Signal, RuntimeError> {
self.evaluate(expr)?;
Ok(Signal::None)
}
fn exec_block(&mut self, stmts: Vec<Stmt>) -> Result<Signal, RuntimeError> {
let previous = Rc::clone(&self.env);
self.env = Rc::new(RefCell::new(Env::new(Some(previous))));
let mut signal = Signal::None;
for s in stmts {
signal = self.execute(s)?;
if !matches!(signal, Signal::None) {
break;
}
}
let parent = self.env.borrow().parent.as_ref().unwrap().clone();
self.env = parent;
Ok(signal)
}
fn exec_if(
&mut self,
condition: Expr,
then_branch: Stmt,
else_branch: Option<Stmt>,
) -> Result<Signal, RuntimeError> {
let cond_val = self.evaluate(condition)?;
if self.is_truthy(&cond_val) {
self.execute(then_branch)
} else if let Some(else_branch) = else_branch {
self.execute(else_branch)
} else {
Ok(Signal::None)
}
}
fn exec_while(&mut self, condition: Expr, body: Stmt) -> Result<Signal, RuntimeError> {
loop {
let cond_val = self.evaluate(condition.clone())?;
if !self.is_truthy(&cond_val) {
break;
}
match self.execute(body.clone())? {
Signal::Break => break,
Signal::Continue => continue,
sig @ Signal::Return(_) => return Ok(sig),
Signal::None => {}
}
}
Ok(Signal::None)
}
fn exec_for(
&mut self,
initializer: Option<Box<Stmt>>,
condition: Option<Expr>,
step: Option<Expr>,
body: Stmt,
) -> Result<Signal, RuntimeError> {
if let Some(init) = initializer {
self.execute(*init)?;
}
loop {
if let Some(cond) = &condition {
let cond_val = self.evaluate(cond.clone())?;
if !self.is_truthy(&cond_val) {
break;
}
}
match self.execute(body.clone())? {
Signal::Break => break,
Signal::Continue => {
if let Some(step) = &step {
self.evaluate(step.clone())?;
}
continue;
}
sig @ Signal::Return(_) => return Ok(sig),
Signal::None => {}
}
if let Some(step) = &step {
self.evaluate(step.clone())?;
}
}
Ok(Signal::None)
}
fn exec_for_in(
&mut self,
var_name: String,
iterable: Expr,
body: Stmt,
) -> Result<Signal, RuntimeError> {
let iter_val = self.evaluate(iterable)?;
let items: Vec<Value> = match &iter_val {
Value::Array(arr) => arr.borrow().clone(),
Value::Object(obj) => obj.borrow().keys().map(|k| Value::String(k.clone())).collect(),
Value::String(s) => s.chars().map(|c| Value::String(c.to_string())).collect(),
_ => {
return Err(RuntimeError::RuntimeError {
message: "for-in requires an array, object, or string".into(),
token: None,
});
}
};
let mut signal = Signal::None;
for item in items {
let previous = Rc::clone(&self.env);
self.env = Rc::new(RefCell::new(Env::new(Some(previous))));
self.env.borrow_mut().define(var_name.clone(), item, true);
signal = self.execute(body.clone())?;
let parent = self.env.borrow().parent.as_ref().unwrap().clone();
self.env = parent;
match signal {
Signal::Break => { signal = Signal::None; break; }
Signal::Continue => { signal = Signal::None; continue; }
sig @ Signal::Return(_) => return Ok(sig),
Signal::None => {}
}
}
Ok(signal)
}
fn exec_function(
&mut self,
name: String,
params: Vec<String>,
body: Vec<Stmt>,
) -> Result<Signal, RuntimeError> {
let func = Value::Function(Rc::new(Function {
params,
body,
env: Rc::clone(&self.env),
name: Some(name.clone()),
}));
self.env.borrow_mut().define(name, func, true);
Ok(Signal::None)
}
fn exec_return(&mut self, expr_opt: Option<Expr>) -> Result<Signal, RuntimeError> {
if let Some(expr) = expr_opt {
Ok(Signal::Return(self.evaluate(expr)?))
} else {
Ok(Signal::Return(Value::Nil))
}
}
}
+55
View File
@@ -0,0 +1,55 @@
use crate::ast::*;
use crate::error::RuntimeError;
use super::{Env, Value};
use std::collections::HashMap;
use std::rc::Rc;
use std::cell::RefCell;
pub struct Interpreter {
/// 当前作用域(用户代码所在 envparent 指向 builtins_env
pub env: Rc<RefCell<Env>>,
/// 内置函数根作用域(parent = None,所有内置函数注册在此)
pub builtins_env: Rc<RefCell<Env>>,
/// 模块缓存:规范路径 → exports 对象
pub module_cache: RefCell<HashMap<String, Value>>,
/// 当前执行文件的目录,用于 require() 解析相对路径
pub current_dir: String,
}
impl Interpreter {
pub fn new() -> Self {
// 根层:仅包含内置函数
let builtins_env = Rc::new(RefCell::new(Env::new(None)));
super::builtins::register_all(&builtins_env);
// 用户层:parent 指向 builtins_env,用户定义的变量都在这层
let script_env = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&builtins_env)))));
Self {
env: script_env,
builtins_env,
module_cache: RefCell::new(HashMap::new()),
current_dir: std::env::current_dir()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| ".".to_string()),
}
}
/// 指定当前目录的构造器,用于 `run_file` 时设置脚本所在目录
pub fn with_current_dir(dir: String) -> Self {
let mut interp = Self::new();
interp.current_dir = dir;
interp
}
pub fn interpret(&mut self, statements: Vec<Stmt>) -> Result<(), RuntimeError> {
for stmt in statements {
self.execute(stmt)?;
}
Ok(())
}
}
#[cfg(test)]
#[path = "tests.rs"]
mod tests;
+133 -3
View File
@@ -1,3 +1,133 @@
// Re-export shared types from runtime (backward compatibility) pub mod builtins;
pub use crate::runtime::{Value, NativeFn, Runtime}; pub mod env;
pub use crate::runtime::builtins; pub mod eval;
pub mod exec;
pub mod interpreter;
pub mod module;
pub use env::Env;
pub use interpreter::Interpreter;
use std::collections::HashMap;
use std::rc::Rc;
use std::cell::RefCell;
use std::fmt;
/// Trait abstracting runtime services that native functions may need.
/// Both the tree-walking `Interpreter` and the bytecode `Vm` implement this.
pub trait Runtime {
/// Load and execute a module, returning its exports object.
/// Implementations differ: tree-walker interprets directly,
/// VM compiles to bytecode then executes.
fn require(&mut self, path: &str) -> Result<Value, crate::error::RuntimeError>;
}
pub type NativeFn = Rc<dyn Fn(&mut dyn Runtime, Vec<Value>) -> Result<Value, crate::error::RuntimeError>>;
#[derive(Clone)]
pub enum Value {
Number(f64),
String(String),
Bool(bool),
Nil,
Object(Rc<RefCell<HashMap<String, Value>>>),
Array(Rc<RefCell<Vec<Value>>>),
Function(Rc<Function>),
NativeFunction(NativeFn),
}
pub enum Signal {
None, // 正常执行
Return(Value), // return 语句携带的返回值
Break, // break 信号
Continue, // continue 信号
}
impl Value {
pub fn get(&self, key: &str) -> Option<Value> {
match self {
Value::Object(obj) => obj.borrow().get(key).cloned(),
_ => None,
}
}
pub fn set(&self, key: &str, val: Value) -> Result<(), crate::error::RuntimeError> {
match self {
Value::Object(obj) => {
obj.borrow_mut().insert(key.to_string(), val);
Ok(())
},
_ => Err(crate::error::RuntimeError::RuntimeError {
message: "Only objects have properties".to_string(),
token: None,
}),
}
}
}
impl std::fmt::Debug for Value {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Value::Number(n) => write!(f, "Number({:?})", n),
Value::String(s) => write!(f, "String({:?})", s),
Value::Bool(b) => write!(f, "Bool({:?})", b),
Value::Nil => write!(f, "Nil"),
Value::Object(_) => write!(f, "Object(...)"),
Value::Array(_) => write!(f, "Array(...)"),
Value::Function(_) => write!(f, "Function(...)"),
Value::NativeFunction(_) => write!(f, "NativeFunction(...)"),
}
}
}
impl Value {
/// 容器内值的格式化:字符串加引号以区分类型,其余类型用 Display
fn fmt_element(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Value::String(s) => write!(f, "\"{}\"", s),
other => write!(f, "{}", other),
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Value::Number(n) => write!(f, "{}", n),
Value::String(s) => write!(f, "{}", s),
Value::Bool(b) => write!(f, "{}", b),
Value::Nil => write!(f, "nil"),
Value::Object(obj) => {
let obj = obj.borrow();
write!(f, "{{ ")?;
for (key, value) in obj.iter() {
write!(f, "{}: ", key)?;
value.fmt_element(f)?;
write!(f, ", ")?;
}
write!(f, "}}")
},
Value::Array(arr) => {
let arr = arr.borrow();
write!(f, "[")?;
for (i, val) in arr.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
val.fmt_element(f)?;
}
write!(f, "]")
},
Value::Function(_) => write!(f, "<function>"),
Value::NativeFunction(_) => write!(f, "<native function>"),
}
}
}
#[derive(Debug, Clone)]
pub struct Function {
pub params: Vec<String>,
pub body: Vec<crate::ast::Stmt>,
pub env: Rc<RefCell<Env>>, // 闭包捕获环境
pub name: Option<String>,
}
+143
View File
@@ -0,0 +1,143 @@
//! 模块系统:require() 加载器
//!
//! `require("path/to/module.ast")` 加载并执行指定的 Aster 文件,
//! 返回一个包含模块所有顶层定义的 `Value::Object`。
//! 模块在自己的作用域中执行,只能访问内置函数,无法访问调用者的变量。
//! 第二次 require 同一文件会返回缓存的对象。
use crate::error::RuntimeError;
use crate::lexer::Lexer;
use crate::parser::Parser;
use super::{Interpreter, Env, Value, Signal, Runtime};
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::Path;
use std::rc::Rc;
impl Runtime for Interpreter {
fn require(&mut self, path: &str) -> Result<Value, RuntimeError> {
require_impl(self, path)
}
}
/// require() 的内置函数实现 — 薄包装,委托给 Runtime::require
pub fn require_fn(runtime: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
let path_str = match args.first() {
Some(Value::String(s)) => s.clone(),
Some(other) => return Err(runtime_error(format!(
"require() expects a string argument, got {}", other))),
None => return Err(runtime_error(
"require() expects 1 argument (string path)")),
};
runtime.require(&path_str)
}
/// require() 的内部实现 (供 Interpreter::require 使用)
fn require_impl(interp: &mut Interpreter, path_str: &str) -> Result<Value, RuntimeError> {
// 1. 路径解析
let resolved = resolve_path(&interp.current_dir, path_str)?;
// 2. 缓存查找
if let Some(cached) = interp.module_cache.borrow().get(&resolved) {
return Ok(cached.clone());
}
// 3. 读取文件
let src = std::fs::read_to_string(&resolved)
.map_err(|e| runtime_error(format!(
"Module '{}' not found: {}", path_str, e)))?;
// 4. 词法分析
let (tokens, lex_errors) = Lexer::new(&src).tokenize();
if !lex_errors.is_empty() {
return Err(runtime_error(format!(
"Lex error in module '{}': {}", path_str, lex_errors[0])));
}
// 5. 语法分析
let mut parser = Parser::new(tokens);
let (stmts, parse_errors) = parser.parse();
if !parse_errors.is_empty() {
return Err(runtime_error(format!(
"Parse error in module '{}': {}", path_str, parse_errors[0])));
}
// 6. 创建隔离的模块 env(父级 = builtins_env,看不到调用者的变量)
let module_env = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&interp.builtins_env)))));
// 7. 在缓存中插入占位符(支持循环 require)
let exports_map = Rc::new(RefCell::new(HashMap::new()));
let exports = Value::Object(Rc::clone(&exports_map));
interp.module_cache.borrow_mut().insert(resolved.clone(), exports.clone());
// 8. 保存调用者状态
let previous_env = Rc::clone(&interp.env);
let previous_dir = interp.current_dir.clone();
// 9. 切换到模块上下文
interp.env = module_env.clone();
interp.current_dir = module_dir(&resolved);
// 10. 执行模块
for stmt in &stmts {
match interp.execute(stmt.clone()) {
Ok(Signal::Break) | Ok(Signal::Continue) => {
interp.env = previous_env;
interp.current_dir = previous_dir;
interp.module_cache.borrow_mut().remove(&resolved);
return Err(runtime_error(
"break/continue outside of loop in module"));
}
Err(e) => {
interp.env = previous_env;
interp.current_dir = previous_dir;
interp.module_cache.borrow_mut().remove(&resolved);
return Err(e);
}
Ok(Signal::None) | Ok(Signal::Return(_)) => {}
}
}
// 11. 恢复调用者状态
interp.env = previous_env;
interp.current_dir = previous_dir;
// 12. 收集 exports(模块 env 中的所有直接绑定)
for (name, (val, _mutable)) in module_env.borrow().values.clone() {
exports_map.borrow_mut().insert(name, val);
}
Ok(exports)
}
// ============================================================================
// Helpers
// ============================================================================
fn resolve_path(current_dir: &str, path_str: &str) -> Result<String, RuntimeError> {
let path = Path::new(path_str);
let resolved = if path.is_absolute() {
path.to_path_buf()
} else {
Path::new(current_dir).join(path)
};
std::fs::canonicalize(&resolved)
.map(|p| p.to_string_lossy().to_string())
.map_err(|_| runtime_error(format!(
"Module '{}' not found (resolved to '{}')",
path_str, resolved.display())))
}
fn module_dir(resolved: &str) -> String {
Path::new(resolved)
.parent()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| ".".to_string())
}
fn runtime_error(msg: impl Into<String>) -> RuntimeError {
RuntimeError::RuntimeError {
message: msg.into(),
token: None,
}
}
@@ -1,10 +1,8 @@
use crate::runtime::Value; use crate::interpreter::{Interpreter, Value};
use crate::lexer::Lexer; use crate::lexer::Lexer;
use crate::parser::Parser; use crate::parser::Parser;
use crate::vm::compiler::Compiler;
use crate::vm::vm::Vm;
/// Full pipeline: source → tokens → ast → compile → vm → last expression value. /// Full pipeline: source → tokens → ast → interpret → last expression value.
/// Wraps in `let __result = <expr>;` so we can read the value back. /// Wraps in `let __result = <expr>;` so we can read the value back.
fn eval_expr(input: &str) -> Value { fn eval_expr(input: &str) -> Value {
let wrapped = format!("let __result = {};", input); let wrapped = format!("let __result = {};", input);
@@ -12,27 +10,25 @@ fn eval_expr(input: &str) -> Value {
let mut parser = Parser::new(tokens); let mut parser = Parser::new(tokens);
let (stmts, errors) = parser.parse(); let (stmts, errors) = parser.parse();
assert!(errors.is_empty(), "Parse errors: {:?}", errors); assert!(errors.is_empty(), "Parse errors: {:?}", errors);
let proto = Compiler::compile(&stmts).expect("Compile error"); let mut interpreter = Interpreter::new();
let mut vm = Vm::new(); interpreter.interpret(stmts).expect("Runtime error");
vm.run(std::rc::Rc::new(proto)).expect("Runtime error"); interpreter.env.borrow().get("__result").expect("No __result in env")
vm.get_global("__result").expect("No __result in globals")
} }
/// Full pipeline for multiple statements. Returns the VM for state inspection. /// Full pipeline for multiple statements. Returns the interpreter for env inspection.
fn run(input: &str) -> Vm { fn run(input: &str) -> Interpreter {
let (tokens, _) = Lexer::new(input).tokenize(); let (tokens, _) = Lexer::new(input).tokenize();
let mut parser = Parser::new(tokens); let mut parser = Parser::new(tokens);
let (stmts, errors) = parser.parse(); let (stmts, errors) = parser.parse();
assert!(errors.is_empty(), "Parse errors: {:?}", errors); assert!(errors.is_empty(), "Parse errors: {:?}", errors);
let proto = Compiler::compile(&stmts).expect("Compile error"); let mut interpreter = Interpreter::new();
let mut vm = Vm::new(); interpreter.interpret(stmts).expect("Runtime error");
vm.run(std::rc::Rc::new(proto)).expect("Runtime error"); interpreter
vm
} }
/// Helper: get a variable from the VM's globals. /// Helper: get a variable from the interpreter's environment.
fn get_var(vm: &Vm, name: &str) -> Value { fn get_var(interp: &Interpreter, name: &str) -> Value {
vm.get_global(name).unwrap_or(Value::Nil) interp.env.borrow().get(name).unwrap_or(Value::Nil)
} }
/// Helper: assert a number value. /// Helper: assert a number value.
+33 -13
View File
@@ -1,7 +1,6 @@
pub mod lexer; pub mod lexer;
pub mod ast; pub mod ast;
pub mod parser; pub mod parser;
pub mod runtime;
pub mod interpreter; pub mod interpreter;
pub mod vm; pub mod vm;
pub mod error; pub mod error;
@@ -9,6 +8,7 @@ pub mod analysis;
use lexer::Lexer; use lexer::Lexer;
use parser::Parser; use parser::Parser;
use interpreter::Interpreter;
use vm::compiler::Compiler; use vm::compiler::Compiler;
use vm::vm::Vm; use vm::vm::Vm;
use error::RuntimeError; use error::RuntimeError;
@@ -37,6 +37,33 @@ pub fn run_file(filename: &str, src: String) {
std::process::exit(65); std::process::exit(65);
} }
let script_dir = std::path::Path::new(filename)
.parent()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| ".".to_string());
let mut interpreter = Interpreter::with_current_dir(script_dir);
if let Err(e) = interpreter.interpret(stmts) {
eprintln!("Error: {}", e);
std::process::exit(70);
}
}
/// Run a file using the bytecode VM (for performance comparison).
pub fn run_file_vm(filename: &str, src: String) {
let (tokens, lex_errors) = Lexer::new(&src).tokenize();
if !lex_errors.is_empty() {
print_errors(&lex_errors);
std::process::exit(65);
}
let mut parser = Parser::new(tokens);
let (stmts, parse_errors) = parser.parse();
if !parse_errors.is_empty() {
print_errors(&parse_errors);
std::process::exit(65);
}
let script_dir = std::path::Path::new(filename) let script_dir = std::path::Path::new(filename)
.parent() .parent()
.map(|p| p.to_string_lossy().to_string()) .map(|p| p.to_string_lossy().to_string())
@@ -60,9 +87,9 @@ pub fn run_file(filename: &str, src: String) {
pub fn run_repl() { pub fn run_repl() {
println!("Welcome to Aster REPL!"); println!("Welcome to Aster REPL!");
println!("Type ':exit' to quit."); println!("Type ':exit' to quit.");
println!("Type ':reset' to reset the VM state."); println!("Type ':reset' to reset the interpreter state.");
let mut vm = Vm::new(); let mut interpreter = Interpreter::new();
let mut line = String::new(); let mut line = String::new();
loop { loop {
@@ -86,8 +113,8 @@ pub fn run_repl() {
break; break;
} }
":reset" => { ":reset" => {
vm = Vm::new(); interpreter = Interpreter::new();
println!("VM reset."); println!("Interpreter reset.");
continue; continue;
} }
_ => {} _ => {}
@@ -106,14 +133,7 @@ pub fn run_repl() {
continue; continue;
} }
let proto = match Compiler::compile(&stmts) { if let Err(e) = interpreter.interpret(stmts) {
Ok(p) => p,
Err(e) => {
eprintln!("Compile Error: {}", e);
continue;
}
};
if let Err(e) = vm.run(std::rc::Rc::new(proto)) {
eprintln!("Error: {}", e); eprintln!("Error: {}", e);
} }
} }
-45
View File
@@ -1,45 +0,0 @@
//! Builtin function modules.
pub mod core;
pub mod io;
pub mod os;
pub mod string;
use crate::runtime::Value;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
/// Register all standard library functions into a HashMap.
pub fn register_all(map: &mut HashMap<String, Value>) {
// io
let mut io_map = HashMap::new();
io_map.insert("print".into(), Value::NativeFunction(Rc::new(io::print)));
io_map.insert("input".into(), Value::NativeFunction(Rc::new(io::input)));
map.insert("io".into(), Value::Object(Rc::new(RefCell::new(io_map))));
map.insert("print".into(), Value::NativeFunction(Rc::new(io::print)));
map.insert("input".into(), Value::NativeFunction(Rc::new(io::input)));
// os
let mut os_map = HashMap::new();
os_map.insert("clock".into(), Value::NativeFunction(Rc::new(os::clock)));
map.insert("os".into(), Value::Object(Rc::new(RefCell::new(os_map))));
map.insert("clock".into(), Value::NativeFunction(Rc::new(os::clock)));
// core
map.insert("len".into(), Value::NativeFunction(Rc::new(core::len)));
map.insert("typeof".into(), Value::NativeFunction(Rc::new(core::typeof_fn)));
map.insert("push".into(), Value::NativeFunction(Rc::new(core::push)));
map.insert("pop".into(), Value::NativeFunction(Rc::new(core::pop)));
// string
map.insert("split".into(), Value::NativeFunction(Rc::new(string::split)));
map.insert("trim".into(), Value::NativeFunction(Rc::new(string::trim)));
map.insert("substring".into(), Value::NativeFunction(Rc::new(string::substring)));
map.insert("replace".into(), Value::NativeFunction(Rc::new(string::replace)));
map.insert("contains".into(), Value::NativeFunction(Rc::new(string::contains)));
map.insert("upper".into(), Value::NativeFunction(Rc::new(string::upper)));
map.insert("lower".into(), Value::NativeFunction(Rc::new(string::lower)));
map.insert("starts_with".into(), Value::NativeFunction(Rc::new(string::starts_with)));
map.insert("ends_with".into(), Value::NativeFunction(Rc::new(string::ends_with)));
}
-191
View File
@@ -1,191 +0,0 @@
pub mod builtins;
use std::collections::HashMap;
use std::rc::Rc;
use std::cell::RefCell;
use std::fmt;
/// Trait abstracting runtime services that native functions may need.
pub trait Runtime {
fn require(&mut self, path: &str) -> Result<Value, crate::error::RuntimeError>;
}
pub type NativeFn = Rc<dyn Fn(&mut dyn Runtime, Vec<Value>) -> Result<Value, crate::error::RuntimeError>>;
// ============================================================================
// Compiled function types
// ============================================================================
#[derive(Debug, Clone)]
pub struct FunctionProto {
pub name: Option<String>,
pub arity: u8,
pub code: Vec<u8>,
pub constants: Vec<Value>,
pub protos: Vec<Rc<FunctionProto>>,
pub upvalue_count: u8,
pub upvalues: Vec<(bool, u8)>, // (is_local, index)
pub lines: Vec<(usize, usize)>, // (bytecode_offset, source_line)
}
impl FunctionProto {
pub fn new(name: Option<String>) -> Self {
Self {
name,
arity: 0,
code: Vec::new(),
constants: Vec::new(),
protos: Vec::new(),
upvalue_count: 0,
upvalues: Vec::new(),
lines: Vec::new(),
}
}
pub fn add_constant(&mut self, val: Value) -> u16 {
for (i, c) in self.constants.iter().enumerate() {
if values_eq(c, &val) {
return i as u16;
}
}
let idx = self.constants.len();
self.constants.push(val);
idx as u16
}
}
fn values_eq(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Number(x), Value::Number(y)) => (x - y).abs() < f64::EPSILON,
(Value::String(x), Value::String(y)) => x == y,
(Value::Bool(x), Value::Bool(y)) => x == y,
(Value::Nil, Value::Nil) => true,
_ => false,
}
}
/// An upvalue — a reference to a local variable in an enclosing function.
#[derive(Debug, Clone)]
pub struct UpvalueObj {
pub location: usize,
pub closed: Option<Value>,
}
/// A runtime closure: compiled function proto + captured upvalues.
#[derive(Debug, Clone)]
pub struct Closure {
pub proto: Rc<FunctionProto>,
pub upvalues: Vec<Rc<RefCell<UpvalueObj>>>,
}
// ============================================================================
// Value
// ============================================================================
#[derive(Clone)]
pub enum Value {
Number(f64),
String(String),
Bool(bool),
Nil,
Object(Rc<RefCell<HashMap<String, Value>>>),
Array(Rc<RefCell<Vec<Value>>>),
Function(Rc<Closure>),
NativeFunction(NativeFn),
}
impl Value {
pub fn get(&self, key: &str) -> Option<Value> {
match self {
Value::Object(obj) => obj.borrow().get(key).cloned(),
_ => None,
}
}
pub fn set(&self, key: &str, val: Value) -> Result<(), crate::error::RuntimeError> {
match self {
Value::Object(obj) => {
obj.borrow_mut().insert(key.to_string(), val);
Ok(())
},
_ => Err(crate::error::RuntimeError::RuntimeError {
message: "Only objects have properties".to_string(),
token: None,
}),
}
}
}
impl std::fmt::Debug for Value {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Value::Number(n) => write!(f, "Number({:?})", n),
Value::String(s) => write!(f, "String({:?})", s),
Value::Bool(b) => write!(f, "Bool({:?})", b),
Value::Nil => write!(f, "Nil"),
Value::Object(_) => write!(f, "Object(...)"),
Value::Array(_) => write!(f, "Array(...)"),
Value::Function(_) => write!(f, "Function(...)"),
Value::NativeFunction(_) => write!(f, "NativeFunction(...)"),
}
}
}
impl Value {
fn fmt_element(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Value::String(s) => write!(f, "\"{}\"", s),
other => write!(f, "{}", other),
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Value::Number(n) => write!(f, "{}", n),
Value::String(s) => write!(f, "{}", s),
Value::Bool(b) => write!(f, "{}", b),
Value::Nil => write!(f, "nil"),
Value::Object(obj) => {
let obj = obj.borrow();
write!(f, "{{ ")?;
for (key, value) in obj.iter() {
write!(f, "{}: ", key)?;
value.fmt_element(f)?;
write!(f, ", ")?;
}
write!(f, "}}")
},
Value::Array(arr) => {
let arr = arr.borrow();
write!(f, "[")?;
for (i, val) in arr.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
val.fmt_element(f)?;
}
write!(f, "]")
},
Value::Function(_) => write!(f, "<function>"),
Value::NativeFunction(_) => write!(f, "<native function>"),
}
}
}
/// `require()` builtin — thin wrapper that delegates to `Runtime::require`.
pub fn require_fn(runtime: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, crate::error::RuntimeError> {
let path_str = match args.first() {
Some(Value::String(s)) => s.clone(),
Some(other) => return Err(crate::error::RuntimeError::RuntimeError {
message: format!("require() expects a string argument, got {}", other),
token: None,
}),
None => return Err(crate::error::RuntimeError::RuntimeError {
message: "require() expects 1 argument (string path)".into(),
token: None,
}),
};
runtime.require(&path_str)
}
+64 -4
View File
@@ -7,12 +7,67 @@
use crate::ast::*; use crate::ast::*;
use crate::ast::expr::{Literal, UnaryOp, BinaryOp, LogicalOp, AssignOp}; use crate::ast::expr::{Literal, UnaryOp, BinaryOp, LogicalOp, AssignOp};
use crate::error::RuntimeError; use crate::error::RuntimeError;
use crate::runtime::{Value, FunctionProto}; use crate::interpreter::Value;
use super::opcode::*; use super::opcode::*;
use std::rc::Rc; use std::rc::Rc;
use std::cell::RefCell; use std::cell::RefCell;
// ============================================================================
// FunctionProto — compiled function blueprint
// ============================================================================
#[derive(Debug, Clone)]
pub struct FunctionProto {
pub name: Option<String>,
pub arity: u8,
pub code: Vec<u8>,
pub constants: Vec<Value>,
/// Nested function protos (for closures and function declarations)
pub protos: Vec<Rc<FunctionProto>>,
pub upvalue_count: u8,
/// Upvalue descriptors for this function (for Closure opcode emission)
pub upvalues: Vec<(bool, u8)>, // (is_local, index)
pub lines: Vec<(usize, usize)>, // (bytecode_offset, source_line)
}
impl FunctionProto {
pub fn new(name: Option<String>) -> Self {
Self {
name,
arity: 0,
code: Vec::new(),
constants: Vec::new(),
protos: Vec::new(),
upvalue_count: 0,
upvalues: Vec::new(),
lines: Vec::new(),
}
}
fn add_constant(&mut self, val: Value) -> u16 {
// Check for existing identical constant
for (i, c) in self.constants.iter().enumerate() {
if values_eq(c, &val) {
return i as u16;
}
}
let idx = self.constants.len();
self.constants.push(val);
idx as u16
}
}
fn values_eq(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Number(x), Value::Number(y)) => (x - y).abs() < f64::EPSILON,
(Value::String(x), Value::String(y)) => x == y,
(Value::Bool(x), Value::Bool(y)) => x == y,
(Value::Nil, Value::Nil) => true,
_ => false,
}
}
// ============================================================================ // ============================================================================
// Compiler // Compiler
// ============================================================================ // ============================================================================
@@ -481,7 +536,7 @@ impl Compiler {
self.function.code.push(compound_op as u8); self.function.code.push(compound_op as u8);
} else { } else {
let name_idx = self.add_string_constant(name); let name_idx = self.add_string_constant(name);
emit_u16(&mut self.function.code, OpCode::CompoundAssignGlobal, name_idx); emit_u16(&mut self.function.code, OpCode::CompoundAssignProp, name_idx);
self.function.code.push(compound_op as u8); self.function.code.push(compound_op as u8);
} }
} }
@@ -503,11 +558,16 @@ impl Compiler {
emit_u16(&mut self.function.code, OpCode::SetProperty, name_idx); emit_u16(&mut self.function.code, OpCode::SetProperty, name_idx);
} else { } else {
// Compound property set: object.name op= value // Compound property set: object.name op= value
// CompoundAssignProp handler does get_property internally // Strategy: load object, dup, get property as current value,
// load rhs, apply op, set property
self.compile_expr(object)?; self.compile_expr(object)?;
self.emit_op(OpCode::Dup);
let name_idx = self.add_string_constant(name);
emit_u16(&mut self.function.code, OpCode::GetProperty, name_idx); // current value
self.compile_expr(value)?; // rhs self.compile_expr(value)?; // rhs
let compound_op = assign_op_to_compound(op); let compound_op = assign_op_to_compound(op);
let name_idx = self.add_string_constant(name); self.function.code.push(compound_op as u8);
// Now stack: object, current_val, rhs → set property
emit_u16(&mut self.function.code, OpCode::CompoundAssignProp, name_idx); emit_u16(&mut self.function.code, OpCode::CompoundAssignProp, name_idx);
self.function.code.push(compound_op as u8); self.function.code.push(compound_op as u8);
} }
+2 -5
View File
@@ -79,13 +79,12 @@ pub enum OpCode {
CompoundAssignProp = 40, // operand: u16 name idx + u8 compound-op tag CompoundAssignProp = 40, // operand: u16 name idx + u8 compound-op tag
CompoundAssignIndex = 41, // operand: u8 compound-op tag CompoundAssignIndex = 41, // operand: u8 compound-op tag
CompoundAssignUpvalue = 44, // operand: u8 upvalue idx + u8 compound-op tag CompoundAssignUpvalue = 44, // operand: u8 upvalue idx + u8 compound-op tag
CompoundAssignGlobal = 45, // operand: u16 name idx + u8 compound-op tag
} }
impl OpCode { impl OpCode {
pub fn from_u8(byte: u8) -> Option<Self> { pub fn from_u8(byte: u8) -> Option<Self> {
match byte { match byte {
0..=41 | 44..=45 => Some(unsafe { std::mem::transmute::<u8, OpCode>(byte) }), 0..=41 | 44 => Some(unsafe { std::mem::transmute::<u8, OpCode>(byte) }),
42 => Some(OpCode::LoadUpvalue), 42 => Some(OpCode::LoadUpvalue),
43 => Some(OpCode::StoreUpvalue), 43 => Some(OpCode::StoreUpvalue),
_ => None, _ => None,
@@ -93,7 +92,7 @@ impl OpCode {
} }
/// Total number of distinct opcodes /// Total number of distinct opcodes
pub const COUNT: usize = 46; pub const COUNT: usize = 45;
} }
/// Compound assignment operator tags (used as operand byte after /// Compound assignment operator tags (used as operand byte after
@@ -172,5 +171,3 @@ pub const SIZE_OP: usize = 1;
pub const SIZE_U8: usize = 2; pub const SIZE_U8: usize = 2;
/// Size in bytes of an opcode with a u16/i16 operand. /// Size in bytes of an opcode with a u16/i16 operand.
pub const SIZE_U16: usize = 3; pub const SIZE_U16: usize = 3;
/// Size in bytes of an opcode with a u16 + u8 operand.
pub const SIZE_U16_PLUS1: usize = 4;
+94 -146
View File
@@ -4,11 +4,11 @@
//! call frames. Implements the `Runtime` trait for native function support. //! call frames. Implements the `Runtime` trait for native function support.
use crate::error::RuntimeError; use crate::error::RuntimeError;
use crate::runtime::{Value, Runtime, FunctionProto, Closure, UpvalueObj}; use crate::interpreter::{Value, Runtime};
use crate::lexer::Lexer; use crate::lexer::Lexer;
use crate::parser::Parser; use crate::parser::Parser;
use super::opcode::*; use super::opcode::*;
use super::compiler::Compiler; use super::compiler::{Compiler, FunctionProto};
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
@@ -18,6 +18,22 @@ use std::rc::Rc;
// VM data structures // VM data structures
// ============================================================================ // ============================================================================
/// An upvalue — a reference to a local variable in an enclosing function.
#[derive(Debug, Clone)]
struct UpvalueObj {
/// Stack index where the value lives, or usize::MAX if closed
location: usize,
/// The value, if it has been moved off the stack (closed)
closed: Option<Value>,
}
/// A runtime closure: compiled function proto + captured upvalues.
#[derive(Debug, Clone)]
struct Closure {
proto: Rc<FunctionProto>,
upvalues: Vec<Rc<RefCell<UpvalueObj>>>,
}
/// A call frame on the VM stack. /// A call frame on the VM stack.
struct CallFrame { struct CallFrame {
closure: Rc<Closure>, closure: Rc<Closure>,
@@ -46,6 +62,9 @@ pub struct Vm {
/// Open upvalues (tracked so closures share the same upvalue object) /// Open upvalues (tracked so closures share the same upvalue object)
open_upvalues: Vec<Rc<RefCell<UpvalueObj>>>, open_upvalues: Vec<Rc<RefCell<UpvalueObj>>>,
/// VM-compiled closures: maps Function Rc pointer → Closure data
closures: RefCell<HashMap<*const crate::interpreter::Function, Rc<Closure>>>,
} }
impl Vm { impl Vm {
@@ -66,6 +85,7 @@ impl Vm {
.map(|p| p.to_string_lossy().to_string()) .map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| ".".to_string()), .unwrap_or_else(|_| ".".to_string()),
open_upvalues: Vec::new(), open_upvalues: Vec::new(),
closures: RefCell::new(HashMap::new()),
} }
} }
@@ -81,12 +101,6 @@ impl Vm {
self.run(Rc::new(proto)) self.run(Rc::new(proto))
} }
/// Look up a global variable by name (for test inspection).
pub fn get_global(&self, name: &str) -> Option<Value> {
self.globals.borrow().get(name).map(|(v, _)| v.clone())
.or_else(|| self.builtins.borrow().get(name).cloned())
}
/// Execute a compiled FunctionProto. /// Execute a compiled FunctionProto.
pub fn run(&mut self, proto: Rc<FunctionProto>) -> Result<(), RuntimeError> { pub fn run(&mut self, proto: Rc<FunctionProto>) -> Result<(), RuntimeError> {
let closure = Rc::new(Closure { let closure = Rc::new(Closure {
@@ -482,7 +496,15 @@ impl Vm {
} }
let closure = Rc::new(Closure { proto, upvalues }); let closure = Rc::new(Closure { proto, upvalues });
self.stack.push(Value::Function(closure)); let func = Rc::new(crate::interpreter::Function {
params: Vec::new(),
body: Vec::new(),
env: Rc::new(RefCell::new(crate::interpreter::Env::new(None))),
name: None,
});
let key = Rc::as_ptr(&func) as *const crate::interpreter::Function;
self.closures.borrow_mut().insert(key, closure);
self.stack.push(Value::Function(func));
self.advance_ip_to(off); self.advance_ip_to(off);
} }
@@ -505,15 +527,7 @@ impl Vm {
// --- For-in --- // --- For-in ---
OpCode::ForInInit => { OpCode::ForInInit => {
let iterable = self.stack.pop().unwrap(); let iterable = self.stack.pop().unwrap();
let items = match &iterable { let items = self.for_in_items(&iterable);
Value::Array(arr) => arr.borrow().clone(),
Value::Object(obj) => obj.borrow().keys().map(|k| Value::String(k.clone())).collect(),
Value::String(s) => s.chars().map(|c| Value::String(c.to_string())).collect(),
_ => return Err(RuntimeError::RuntimeError {
message: format!("for-in requires an array, object, or string, got {}", iterable),
token: None,
}),
};
self.stack.push(Value::Array(Rc::new(RefCell::new(items)))); self.stack.push(Value::Array(Rc::new(RefCell::new(items))));
self.advance_ip(SIZE_OP); self.advance_ip(SIZE_OP);
} }
@@ -618,33 +632,6 @@ impl Vm {
self.stack.push(result); self.stack.push(result);
self.advance_ip(SIZE_U8 + 1); self.advance_ip(SIZE_U8 + 1);
} }
OpCode::CompoundAssignGlobal => {
let name_idx = read_u16(code, ip) as usize;
let op_tag = code[ip + 3];
let compound_op = CompoundOp::from_u8(op_tag).unwrap();
let rhs = self.stack.pop().unwrap();
let name = proto_string(&proto, name_idx)?;
let lhs = self.globals.borrow().get(&name)
.map(|(v, _)| v.clone())
.or_else(|| self.builtins.borrow().get(&name).cloned())
.ok_or_else(|| RuntimeError::RuntimeError {
message: format!("Undefined variable '{}'", name),
token: None,
})?;
let result = self.apply_compound_op(lhs, rhs, compound_op)?;
// Check const flag (globals tuple stores (value, mutable))
if let Some((_, mutable)) = self.globals.borrow().get(&name) {
if !*mutable {
return Err(RuntimeError::RuntimeError {
message: format!("Cannot reassign constant '{}'", name),
token: None,
});
}
}
self.globals.borrow_mut().insert(name, (result.clone(), true));
self.stack.push(result);
self.advance_ip(SIZE_U16_PLUS1);
}
} }
} }
} }
@@ -691,13 +678,22 @@ impl Vm {
self.stack.push(result); self.stack.push(result);
self.advance_ip(SIZE_U8); self.advance_ip(SIZE_U8);
} }
Value::Function(closure) => { Value::Function(_) => {
let closure = Rc::clone(closure); // Look up the VM-compiled closure
let func_ptr = match &self.stack[callee_idx] {
Value::Function(f) => Rc::as_ptr(f) as *const crate::interpreter::Function,
_ => unreachable!(),
};
let closure = self.closures.borrow().get(&func_ptr).cloned().ok_or_else(|| RuntimeError::RuntimeError {
message: "VM: call to non-VM function (tree-walker function not supported in VM)".into(),
token: None,
})?;
// Advance caller's IP past the Call instruction before pushing new frame // Advance caller's IP past the Call instruction before pushing new frame
self.frame_mut().ip += SIZE_U8; self.frame_mut().ip += SIZE_U8;
// Callee is at callee_idx, args start at callee_idx+1 // Callee is at callee_idx, args start at callee_idx+1
let base = callee_idx + 1; let base = callee_idx + 1; // first param is here
self.frames.push(CallFrame { self.frames.push(CallFrame {
closure, closure,
ip: 0, ip: 0,
@@ -752,78 +748,6 @@ impl Vm {
}, },
Value::String(s) => match name { Value::String(s) => match name {
"length" => Ok(Value::Number(s.chars().count() as f64)), "length" => Ok(Value::Number(s.chars().count() as f64)),
"upper" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
crate::runtime::builtins::string::upper(runtime, all_args)
})))
}
"lower" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
crate::runtime::builtins::string::lower(runtime, all_args)
})))
}
"trim" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
crate::runtime::builtins::string::trim(runtime, all_args)
})))
}
"substring" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
crate::runtime::builtins::string::substring(runtime, all_args)
})))
}
"replace" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
crate::runtime::builtins::string::replace(runtime, all_args)
})))
}
"contains" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
crate::runtime::builtins::string::contains(runtime, all_args)
})))
}
"starts_with" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
crate::runtime::builtins::string::starts_with(runtime, all_args)
})))
}
"ends_with" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
crate::runtime::builtins::string::ends_with(runtime, all_args)
})))
}
"split" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
crate::runtime::builtins::string::split(runtime, all_args)
})))
}
_ => Err(RuntimeError::RuntimeError { _ => Err(RuntimeError::RuntimeError {
message: format!("String has no property '{}'", name), message: format!("String has no property '{}'", name),
token: None, token: None,
@@ -998,23 +922,7 @@ impl Vm {
(Value::Bool(x), Value::Bool(y)) => x == y, (Value::Bool(x), Value::Bool(y)) => x == y,
(Value::Number(x), Value::Number(y)) => x == y, (Value::Number(x), Value::Number(y)) => x == y,
(Value::String(x), Value::String(y)) => x == y, (Value::String(x), Value::String(y)) => x == y,
(Value::Array(x), Value::Array(y)) => { _ => false, // Simplified — full structural equality omitted for now
let x = x.borrow();
let y = y.borrow();
if x.len() != y.len() { return false; }
x.iter().zip(y.iter()).all(|(a, b)| self.is_equal(a, b))
}
(Value::Object(x), Value::Object(y)) => {
let x = x.borrow();
let y = y.borrow();
if x.len() != y.len() { return false; }
x.iter().all(|(k, v)| {
y.get(k).map_or(false, |yv| self.is_equal(v, yv))
})
}
(Value::Function(x), Value::Function(y)) => Rc::ptr_eq(x, y),
(Value::NativeFunction(x), Value::NativeFunction(y)) => Rc::ptr_eq(x, y),
_ => false,
} }
} }
@@ -1080,6 +988,14 @@ impl Vm {
// For-in helper // For-in helper
// ======================================================================== // ========================================================================
fn for_in_items(&self, collection: &Value) -> Vec<Value> {
match collection {
Value::Array(arr) => arr.borrow().clone(),
Value::Object(obj) => obj.borrow().keys().map(|k| Value::String(k.clone())).collect(),
Value::String(s) => s.chars().map(|c| Value::String(c.to_string())).collect(),
_ => Vec::new(),
}
}
} }
impl Runtime for Vm { impl Runtime for Vm {
@@ -1164,7 +1080,12 @@ impl Runtime for Vm {
} }
} }
// 11. Cache in parent // 11. Transfer closures from module VM to parent (so exported fns are callable)
for (key, closure) in module_vm.closures.borrow().iter() {
self.closures.borrow_mut().insert(*key, Rc::clone(closure));
}
// 12. Cache in parent
self.module_cache.borrow_mut().insert(resolved, exports_obj.clone()); self.module_cache.borrow_mut().insert(resolved, exports_obj.clone());
Ok(exports_obj) Ok(exports_obj)
@@ -1186,16 +1107,43 @@ fn proto_string(proto: &FunctionProto, idx: usize) -> Result<String, RuntimeErro
} }
// ============================================================================ // ============================================================================
// Builtin registration (delegates to runtime::builtins) // Builtin registration (reuses tree-walker's builtins)
// ============================================================================ // ============================================================================
fn register_builtins(map: &Rc<RefCell<HashMap<String, Value>>>) { fn register_builtins(map: &Rc<RefCell<HashMap<String, Value>>>) {
let mut m = map.borrow_mut(); let mut m = map.borrow_mut();
crate::runtime::builtins::register_all(&mut *m);
// require is VM-specific
m.insert("require".into(), Value::NativeFunction(Rc::new(crate::runtime::require_fn)));
}
#[cfg(test)] // io
#[path = "tests.rs"] let mut io = HashMap::new();
mod tests; io.insert("print".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::io::print)));
io.insert("input".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::io::input)));
m.insert("io".into(), Value::Object(Rc::new(RefCell::new(io))));
m.insert("print".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::io::print)));
m.insert("input".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::io::input)));
// os
let mut os = HashMap::new();
os.insert("clock".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::os::clock)));
m.insert("os".into(), Value::Object(Rc::new(RefCell::new(os))));
m.insert("clock".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::os::clock)));
// core
m.insert("len".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::core::len)));
m.insert("typeof".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::core::typeof_fn)));
m.insert("push".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::core::push)));
m.insert("pop".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::core::pop)));
// require (VM version)
m.insert("require".into(), Value::NativeFunction(Rc::new(crate::interpreter::module::require_fn)));
// string
m.insert("split".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::split)));
m.insert("trim".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::trim)));
m.insert("substring".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::substring)));
m.insert("replace".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::replace)));
m.insert("contains".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::contains)));
m.insert("upper".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::upper)));
m.insert("lower".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::lower)));
m.insert("starts_with".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::starts_with)));
m.insert("ends_with".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::ends_with)));
}
+12 -4
View File
@@ -3,13 +3,21 @@ use std::fs;
fn main() { fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
let use_vm = args.iter().any(|a| a == "--vm");
let file_args: Vec<&String> = args.iter().filter(|a| *a != "--vm").collect();
match args.len() { match file_args.len() {
1 => aster_core::run_repl(), 1 => aster_core::run_repl(),
2 => { 2 => {
let filename = &args[1]; let filename = file_args[1];
match fs::read_to_string(filename) { match fs::read_to_string(filename) {
Ok(src) => aster_core::run_file(filename, src), Ok(src) => {
if use_vm {
aster_core::run_file_vm(filename, src)
} else {
aster_core::run_file(filename, src)
}
}
Err(e) => { Err(e) => {
eprintln!("Error reading file '{}': {}", filename, e); eprintln!("Error reading file '{}': {}", filename, e);
std::process::exit(1); std::process::exit(1);
@@ -17,7 +25,7 @@ fn main() {
} }
} }
_ => { _ => {
eprintln!("Usage: aster [script]"); eprintln!("Usage: aster [--vm] [script]");
std::process::exit(1); std::process::exit(1);
} }
} }