Compare commits
11 Commits
d854b22006
...
try-catch
| Author | SHA1 | Date | |
|---|---|---|---|
| af63957b11 | |||
| a70e5fbc30 | |||
| deab894864 | |||
| 9f75ec68ee | |||
| fa6ea512b3 | |||
| abe0844816 | |||
| 7b9bded5ee | |||
| 9a193fd7ca | |||
| ddee395f52 | |||
| 9c9a17b149 | |||
| 299003a718 |
@@ -1,6 +1,6 @@
|
||||
# Aster
|
||||
|
||||
Aster 是一门使用 Rust 编写的动态类型脚本语言解释器,基于树遍历(tree-walker)实现。无外部依赖,纯标准库。
|
||||
Aster 是一门使用 Rust 编写的动态类型脚本语言,采用**基于栈的字节码 VM** 执行。无外部依赖,纯标准库。
|
||||
|
||||
## 快速开始
|
||||
|
||||
@@ -84,6 +84,21 @@ while (true) {
|
||||
if (condition) { break; }
|
||||
if (skip) { continue; }
|
||||
}
|
||||
|
||||
// for-in 遍历(数组 / 字符串)
|
||||
for (let item in [1, 2, 3]) {
|
||||
print(item);
|
||||
}
|
||||
```
|
||||
|
||||
### 模块系统
|
||||
|
||||
```js
|
||||
// 加载并执行外部脚本,返回模块导出的对象
|
||||
let math = require("math.ast");
|
||||
print(math.add(3, 4));
|
||||
|
||||
// 第二次 require 同一文件会返回缓存的对象(支持循环引用)
|
||||
```
|
||||
|
||||
### 函数与闭包
|
||||
@@ -144,23 +159,25 @@ let t = os.clock(); // 模块化调用
|
||||
## 架构
|
||||
|
||||
```
|
||||
源码文本 → Lexer → Tokens → Parser → AST → Interpreter → 输出
|
||||
源码文本 → Lexer → Tokens → Parser → AST → Compiler → Bytecode → VM → 输出
|
||||
```
|
||||
|
||||
| 模块 | 职责 |
|
||||
|------|------|
|
||||
| `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()` 跳过至下一条语句边界继续解析 |
|
||||
| `ast/` | AST 节点定义。`Expr`(表达式)涵盖字面量、变量、赋值、属性/索引访问、一元/二元/逻辑运算、函数调用、lambda 和对象/数组字面量。`Stmt`(语句)涵盖 let、表达式语句、块、if/while/for、函数、return/break/continue |
|
||||
| `interpreter/` | 树遍历求值器。`Env` 是基于 `Rc<RefCell<>>` 的链式作用域。`Signal` 枚举通过调用栈传播 `Return`/`Break`/`Continue`。`builtins/` 按 `io` 和 `os` 模块组织原生函数,同时注册为全局函数以方便使用 |
|
||||
| `runtime/` | 运行时类型:`Value` 枚举、`FunctionProto`(编译后的函数蓝图)、`Closure`(函数原型 + 捕获的 upvalue)、`Runtime` trait、`NativeFn` 类型。`builtins/` 按功能分组注册原生函数 |
|
||||
| `vm/` | 字节码 VM。`Compiler` 将 AST 编译为基于栈的字节码(44 条指令),`Vm` 执行字节码。支持闭包 upvalue 捕获、3 层嵌套闭包、`require()` 模块加载 |
|
||||
| `error/` | 三种错误:`LexError`(行列号)、`ParseError`(Token)、`RuntimeError`(可选 Token) |
|
||||
|
||||
### 关键设计决策
|
||||
|
||||
- **零外部依赖** — 全部基于 Rust 标准库构建
|
||||
- **基于栈的字节码 VM** — AST 先编译为字节码再执行,单一执行路径,无语义漂移
|
||||
- **错误容忍解析** — 词法分析器和解析器均将错误收集到 `Vec` 中,单次运行可报告多个诊断信息,而非在第一个错误处就中止
|
||||
- **`Rc<RefCell<>>` 共享所有权** — 用于 `Env` 链、`Value::Object`、`Value::Array` 和 `Value::Function`,提供动态可变语义
|
||||
- **词法作用域闭包** — `Function` 在定义时捕获 `Env`,lambda 表达式同理
|
||||
- **`Rc<RefCell<>>` 共享所有权** — 用于 `Value::Object`、`Value::Array` 和 `UpvalueObj`,提供动态可变语义。闭包通过 upvalue 机制捕获外层局部变量
|
||||
- **词法作用域闭包** — 编译时解析 upvalue 捕获,通过增量链路支持任意深度嵌套;`resolve_upvalue` 中显式处理 3 层穿透(本地→父级→祖级),更深层级通过逐层编译自然建立
|
||||
|
||||
## 许可证
|
||||
|
||||
|
||||
@@ -102,6 +102,19 @@ fn collect_stmt(stmt: &Stmt, symbols: &mut Vec<Symbol>) {
|
||||
collect_expr(expr, symbols);
|
||||
}
|
||||
Stmt::Return(None) | Stmt::Break | Stmt::Continue => {}
|
||||
Stmt::Try { body, catch_var, catch_body, finally_body } => {
|
||||
collect_stmts(body, symbols);
|
||||
if let Some(var) = catch_var {
|
||||
symbols.push(Symbol { name: var.clone(), kind: SymbolKind::Variable });
|
||||
}
|
||||
if let Some(cb) = catch_body {
|
||||
collect_stmts(cb, symbols);
|
||||
}
|
||||
if let Some(fb) = finally_body {
|
||||
collect_stmts(fb, symbols);
|
||||
}
|
||||
}
|
||||
Stmt::Throw(expr) => collect_expr(expr, symbols),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,4 +58,15 @@ pub enum Stmt {
|
||||
|
||||
/// continue
|
||||
Continue,
|
||||
|
||||
/// try { body } catch (e) { catch_body } finally { finally_body }
|
||||
Try {
|
||||
body: Vec<Stmt>,
|
||||
catch_var: Option<String>,
|
||||
catch_body: Option<Vec<Stmt>>,
|
||||
finally_body: Option<Vec<Stmt>>,
|
||||
},
|
||||
|
||||
/// throw expr;
|
||||
Throw(Expr),
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
//! 内置函数模块:按功能分组注册,便于扩展和维护。
|
||||
|
||||
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,50 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,624 +0,0 @@
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
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 {
|
||||
/// 当前作用域(用户代码所在 env,parent 指向 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;
|
||||
@@ -1,133 +1,3 @@
|
||||
pub mod builtins;
|
||||
pub mod env;
|
||||
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>,
|
||||
}
|
||||
// Re-export shared types from runtime (backward compatibility)
|
||||
pub use crate::runtime::{Value, NativeFn, Runtime};
|
||||
pub use crate::runtime::builtins;
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
//! 模块系统: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,
|
||||
}
|
||||
}
|
||||
@@ -360,6 +360,10 @@ impl Lexer {
|
||||
"true" => TokenKind::True,
|
||||
"false" => TokenKind::False,
|
||||
"nil" => TokenKind::Nil,
|
||||
"try" => TokenKind::Try,
|
||||
"catch" => TokenKind::Catch,
|
||||
"finally" => TokenKind::Finally,
|
||||
"throw" => TokenKind::Throw,
|
||||
_ => TokenKind::Identifier(s),
|
||||
};
|
||||
|
||||
|
||||
@@ -39,6 +39,10 @@ pub enum TokenKind {
|
||||
True,
|
||||
False,
|
||||
Nil,
|
||||
Try,
|
||||
Catch,
|
||||
Finally,
|
||||
Throw,
|
||||
|
||||
EOF,
|
||||
}
|
||||
|
||||
+13
-33
@@ -1,6 +1,7 @@
|
||||
pub mod lexer;
|
||||
pub mod ast;
|
||||
pub mod parser;
|
||||
pub mod runtime;
|
||||
pub mod interpreter;
|
||||
pub mod vm;
|
||||
pub mod error;
|
||||
@@ -8,7 +9,6 @@ pub mod analysis;
|
||||
|
||||
use lexer::Lexer;
|
||||
use parser::Parser;
|
||||
use interpreter::Interpreter;
|
||||
use vm::compiler::Compiler;
|
||||
use vm::vm::Vm;
|
||||
use error::RuntimeError;
|
||||
@@ -37,33 +37,6 @@ pub fn run_file(filename: &str, src: String) {
|
||||
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)
|
||||
.parent()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
@@ -87,9 +60,9 @@ pub fn run_file_vm(filename: &str, src: String) {
|
||||
pub fn run_repl() {
|
||||
println!("Welcome to Aster REPL!");
|
||||
println!("Type ':exit' to quit.");
|
||||
println!("Type ':reset' to reset the interpreter state.");
|
||||
println!("Type ':reset' to reset the VM state.");
|
||||
|
||||
let mut interpreter = Interpreter::new();
|
||||
let mut vm = Vm::new();
|
||||
let mut line = String::new();
|
||||
|
||||
loop {
|
||||
@@ -113,8 +86,8 @@ pub fn run_repl() {
|
||||
break;
|
||||
}
|
||||
":reset" => {
|
||||
interpreter = Interpreter::new();
|
||||
println!("Interpreter reset.");
|
||||
vm = Vm::new();
|
||||
println!("VM reset.");
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
@@ -133,7 +106,14 @@ pub fn run_repl() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Err(e) = interpreter.interpret(stmts) {
|
||||
let proto = match Compiler::compile(&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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ impl Parser {
|
||||
self.continue_statement()
|
||||
} else if self.match_kind(&[TokenKind::Return]) {
|
||||
self.return_statement()
|
||||
} else if self.match_kind(&[TokenKind::Try]) {
|
||||
self.try_statement()
|
||||
} else if self.match_kind(&[TokenKind::Throw]) {
|
||||
self.throw_statement()
|
||||
} else if self.match_kind(&[TokenKind::LeftBrace]) {
|
||||
Ok(Stmt::Block(self.block()?))
|
||||
} else {
|
||||
@@ -201,6 +205,39 @@ impl Parser {
|
||||
Ok(Stmt::Return(value))
|
||||
}
|
||||
|
||||
fn try_statement(&mut self) -> Result<Stmt, RuntimeError> {
|
||||
self.consume(TokenKind::LeftBrace, "Expected '{' after 'try'.")?;
|
||||
let body = self.block()?;
|
||||
|
||||
let mut catch_var = None;
|
||||
let mut catch_body = None;
|
||||
if self.match_kind(&[TokenKind::Catch]) {
|
||||
self.consume(TokenKind::LeftParen, "Expected '(' after 'catch'.")?;
|
||||
catch_var = Some(self.consume_ident("Expected exception variable.")?);
|
||||
self.consume(TokenKind::RightParen, "Expected ')' after catch variable.")?;
|
||||
self.consume(TokenKind::LeftBrace, "Expected '{' before catch body.")?;
|
||||
catch_body = Some(self.block()?);
|
||||
}
|
||||
|
||||
let mut finally_body = None;
|
||||
if self.match_kind(&[TokenKind::Finally]) {
|
||||
self.consume(TokenKind::LeftBrace, "Expected '{' after 'finally'.")?;
|
||||
finally_body = Some(self.block()?);
|
||||
}
|
||||
|
||||
if catch_var.is_none() && finally_body.is_none() {
|
||||
return Err(RuntimeError::parse("Expected 'catch' or 'finally' after 'try'.", self.peek().clone()));
|
||||
}
|
||||
|
||||
Ok(Stmt::Try { body, catch_var, catch_body, finally_body })
|
||||
}
|
||||
|
||||
fn throw_statement(&mut self) -> Result<Stmt, RuntimeError> {
|
||||
let expr = self.expression()?;
|
||||
self.match_kind(&[TokenKind::Semicolon]); // 分号可选
|
||||
Ok(Stmt::Throw(expr))
|
||||
}
|
||||
|
||||
fn block(&mut self) -> Result<Vec<Stmt>, RuntimeError> {
|
||||
let mut statements = Vec::new();
|
||||
|
||||
@@ -616,7 +653,9 @@ impl Parser {
|
||||
| TokenKind::For
|
||||
| TokenKind::Return
|
||||
| TokenKind::Break
|
||||
| TokenKind::Continue => return,
|
||||
| TokenKind::Continue
|
||||
| TokenKind::Try
|
||||
| TokenKind::Throw => return,
|
||||
_ => {
|
||||
self.advance();
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
//! 标准库:core(len, typeof, push, pop)
|
||||
|
||||
use crate::error::RuntimeError;
|
||||
use crate::interpreter::{Runtime, Value};
|
||||
use crate::runtime::{Runtime, Value};
|
||||
|
||||
pub fn len(_runtime: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||
if args.is_empty() {
|
||||
@@ -1,7 +1,7 @@
|
||||
//! 标准库:io(print, input)
|
||||
|
||||
use crate::error::RuntimeError;
|
||||
use crate::interpreter::{Runtime, Value};
|
||||
use crate::runtime::{Runtime, Value};
|
||||
|
||||
pub fn print(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||
for arg in args.iter() {
|
||||
@@ -0,0 +1,45 @@
|
||||
//! 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)));
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
//! 标准库:os(clock)
|
||||
|
||||
use crate::error::RuntimeError;
|
||||
use crate::interpreter::{Runtime, Value};
|
||||
use crate::runtime::{Runtime, Value};
|
||||
|
||||
pub fn clock(_interp: &mut dyn Runtime, _args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||
Ok(Value::Number(
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
//! 标准库:string(split, trim, substring, replace, contains, upper, lower, starts_with, ends_with)
|
||||
|
||||
use crate::error::RuntimeError;
|
||||
use crate::interpreter::{Runtime, Value};
|
||||
use crate::runtime::{Runtime, Value};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
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)
|
||||
pub exception_handlers: Vec<ExceptionHandler>,
|
||||
}
|
||||
|
||||
/// Exception handler entry: defines a try-catch-finally region.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExceptionHandler {
|
||||
pub try_start: usize, // inclusive
|
||||
pub try_end: usize, // exclusive
|
||||
pub catch_ip: usize, // catch handler IP (0 if no catch clause)
|
||||
pub catch_slot: u8, // local slot for catch variable (0 if no catch)
|
||||
pub finally_ip: usize, // finally rethrow entry (0 if no finally)
|
||||
}
|
||||
|
||||
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(),
|
||||
exception_handlers: 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)
|
||||
}
|
||||
+364
-157
@@ -7,65 +7,17 @@
|
||||
use crate::ast::*;
|
||||
use crate::ast::expr::{Literal, UnaryOp, BinaryOp, LogicalOp, AssignOp};
|
||||
use crate::error::RuntimeError;
|
||||
use crate::interpreter::Value;
|
||||
use crate::runtime::{Value, FunctionProto, ExceptionHandler};
|
||||
use super::opcode::*;
|
||||
|
||||
use std::rc::Rc;
|
||||
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>,
|
||||
pub upvalue_count: u8,
|
||||
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(),
|
||||
upvalue_count: 0,
|
||||
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
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Local {
|
||||
name: String,
|
||||
depth: u8, // scope depth where declared; 0 = uninitialized
|
||||
@@ -73,26 +25,34 @@ struct Local {
|
||||
is_const: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Upvalue {
|
||||
index: u8,
|
||||
is_local: bool, // true = captured from enclosing fn's local; false = from upvalue
|
||||
is_const: bool, // true = the source variable was declared `const`
|
||||
name: String, // variable name (for transitive upvalue resolution)
|
||||
}
|
||||
|
||||
struct LoopContext {
|
||||
start_ip: usize, // bytecode offset of loop condition
|
||||
break_patches: Vec<usize>, // jump instruction offsets that need break dest
|
||||
continue_patches: Vec<usize>,// jump instruction offsets that need continue dest
|
||||
scope_depth: u8, // scope depth when loop started
|
||||
break_patches: Vec<usize>,
|
||||
continue_patches: Vec<usize>,
|
||||
}
|
||||
|
||||
pub struct Compiler {
|
||||
function: FunctionProto,
|
||||
locals: Vec<Local>,
|
||||
upvalues: Vec<Upvalue>,
|
||||
/// Upvalues for this function (shared with child for transitive resolution)
|
||||
upvalues: Rc<RefCell<Vec<Upvalue>>>,
|
||||
/// ALL enclosing locals from entire chain (merged, direct parent first)
|
||||
enclosing_locals: Option<Vec<Local>>,
|
||||
/// Direct parent's own locals count (to distinguish parent locals from deeper ones)
|
||||
parent_locals_count: usize,
|
||||
/// Direct parent's upvalues (shared)
|
||||
enclosing_upvalues: Option<Rc<RefCell<Vec<Upvalue>>>>,
|
||||
/// Grandparent's upvalues (shared, for 3-level transitive capture)
|
||||
grandparent_upvalues: Option<Rc<RefCell<Vec<Upvalue>>>>,
|
||||
scope_depth: u8,
|
||||
loop_stack: Vec<LoopContext>,
|
||||
/// Index into an outer compiler array for upvalue resolution
|
||||
enclosing_idx: Option<usize>,
|
||||
}
|
||||
|
||||
impl Compiler {
|
||||
@@ -100,10 +60,13 @@ impl Compiler {
|
||||
Self {
|
||||
function: FunctionProto::new(name),
|
||||
locals: Vec::new(),
|
||||
upvalues: Vec::new(),
|
||||
upvalues: Rc::new(RefCell::new(Vec::new())),
|
||||
enclosing_locals: None,
|
||||
parent_locals_count: 0,
|
||||
enclosing_upvalues: None,
|
||||
grandparent_upvalues: None,
|
||||
scope_depth: 0,
|
||||
loop_stack: Vec::new(),
|
||||
enclosing_idx: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +122,8 @@ impl Compiler {
|
||||
}
|
||||
Stmt::Function { name, params, body } => {
|
||||
self.compile_function_decl(name, params, body)?;
|
||||
// DefineGlobal pushes the value back; pop it as this is a statement
|
||||
self.emit_op(OpCode::Pop);
|
||||
}
|
||||
Stmt::Return(expr) => {
|
||||
if let Some(e) = expr {
|
||||
@@ -174,6 +139,12 @@ impl Compiler {
|
||||
Stmt::Continue => {
|
||||
self.compile_continue()?;
|
||||
}
|
||||
Stmt::Try { body, catch_var, catch_body, finally_body } => {
|
||||
self.compile_try(body, catch_var.as_deref(), catch_body.as_ref(), finally_body.as_ref())?;
|
||||
}
|
||||
Stmt::Throw(expr) => {
|
||||
self.compile_throw(expr)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -181,11 +152,12 @@ impl Compiler {
|
||||
fn compile_let(&mut self, name: &str, initializer: &Expr, mutable: bool) -> Result<(), RuntimeError> {
|
||||
self.compile_expr(initializer)?;
|
||||
if self.scope_depth == 0 {
|
||||
// Global variable
|
||||
let name_idx = self.add_string_constant(name);
|
||||
emit_u16(&mut self.function.code, OpCode::DefineGlobal, name_idx);
|
||||
self.function.code.push(if mutable { 1 } else { 0 });
|
||||
// DefineGlobal pushes value back; pop it for statement-level let
|
||||
self.emit_op(OpCode::Pop);
|
||||
} else {
|
||||
// Local variable
|
||||
let slot = self.locals.len() as u8;
|
||||
self.locals.push(Local {
|
||||
name: name.to_string(),
|
||||
@@ -193,6 +165,7 @@ impl Compiler {
|
||||
is_captured: false,
|
||||
is_const: !mutable,
|
||||
});
|
||||
// StoreLocal PEEKS the value — it stays on stack as the local
|
||||
emit_u8(&mut self.function.code, OpCode::StoreLocal, slot);
|
||||
}
|
||||
Ok(())
|
||||
@@ -219,10 +192,8 @@ impl Compiler {
|
||||
let exit_jump = self.emit_jump(OpCode::JumpIfFalse);
|
||||
|
||||
self.loop_stack.push(LoopContext {
|
||||
start_ip,
|
||||
break_patches: Vec::new(),
|
||||
continue_patches: Vec::new(),
|
||||
scope_depth: self.scope_depth,
|
||||
});
|
||||
|
||||
self.compile_stmt(body)?;
|
||||
@@ -257,10 +228,8 @@ impl Compiler {
|
||||
let exit_jump = self.emit_jump(OpCode::JumpIfFalse);
|
||||
|
||||
self.loop_stack.push(LoopContext {
|
||||
start_ip,
|
||||
break_patches: Vec::new(),
|
||||
continue_patches: Vec::new(),
|
||||
scope_depth: self.scope_depth,
|
||||
});
|
||||
|
||||
self.compile_stmt(body)?;
|
||||
@@ -289,13 +258,39 @@ impl Compiler {
|
||||
}
|
||||
|
||||
fn compile_for_in(&mut self, var_name: &str, iterable: &Expr, body: &Stmt) -> Result<(), RuntimeError> {
|
||||
// Evaluate iterable, set up iterator
|
||||
self.compile_expr(iterable)?;
|
||||
emit_op(&mut self.function.code, OpCode::ForInInit);
|
||||
let exit_jump = emit_i16_placeholder(&mut self.function.code, OpCode::ForInNext);
|
||||
|
||||
// New scope for the loop variable
|
||||
// 1. New scope for hidden iterator locals (items, idx)
|
||||
self.begin_scope();
|
||||
|
||||
// 2. Evaluate iterable and compute items array
|
||||
self.compile_expr(iterable)?;
|
||||
emit_op(&mut self.function.code, OpCode::ForInInit); // pops iterable, pushes items[]
|
||||
|
||||
// 3. Store items array in a hidden local (peek, no Pop — cleaned by end_scope)
|
||||
let items_slot = self.locals.len() as u8;
|
||||
self.locals.push(Local {
|
||||
name: format!("__iter_items_{}", items_slot),
|
||||
depth: self.scope_depth,
|
||||
is_captured: false,
|
||||
is_const: false,
|
||||
});
|
||||
emit_u8(&mut self.function.code, OpCode::StoreLocal, items_slot);
|
||||
|
||||
// 4. Initialize index = 0 in hidden local
|
||||
let idx_slot = self.locals.len() as u8;
|
||||
self.locals.push(Local {
|
||||
name: format!("__iter_idx_{}", idx_slot),
|
||||
depth: self.scope_depth,
|
||||
is_captured: false,
|
||||
is_const: false,
|
||||
});
|
||||
let zero_idx = self.add_constant(Value::Number(0.0));
|
||||
emit_u16(&mut self.function.code, OpCode::LoadConst, zero_idx);
|
||||
emit_u8(&mut self.function.code, OpCode::StoreLocal, idx_slot);
|
||||
|
||||
// 5. ForInNext: reads items[idx_slot], idx[idx_slot], pushes element
|
||||
let forin_loc = self.emit_forin_next(items_slot, idx_slot);
|
||||
|
||||
// 6. Store loop variable in a local (peek, value stays on stack)
|
||||
let loop_var_slot = self.locals.len() as u8;
|
||||
self.locals.push(Local {
|
||||
name: var_name.to_string(),
|
||||
@@ -305,64 +300,54 @@ impl Compiler {
|
||||
});
|
||||
emit_u8(&mut self.function.code, OpCode::StoreLocal, loop_var_slot);
|
||||
|
||||
let start_ip = self.function.code.len();
|
||||
|
||||
// 7. Loop body (can resolve var_name to loop_var_slot)
|
||||
self.loop_stack.push(LoopContext {
|
||||
start_ip,
|
||||
break_patches: Vec::new(),
|
||||
continue_patches: Vec::new(),
|
||||
scope_depth: self.scope_depth,
|
||||
});
|
||||
|
||||
self.compile_stmt(body)?;
|
||||
|
||||
// Patch continue → loop back
|
||||
// Patch continue
|
||||
let continue_ip = self.function.code.len();
|
||||
let loop_ctx = self.loop_stack.pop().unwrap();
|
||||
for patch in loop_ctx.continue_patches {
|
||||
self.patch_jump_to(patch, continue_ip);
|
||||
}
|
||||
|
||||
// Jump back to ForInNext
|
||||
self.emit_loop_jump(start_ip - 3); // jump back to the ForInNext instruction
|
||||
let exit_ip = self.function.code.len();
|
||||
self.patch_jump_to(exit_jump, exit_ip);
|
||||
// 8. Pop loop var element and remove from tracking
|
||||
self.emit_op(OpCode::Pop);
|
||||
self.locals.pop(); // loop_var_slot
|
||||
|
||||
// 9. Jump back to ForInNext (next iteration re-adds loop var via StoreLocal)
|
||||
self.emit_loop_jump(forin_loc);
|
||||
|
||||
// 10. Exit target: patch ForInNext exit and break jumps
|
||||
let exit_ip = self.function.code.len();
|
||||
self.patch_forin_jump(forin_loc, exit_ip);
|
||||
for patch in loop_ctx.break_patches {
|
||||
self.patch_jump(patch);
|
||||
self.patch_jump_to(patch, exit_ip);
|
||||
}
|
||||
|
||||
// Pop the loop variable
|
||||
self.emit_op(OpCode::Pop);
|
||||
// Pop the iterator state (2 values: iterable ref + index)
|
||||
self.emit_op(OpCode::Pop);
|
||||
// 11. Pop loop var (ForInNext pushes Nil on exit; break leaves element on stack)
|
||||
self.emit_op(OpCode::Pop);
|
||||
|
||||
// 12. End scope: pops items_slot + idx_slot values (left by StoreLocal peek)
|
||||
self.end_scope();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_function_decl(&mut self, name: &str, params: &[String], body: &[Stmt]) -> Result<(), RuntimeError> {
|
||||
let proto = self.compile_nested_function(Some(name.to_string()), params, body)?;
|
||||
let const_idx = self.function.add_constant(Value::Function(Rc::new(
|
||||
crate::interpreter::Function {
|
||||
params: params.to_vec(),
|
||||
body: body.to_vec(),
|
||||
env: Rc::new(RefCell::new(crate::interpreter::Env::new(None))),
|
||||
name: Some(name.to_string()),
|
||||
}
|
||||
)));
|
||||
// For now, we also need to store the proto for the VM to use.
|
||||
// We'll store it as a special constant.
|
||||
let upvalues = proto.upvalues.clone();
|
||||
let proto_idx = self.add_function_proto_constant(proto);
|
||||
|
||||
// Emit Closure opcode with upvalues
|
||||
self.emit_closure(proto_idx);
|
||||
self.emit_closure(proto_idx, &upvalues);
|
||||
|
||||
// Bind to name
|
||||
if self.scope_depth == 0 {
|
||||
let name_idx = self.add_string_constant(name);
|
||||
emit_u16(&mut self.function.code, OpCode::DefineGlobal, name_idx);
|
||||
self.function.code.push(1); // mutable=true for fn declarations
|
||||
} else {
|
||||
let slot = self.locals.len() as u8;
|
||||
self.locals.push(Local {
|
||||
@@ -396,6 +381,87 @@ impl Compiler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_throw(&mut self, expr: &Expr) -> Result<(), RuntimeError> {
|
||||
self.compile_expr(expr)?;
|
||||
self.emit_op(OpCode::Throw);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_try(
|
||||
&mut self,
|
||||
body: &[Stmt],
|
||||
catch_var: Option<&str>,
|
||||
catch_body: Option<&Vec<Stmt>>,
|
||||
finally_body: Option<&Vec<Stmt>>,
|
||||
) -> Result<(), RuntimeError> {
|
||||
let try_start = self.function.code.len();
|
||||
|
||||
// Compile try body
|
||||
self.begin_scope();
|
||||
self.compile_stmts(body)?;
|
||||
self.end_scope();
|
||||
let try_end = self.function.code.len();
|
||||
|
||||
// ── Normal path: jump past catch to finally (or end) ──
|
||||
let skip_catch = self.emit_jump(OpCode::Jump);
|
||||
|
||||
// ── Catch handler (entry via unwind) ──
|
||||
let (catch_ip, catch_slot) = if let (Some(var), Some(cb)) = (catch_var, catch_body) {
|
||||
let ip = self.function.code.len();
|
||||
self.begin_scope();
|
||||
let slot = self.locals.len() as u8;
|
||||
self.locals.push(Local {
|
||||
name: var.to_string(),
|
||||
depth: self.scope_depth,
|
||||
is_captured: false,
|
||||
is_const: false,
|
||||
});
|
||||
// VM pushes exception value before jumping here; StoreLocal peeks it
|
||||
emit_u8(&mut self.function.code, OpCode::StoreLocal, slot);
|
||||
self.compile_stmts(cb)?;
|
||||
self.end_scope();
|
||||
(ip, slot)
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
self.patch_jump(skip_catch);
|
||||
|
||||
// ── Finally: inline for success paths (try/catch fall through here) ──
|
||||
let finally_block_ip = if let Some(fb) = finally_body {
|
||||
let ip = self.function.code.len();
|
||||
self.begin_scope();
|
||||
self.compile_stmts(fb)?;
|
||||
self.end_scope();
|
||||
let done_jump = self.emit_jump(OpCode::Jump);
|
||||
|
||||
// ── Finally rethrow entry (for exception path) ──
|
||||
let rethrow_ip = self.function.code.len();
|
||||
// Duplicate finally body for exception path
|
||||
self.begin_scope();
|
||||
self.compile_stmts(fb)?;
|
||||
self.end_scope();
|
||||
self.emit_op(OpCode::Throw); // re-throw after finally
|
||||
|
||||
self.patch_jump(done_jump);
|
||||
(ip, rethrow_ip)
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
|
||||
let (_, rethrow_ip) = finally_block_ip;
|
||||
|
||||
// Register exception handler
|
||||
self.function.exception_handlers.push(ExceptionHandler {
|
||||
try_start,
|
||||
try_end,
|
||||
catch_ip,
|
||||
catch_slot,
|
||||
finally_ip: rethrow_ip,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Expression compilation
|
||||
// ========================================================================
|
||||
@@ -447,17 +513,8 @@ impl Compiler {
|
||||
}
|
||||
// Try to resolve as upvalue
|
||||
if let Some(upvalue_idx) = self.resolve_upvalue(name) {
|
||||
// Upvalue access: LoadUpvalue is LoadLocal with slot = upvalue-local-marker
|
||||
// For simplicity, we use a convention: upvalues are locals with special marking.
|
||||
// Actually, we need a dedicated LoadUpvalue opcode. Let's add it...
|
||||
// For now, store upvalues at "local slots" offset by 256.
|
||||
emit_u16(&mut self.function.code, OpCode::LoadConst, 0xFFFF); // placeholder
|
||||
// We'll handle upvalues properly when we have LoadUpvalue
|
||||
// TODO: Add LoadUpvalue opcode
|
||||
return Err(RuntimeError::RuntimeError {
|
||||
message: format!("Upvalue '{}' not yet supported", name),
|
||||
token: None,
|
||||
});
|
||||
emit_u8(&mut self.function.code, OpCode::LoadUpvalue, upvalue_idx);
|
||||
return Ok(());
|
||||
}
|
||||
// Fall back to global
|
||||
let name_idx = self.add_string_constant(name);
|
||||
@@ -470,7 +527,24 @@ impl Compiler {
|
||||
// Simple assignment
|
||||
self.compile_expr(value)?;
|
||||
if let Some(slot) = self.resolve_local(name) {
|
||||
// Check const for local
|
||||
if self.locals[slot as usize].is_const {
|
||||
return Err(RuntimeError::RuntimeError {
|
||||
message: format!("Cannot reassign constant '{}'", name),
|
||||
token: None,
|
||||
});
|
||||
}
|
||||
emit_u8(&mut self.function.code, OpCode::StoreLocal, slot);
|
||||
} else if let Some(uv_idx) = self.resolve_upvalue(name) {
|
||||
// Check const for upvalue
|
||||
let upvalues = self.upvalues.borrow();
|
||||
if upvalues[uv_idx as usize].is_const {
|
||||
return Err(RuntimeError::RuntimeError {
|
||||
message: format!("Cannot reassign constant '{}'", name),
|
||||
token: None,
|
||||
});
|
||||
}
|
||||
emit_u8(&mut self.function.code, OpCode::StoreUpvalue, uv_idx);
|
||||
} else {
|
||||
let name_idx = self.add_string_constant(name);
|
||||
emit_u16(&mut self.function.code, OpCode::StoreGlobal, name_idx);
|
||||
@@ -489,9 +563,12 @@ impl Compiler {
|
||||
if let Some(slot) = self.resolve_local(name) {
|
||||
emit_u8(&mut self.function.code, OpCode::CompoundAssignLocal, slot);
|
||||
self.function.code.push(compound_op as u8);
|
||||
} else if let Some(uv_idx) = self.resolve_upvalue(name) {
|
||||
emit_u8(&mut self.function.code, OpCode::CompoundAssignUpvalue, uv_idx);
|
||||
self.function.code.push(compound_op as u8);
|
||||
} else {
|
||||
let name_idx = self.add_string_constant(name);
|
||||
emit_u16(&mut self.function.code, OpCode::CompoundAssignProp, name_idx);
|
||||
emit_u16(&mut self.function.code, OpCode::CompoundAssignGlobal, name_idx);
|
||||
self.function.code.push(compound_op as u8);
|
||||
}
|
||||
}
|
||||
@@ -513,16 +590,11 @@ impl Compiler {
|
||||
emit_u16(&mut self.function.code, OpCode::SetProperty, name_idx);
|
||||
} else {
|
||||
// Compound property set: object.name op= value
|
||||
// Strategy: load object, dup, get property as current value,
|
||||
// load rhs, apply op, set property
|
||||
// CompoundAssignProp handler does get_property internally
|
||||
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
|
||||
let compound_op = assign_op_to_compound(op);
|
||||
self.function.code.push(compound_op as u8);
|
||||
// Now stack: object, current_val, rhs → set property
|
||||
let name_idx = self.add_string_constant(name);
|
||||
emit_u16(&mut self.function.code, OpCode::CompoundAssignProp, name_idx);
|
||||
self.function.code.push(compound_op as u8);
|
||||
}
|
||||
@@ -643,27 +715,33 @@ impl Compiler {
|
||||
|
||||
fn compile_lambda(&mut self, params: &[String], body: &[Stmt]) -> Result<(), RuntimeError> {
|
||||
let proto = self.compile_nested_function(None, params, body)?;
|
||||
let upvalues = proto.upvalues.clone();
|
||||
let proto_idx = self.add_function_proto_constant(proto);
|
||||
self.emit_closure(proto_idx);
|
||||
self.emit_closure(proto_idx, &upvalues);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compile a nested function (lambda or function declaration) and return its proto.
|
||||
fn compile_nested_function(&mut self, name: Option<String>, params: &[String], body: &[Stmt]) -> Result<FunctionProto, RuntimeError> {
|
||||
let mut child = Compiler::new(name);
|
||||
child.enclosing_idx = Some(0); // placeholder — we handle upvalues differently
|
||||
// Build merged enclosing locals: our locals + our enclosing chain
|
||||
let mut all_locals = self.locals.clone();
|
||||
if let Some(ref enc) = self.enclosing_locals {
|
||||
all_locals.extend(enc.iter().cloned());
|
||||
}
|
||||
child.enclosing_locals = Some(all_locals);
|
||||
child.parent_locals_count = self.locals.len();
|
||||
child.enclosing_upvalues = Some(Rc::clone(&self.upvalues));
|
||||
child.grandparent_upvalues = self.enclosing_upvalues.clone();
|
||||
|
||||
// Add params as locals
|
||||
// Add params as locals without StoreLocal — they're already on stack from Call
|
||||
for param in params {
|
||||
let slot = child.locals.len() as u8;
|
||||
child.locals.push(Local {
|
||||
name: param.clone(),
|
||||
depth: 1, // params are at scope depth 1 (function body)
|
||||
is_captured: false,
|
||||
is_const: false,
|
||||
});
|
||||
// Params are already on stack from Call; StoreLocal just peeks
|
||||
emit_u8(&mut child.function.code, OpCode::StoreLocal, slot);
|
||||
}
|
||||
child.function.arity = params.len() as u8;
|
||||
|
||||
@@ -676,11 +754,19 @@ impl Compiler {
|
||||
child.emit_op(OpCode::LoadNil);
|
||||
child.emit_op(OpCode::Return);
|
||||
|
||||
// Resolve upvalues: for each variable reference in the child that wasn't
|
||||
// resolved locally, check if it exists in the parent's locals/upvalues.
|
||||
// (We handle this lazily in compile_variable for now — if not local, try upvalue.)
|
||||
// Mark captured locals in the parent compiler
|
||||
for uv in child.upvalues.borrow().iter() {
|
||||
if uv.is_local {
|
||||
if let Some(local) = self.locals.get_mut(uv.index as usize) {
|
||||
local.is_captured = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
child.function.upvalue_count = child.upvalues.len() as u8;
|
||||
let child_upvalues = child.upvalues.borrow();
|
||||
child.function.upvalue_count = child_upvalues.len() as u8;
|
||||
child.function.upvalues = child_upvalues.iter().map(|uv| (uv.is_local, uv.index)).collect();
|
||||
drop(child_upvalues);
|
||||
Ok(child.function)
|
||||
}
|
||||
|
||||
@@ -694,16 +780,18 @@ impl Compiler {
|
||||
|
||||
fn end_scope(&mut self) {
|
||||
self.scope_depth -= 1;
|
||||
// Pop locals that are going out of scope
|
||||
// Pop locals that are going out of scope (except captured ones)
|
||||
let mut pop_count = 0u8;
|
||||
while let Some(local) = self.locals.last() {
|
||||
if local.depth > self.scope_depth {
|
||||
if local.is_captured {
|
||||
// Close upvalue instead of pop
|
||||
// (For now, just pop — upvalue closing handled in VM)
|
||||
// Don't pop — the upvalue still needs it on the stack
|
||||
// The VM will close the upvalue when the function returns
|
||||
self.locals.pop();
|
||||
} else {
|
||||
self.locals.pop();
|
||||
pop_count += 1;
|
||||
}
|
||||
self.locals.pop();
|
||||
pop_count += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -728,9 +816,117 @@ impl Compiler {
|
||||
}
|
||||
|
||||
/// Try to resolve a variable as an upvalue from enclosing functions.
|
||||
/// Returns the upvalue index in this function's upvalues list.
|
||||
fn resolve_upvalue(&mut self, name: &str) -> Option<u8> {
|
||||
// For now, we don't have enclosing compiler access.
|
||||
// Upvalues will be fully implemented in a follow-up.
|
||||
// Check if already captured
|
||||
for (j, uv) in self.upvalues.borrow().iter().enumerate() {
|
||||
if uv.name == name {
|
||||
return Some(j as u8);
|
||||
}
|
||||
}
|
||||
// Check all enclosing locals (merged chain: parent, grandparent, ...)
|
||||
if let Some(ref enclosing_locals) = self.enclosing_locals {
|
||||
for (i, local) in enclosing_locals.iter().enumerate().rev() {
|
||||
if local.name == name && local.depth > 0 {
|
||||
if i < self.parent_locals_count {
|
||||
// Found in direct parent's locals → capture as upvalue
|
||||
let idx = self.upvalues.borrow().len() as u8;
|
||||
self.upvalues.borrow_mut().push(Upvalue {
|
||||
index: i as u8, is_local: true,
|
||||
is_const: local.is_const,
|
||||
name: name.to_string()
|
||||
});
|
||||
return Some(idx);
|
||||
} else {
|
||||
// Found deeper than parent. Need to create upvalue chain.
|
||||
if let Some(ref enc_upvalues) = self.enclosing_upvalues {
|
||||
// Check if parent already has this upvalue
|
||||
let parent_uv_idx = {
|
||||
let enc = enc_upvalues.borrow();
|
||||
enc.iter().position(|uv| uv.name == name).map(|p| p as u8)
|
||||
};
|
||||
let parent_idx = match parent_uv_idx {
|
||||
Some(idx) => idx,
|
||||
None => {
|
||||
// Add upvalue to parent's list.
|
||||
// The parent sees this variable at a certain index in its own
|
||||
// enclosing_locals. That index is (i - self.parent_locals_count)
|
||||
// in the merged list, which corresponds to the same variable
|
||||
// in the parent's enclosing_locals.
|
||||
let enc_idx = enc_upvalues.borrow().len() as u8;
|
||||
|
||||
// If we have grandparent_upvalues, the parent's upvalue
|
||||
// should be transitive (is_local=false), and we need to
|
||||
// ensure grandparent has it too.
|
||||
if let Some(ref gp_upvalues) = self.grandparent_upvalues {
|
||||
// Ensure grandparent has the upvalue first
|
||||
let gp_idx = {
|
||||
let gp = gp_upvalues.borrow();
|
||||
gp.iter().position(|uv| uv.name == name).map(|p| p as u8)
|
||||
};
|
||||
let gp_idx = match gp_idx {
|
||||
Some(idx) => idx,
|
||||
None => {
|
||||
let idx = gp_upvalues.borrow().len() as u8;
|
||||
// Grandparent captures this as a local
|
||||
// (it's directly in the grandparent's enclosing scope)
|
||||
gp_upvalues.borrow_mut().push(Upvalue {
|
||||
index: (i - self.parent_locals_count) as u8,
|
||||
is_local: true,
|
||||
is_const: false, // can't easily resolve const-ness at this depth
|
||||
name: name.to_string()
|
||||
});
|
||||
idx
|
||||
}
|
||||
};
|
||||
// Parent's upvalue is transitive through grandparent
|
||||
let gp_uv_is_const = gp_upvalues.borrow()[gp_idx as usize].is_const;
|
||||
enc_upvalues.borrow_mut().push(Upvalue {
|
||||
index: gp_idx,
|
||||
is_local: false,
|
||||
is_const: gp_uv_is_const,
|
||||
name: name.to_string()
|
||||
});
|
||||
} else {
|
||||
// No grandparent — parent captures directly as local
|
||||
enc_upvalues.borrow_mut().push(Upvalue {
|
||||
index: (i - self.parent_locals_count) as u8,
|
||||
is_local: true,
|
||||
is_const: local.is_const,
|
||||
name: name.to_string()
|
||||
});
|
||||
}
|
||||
enc_idx
|
||||
}
|
||||
};
|
||||
// Add transitive upvalue in self pointing to parent's
|
||||
let parent_uv_is_const = enc_upvalues.borrow()[parent_idx as usize].is_const;
|
||||
let idx = self.upvalues.borrow().len() as u8;
|
||||
self.upvalues.borrow_mut().push(Upvalue {
|
||||
index: parent_idx, is_local: false,
|
||||
is_const: parent_uv_is_const,
|
||||
name: name.to_string()
|
||||
});
|
||||
return Some(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check parent's upvalues (transitive closure over 2 levels)
|
||||
if let Some(ref enclosing_upvalues) = self.enclosing_upvalues {
|
||||
for uv in enclosing_upvalues.borrow().iter().rev() {
|
||||
if uv.name == name {
|
||||
let idx = self.upvalues.borrow().len() as u8;
|
||||
self.upvalues.borrow_mut().push(Upvalue {
|
||||
index: uv.index, is_local: false,
|
||||
is_const: uv.is_const,
|
||||
name: name.to_string()
|
||||
});
|
||||
return Some(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -759,13 +955,28 @@ impl Compiler {
|
||||
emit_i16(&mut self.function.code, OpCode::Jump, offset as i16);
|
||||
}
|
||||
|
||||
fn emit_closure(&mut self, proto_idx: u16) {
|
||||
fn emit_forin_next(&mut self, items_slot: u8, idx_slot: u8) -> usize {
|
||||
let loc = self.function.code.len();
|
||||
let code = &mut self.function.code;
|
||||
code.push(OpCode::ForInNext as u8);
|
||||
code.push(items_slot);
|
||||
code.push(idx_slot);
|
||||
code.push(0xFF); // placeholder offset low
|
||||
code.push(0x7F); // placeholder offset high
|
||||
loc
|
||||
}
|
||||
|
||||
fn emit_closure(&mut self, proto_idx: u16, upvalues: &[(bool, u8)]) {
|
||||
let code = &mut self.function.code;
|
||||
code.push(OpCode::Closure as u8);
|
||||
code.push((proto_idx & 0xFF) as u8);
|
||||
code.push(((proto_idx >> 8) & 0xFF) as u8);
|
||||
// No upvalues yet
|
||||
code.push(0u8); // upvalue count = 0 for now
|
||||
let upvalue_count = upvalues.len() as u8;
|
||||
code.push(upvalue_count);
|
||||
for &(is_local, index) in upvalues {
|
||||
code.push(if is_local { 1u8 } else { 0u8 });
|
||||
code.push(index);
|
||||
}
|
||||
}
|
||||
|
||||
fn patch_jump(&mut self, jump_loc: usize) {
|
||||
@@ -782,6 +993,18 @@ impl Compiler {
|
||||
code[jump_loc + 2] = ((offset >> 8) & 0xFF) as u8;
|
||||
}
|
||||
|
||||
/// Patch ForInNext's exit offset (at jump_loc + 3, +4)
|
||||
fn patch_forin_jump(&mut self, forin_loc: usize, target: usize) {
|
||||
let offset = target as isize - forin_loc as isize;
|
||||
let code = &mut self.function.code;
|
||||
code[forin_loc + 3] = (offset & 0xFF) as u8;
|
||||
code[forin_loc + 4] = ((offset >> 8) & 0xFF) as u8;
|
||||
}
|
||||
|
||||
fn add_constant(&mut self, val: Value) -> u16 {
|
||||
self.function.add_constant(val)
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Constant pool helpers
|
||||
// ========================================================================
|
||||
@@ -791,20 +1014,10 @@ impl Compiler {
|
||||
}
|
||||
|
||||
fn add_function_proto_constant(&mut self, proto: FunctionProto) -> u16 {
|
||||
// Store compiled proto as a special marker.
|
||||
// We use Value::Nil as placeholder since FunctionProto isn't a Value.
|
||||
// The VM will look up the proto from a separate table.
|
||||
// For now, store the proto's index in a side table.
|
||||
// Actually, let's store it as a string tag that the VM can recognize.
|
||||
// We'll use a dedicated proto storage: just append to a Vec.
|
||||
// But FunctionProto isn't a Value... let's store it inline.
|
||||
// HACK: store proto in constants with a special Object wrapping
|
||||
let idx = self.function.constants.len() as u16;
|
||||
// Use a Value::Object with a special marker
|
||||
// The VM will need to handle this
|
||||
let mut map = std::collections::HashMap::new();
|
||||
map.insert("__proto__".to_string(), Value::String(format!("proto_{}", idx)));
|
||||
self.function.constants.push(Value::Object(Rc::new(RefCell::new(map))));
|
||||
let idx = self.function.protos.len() as u16;
|
||||
self.function.protos.push(Rc::new(proto));
|
||||
// Store proto index as a sentinel value in constants
|
||||
self.function.constants.push(Value::Number(f64::from_bits(idx as u64 | 0x_F000_0000_0000_0000)));
|
||||
idx
|
||||
}
|
||||
}
|
||||
@@ -823,9 +1036,3 @@ fn assign_op_to_compound(op: &AssignOp) -> CompoundOp {
|
||||
AssignOp::Equal => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_i16_placeholder(code: &mut Vec<u8>, op: OpCode) -> usize {
|
||||
let loc = code.len();
|
||||
emit_i16(code, op, 0x7FFF);
|
||||
loc
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ pub enum OpCode {
|
||||
// --- Local variables (operand: u8 slot index) ---
|
||||
LoadLocal = 6,
|
||||
StoreLocal = 7,
|
||||
LoadUpvalue = 42, // operand: u8 upvalue index
|
||||
StoreUpvalue = 43, // operand: u8 upvalue index
|
||||
|
||||
// --- Global variables (operand: u16 index into constant pool for name) ---
|
||||
LoadGlobal = 8,
|
||||
@@ -73,23 +75,26 @@ pub enum OpCode {
|
||||
ForInNext = 38, // operand: i16 done jump offset
|
||||
|
||||
// --- Compound assignment ---
|
||||
CompoundAssignLocal = 39, // operand: u8 slot + u8 compound-op tag
|
||||
CompoundAssignProp = 40, // operand: u16 name idx + u8 compound-op tag
|
||||
CompoundAssignIndex = 41, // operand: u8 compound-op tag
|
||||
CompoundAssignLocal = 39, // operand: u8 slot + u8 compound-op tag
|
||||
CompoundAssignProp = 40, // operand: u16 name idx + u8 compound-op tag
|
||||
CompoundAssignIndex = 41, // operand: u8 compound-op tag
|
||||
CompoundAssignUpvalue = 44, // operand: u8 upvalue idx + u8 compound-op tag
|
||||
CompoundAssignGlobal = 45, // operand: u16 name idx + u8 compound-op tag
|
||||
Throw = 46, // pops exception value, begins stack unwind
|
||||
}
|
||||
|
||||
impl OpCode {
|
||||
pub fn from_u8(byte: u8) -> Option<Self> {
|
||||
if byte <= 41 {
|
||||
// SAFETY: OpCode is #[repr(u8)] with contiguous values 0..=41
|
||||
Some(unsafe { std::mem::transmute::<u8, OpCode>(byte) })
|
||||
} else {
|
||||
None
|
||||
match byte {
|
||||
0..=41 | 44..=46 => Some(unsafe { std::mem::transmute::<u8, OpCode>(byte) }),
|
||||
42 => Some(OpCode::LoadUpvalue),
|
||||
43 => Some(OpCode::StoreUpvalue),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Total number of distinct opcodes
|
||||
pub const COUNT: usize = 42;
|
||||
pub const COUNT: usize = 47;
|
||||
}
|
||||
|
||||
/// Compound assignment operator tags (used as operand byte after
|
||||
@@ -168,3 +173,5 @@ pub const SIZE_OP: usize = 1;
|
||||
pub const SIZE_U8: usize = 2;
|
||||
/// Size in bytes of an opcode with a u16/i16 operand.
|
||||
pub const SIZE_U16: usize = 3;
|
||||
/// Size in bytes of an opcode with a u16 + u8 operand.
|
||||
pub const SIZE_U16_PLUS1: usize = 4;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use crate::interpreter::{Interpreter, Value};
|
||||
use crate::runtime::Value;
|
||||
use crate::lexer::Lexer;
|
||||
use crate::parser::Parser;
|
||||
use crate::vm::compiler::Compiler;
|
||||
use crate::vm::vm::Vm;
|
||||
|
||||
/// Full pipeline: source → tokens → ast → interpret → last expression value.
|
||||
/// Full pipeline: source → tokens → ast → compile → vm → last expression value.
|
||||
/// Wraps in `let __result = <expr>;` so we can read the value back.
|
||||
fn eval_expr(input: &str) -> Value {
|
||||
let wrapped = format!("let __result = {};", input);
|
||||
@@ -10,25 +12,27 @@ fn eval_expr(input: &str) -> Value {
|
||||
let mut parser = Parser::new(tokens);
|
||||
let (stmts, errors) = parser.parse();
|
||||
assert!(errors.is_empty(), "Parse errors: {:?}", errors);
|
||||
let mut interpreter = Interpreter::new();
|
||||
interpreter.interpret(stmts).expect("Runtime error");
|
||||
interpreter.env.borrow().get("__result").expect("No __result in env")
|
||||
let proto = Compiler::compile(&stmts).expect("Compile error");
|
||||
let mut vm = Vm::new();
|
||||
vm.run(std::rc::Rc::new(proto)).expect("Runtime error");
|
||||
vm.get_global("__result").expect("No __result in globals")
|
||||
}
|
||||
|
||||
/// Full pipeline for multiple statements. Returns the interpreter for env inspection.
|
||||
fn run(input: &str) -> Interpreter {
|
||||
/// Full pipeline for multiple statements. Returns the VM for state inspection.
|
||||
fn run(input: &str) -> Vm {
|
||||
let (tokens, _) = Lexer::new(input).tokenize();
|
||||
let mut parser = Parser::new(tokens);
|
||||
let (stmts, errors) = parser.parse();
|
||||
assert!(errors.is_empty(), "Parse errors: {:?}", errors);
|
||||
let mut interpreter = Interpreter::new();
|
||||
interpreter.interpret(stmts).expect("Runtime error");
|
||||
interpreter
|
||||
let proto = Compiler::compile(&stmts).expect("Compile error");
|
||||
let mut vm = Vm::new();
|
||||
vm.run(std::rc::Rc::new(proto)).expect("Runtime error");
|
||||
vm
|
||||
}
|
||||
|
||||
/// Helper: get a variable from the interpreter's environment.
|
||||
fn get_var(interp: &Interpreter, name: &str) -> Value {
|
||||
interp.env.borrow().get(name).unwrap_or(Value::Nil)
|
||||
/// Helper: get a variable from the VM's globals.
|
||||
fn get_var(vm: &Vm, name: &str) -> Value {
|
||||
vm.get_global(name).unwrap_or(Value::Nil)
|
||||
}
|
||||
|
||||
/// Helper: assert a number value.
|
||||
@@ -1733,3 +1737,305 @@ fn eval_require_empty_module() {
|
||||
v => panic!("Expected Object, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Exception handling: throw
|
||||
// =========================================================================
|
||||
|
||||
#[test]
|
||||
fn eval_throw_caught() {
|
||||
let vm = run("
|
||||
let result = 'none';
|
||||
try {
|
||||
throw 'caught!';
|
||||
} catch (e) {
|
||||
result = e;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "result") {
|
||||
Value::String(s) => assert_eq!(s, "caught!"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_throw_preserves_value_type() {
|
||||
let vm = run("
|
||||
let msg = '';
|
||||
let code = 0;
|
||||
try { throw 'error'; } catch (e) { msg = e; }
|
||||
try { throw 42; } catch (e) { code = e; }
|
||||
");
|
||||
match get_var(&vm, "msg") {
|
||||
Value::String(s) => assert_eq!(s, "error"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
assert_num(&get_var(&vm, "code"), 42.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_throw_caught_correct_handler() {
|
||||
let vm = run("
|
||||
let outer = 'none';
|
||||
let inner = 'none';
|
||||
try {
|
||||
try {
|
||||
throw 'inner';
|
||||
} catch (e) {
|
||||
inner = e;
|
||||
}
|
||||
} catch (e) {
|
||||
outer = e;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "inner") {
|
||||
Value::String(s) => assert_eq!(s, "inner"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
match get_var(&vm, "outer") {
|
||||
Value::String(s) => assert_eq!(s, "none"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Exception handling: runtime errors caught
|
||||
// =========================================================================
|
||||
|
||||
#[test]
|
||||
fn eval_catch_division_by_zero() {
|
||||
let vm = run("
|
||||
let msg = 'none';
|
||||
try {
|
||||
let x = 1 / 0;
|
||||
} catch (e) {
|
||||
msg = e;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "msg") {
|
||||
Value::String(s) => assert!(s.contains("Division by zero") || s.contains("division"), "got: {}", s),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_catch_undefined_variable() {
|
||||
let vm = run("
|
||||
let msg = 'none';
|
||||
try {
|
||||
let x = no_such_var;
|
||||
} catch (e) {
|
||||
msg = e;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "msg") {
|
||||
Value::String(s) => assert!(s.contains("Undefined"), "got: {}", s),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_catch_index_out_of_bounds() {
|
||||
let vm = run("
|
||||
let msg = 'none';
|
||||
try {
|
||||
let x = [1, 2][99];
|
||||
} catch (e) {
|
||||
msg = e;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "msg") {
|
||||
Value::String(s) => assert!(s.contains("out of bounds"), "got: {}", s),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_catch_call_non_function() {
|
||||
let vm = run("
|
||||
let msg = 'none';
|
||||
try {
|
||||
42();
|
||||
} catch (e) {
|
||||
msg = e;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "msg") {
|
||||
Value::String(s) => assert!(s.contains("non-function"), "got: {}", s),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_no_error_runs_normally() {
|
||||
let vm = run("
|
||||
let result = 0;
|
||||
try {
|
||||
result = 42;
|
||||
} catch (e) {
|
||||
result = -1;
|
||||
}
|
||||
");
|
||||
assert_num(&get_var(&vm, "result"), 42.0);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Exception handling: finally
|
||||
// =========================================================================
|
||||
|
||||
#[test]
|
||||
fn eval_finally_runs_on_success() {
|
||||
let vm = run("
|
||||
let flag = 0;
|
||||
try {
|
||||
flag = 1;
|
||||
} finally {
|
||||
flag = flag + 10;
|
||||
}
|
||||
");
|
||||
assert_num(&get_var(&vm, "flag"), 11.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_finally_runs_on_throw() {
|
||||
let vm = run("
|
||||
let flag = 0;
|
||||
let msg = 'none';
|
||||
try {
|
||||
throw 'oops';
|
||||
} catch (e) {
|
||||
msg = e;
|
||||
} finally {
|
||||
flag = flag + 10;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "msg") {
|
||||
Value::String(s) => assert_eq!(s, "oops"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
assert_num(&get_var(&vm, "flag"), 10.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_finally_runs_on_runtime_error() {
|
||||
let vm = run("
|
||||
let flag = 0;
|
||||
let msg = 'none';
|
||||
try {
|
||||
let x = 1 / 0;
|
||||
} catch (e) {
|
||||
msg = 'caught';
|
||||
} finally {
|
||||
flag = 1;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "msg") {
|
||||
Value::String(s) => assert_eq!(s, "caught"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
assert_num(&get_var(&vm, "flag"), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_finally_without_catch() {
|
||||
// try-finally without catch: finally runs, then rethrows
|
||||
let result = std::panic::catch_unwind(|| {
|
||||
run("
|
||||
let flag = 0;
|
||||
try {
|
||||
throw 'boom';
|
||||
} finally {
|
||||
flag = 1;
|
||||
}
|
||||
");
|
||||
});
|
||||
assert!(result.is_err(), "Expected uncaught exception");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_nested_try_catch() {
|
||||
let vm = run("
|
||||
let outer = 'none';
|
||||
let inner = 'none';
|
||||
try {
|
||||
try {
|
||||
throw 'inner_error';
|
||||
} catch (e) {
|
||||
inner = e;
|
||||
}
|
||||
} catch (e) {
|
||||
outer = e;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "inner") {
|
||||
Value::String(s) => assert_eq!(s, "inner_error"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
match get_var(&vm, "outer") {
|
||||
Value::String(s) => assert_eq!(s, "none"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_throw_in_catch_propagates() {
|
||||
let vm = run("
|
||||
let outer = 'none';
|
||||
let inner = 'none';
|
||||
try {
|
||||
try {
|
||||
throw 'first';
|
||||
} catch (e) {
|
||||
inner = e;
|
||||
throw 'second';
|
||||
}
|
||||
} catch (e) {
|
||||
outer = e;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "inner") {
|
||||
Value::String(s) => assert_eq!(s, "first"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
match get_var(&vm, "outer") {
|
||||
Value::String(s) => assert_eq!(s, "second"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_try_catch_in_function() {
|
||||
let vm = run("
|
||||
fn safe_div(a, b) {
|
||||
try {
|
||||
return a / b;
|
||||
} catch (e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
let r1 = safe_div(10, 2);
|
||||
let r2 = safe_div(10, 0);
|
||||
");
|
||||
assert_num(&get_var(&vm, "r1"), 5.0);
|
||||
assert_num(&get_var(&vm, "r2"), -1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_finally_known_limitation_return() {
|
||||
// Note: return/break/continue inside try-finally do NOT yet run finally
|
||||
// (this is a known limitation). This test documents current behavior.
|
||||
let vm = run("
|
||||
let flag = 0;
|
||||
fn test() {
|
||||
try {
|
||||
return 42;
|
||||
} finally {
|
||||
flag = 1;
|
||||
}
|
||||
}
|
||||
let r = test();
|
||||
");
|
||||
assert_num(&get_var(&vm, "r"), 42.0);
|
||||
// flag stays 0 because finally doesn't run before return (known limitation)
|
||||
assert_num(&get_var(&vm, "flag"), 0.0);
|
||||
}
|
||||
+428
-193
@@ -4,11 +4,11 @@
|
||||
//! call frames. Implements the `Runtime` trait for native function support.
|
||||
|
||||
use crate::error::RuntimeError;
|
||||
use crate::interpreter::{Value, Runtime};
|
||||
use crate::runtime::{Value, Runtime, FunctionProto, Closure, UpvalueObj, ExceptionHandler};
|
||||
use crate::lexer::Lexer;
|
||||
use crate::parser::Parser;
|
||||
use super::opcode::*;
|
||||
use super::compiler::{Compiler, FunctionProto};
|
||||
use super::compiler::Compiler;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
@@ -18,22 +18,6 @@ use std::rc::Rc;
|
||||
// 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.
|
||||
struct CallFrame {
|
||||
closure: Rc<Closure>,
|
||||
@@ -46,10 +30,10 @@ pub struct Vm {
|
||||
pub stack: Vec<Value>,
|
||||
|
||||
/// Call frames
|
||||
pub frames: Vec<CallFrame>,
|
||||
frames: Vec<CallFrame>,
|
||||
|
||||
/// Script-level globals (top-level let bindings)
|
||||
pub globals: Rc<RefCell<HashMap<String, Value>>>,
|
||||
/// Script-level globals (top-level let/const bindings). Value is (value, is_mutable).
|
||||
pub globals: Rc<RefCell<HashMap<String, (Value, bool)>>>,
|
||||
|
||||
/// Builtins (shared across modules)
|
||||
pub builtins: Rc<RefCell<HashMap<String, Value>>>,
|
||||
@@ -61,10 +45,7 @@ pub struct Vm {
|
||||
pub current_dir: String,
|
||||
|
||||
/// Open upvalues (tracked so closures share the same upvalue object)
|
||||
pub open_upvalues: Vec<Rc<RefCell<UpvalueObj>>>,
|
||||
|
||||
/// Allocated function protos (indexed by the compiler's constant pool index)
|
||||
protos: Vec<Rc<FunctionProto>>,
|
||||
open_upvalues: Vec<Rc<RefCell<UpvalueObj>>>,
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
@@ -85,7 +66,6 @@ impl Vm {
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|_| ".".to_string()),
|
||||
open_upvalues: Vec::new(),
|
||||
protos: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +81,12 @@ impl Vm {
|
||||
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.
|
||||
pub fn run(&mut self, proto: Rc<FunctionProto>) -> Result<(), RuntimeError> {
|
||||
let closure = Rc::new(Closure {
|
||||
@@ -123,40 +109,59 @@ impl Vm {
|
||||
|
||||
fn execute_loop(&mut self) -> Result<(), RuntimeError> {
|
||||
loop {
|
||||
if self.frames.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Snapshot frame state (must drop borrow before mutating self)
|
||||
let ip = self.frames.last().unwrap().ip;
|
||||
let code_len = self.frames.last().unwrap().closure.proto.code.len();
|
||||
|
||||
if ip >= code_len {
|
||||
// End of function — implicit return nil
|
||||
self.frames.pop();
|
||||
if self.frames.is_empty() {
|
||||
return Ok(());
|
||||
match self.execute_one_step() {
|
||||
Ok(true) => continue, // more instructions to execute
|
||||
Ok(false) => return Ok(()), // VM finished
|
||||
Err(runtime_err) => {
|
||||
// Try to handle as exception
|
||||
let exc = Value::String(runtime_err.to_string());
|
||||
self.unwind(exc)?; // Err if no handler found
|
||||
continue; // Handler found, continue executing
|
||||
}
|
||||
let base = self.frames.last().unwrap().stack_base;
|
||||
self.stack.truncate(base);
|
||||
self.stack.push(Value::Nil);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clone Rc<FunctionProto> to access code without holding self.frames borrow
|
||||
let proto: Rc<FunctionProto> = Rc::clone(&self.frames.last().unwrap().closure.proto);
|
||||
/// Execute a single bytecode instruction. Returns:
|
||||
/// - Ok(true): continue the loop
|
||||
/// - Ok(false): VM is done (frames empty)
|
||||
/// - Err(...): runtime error to potentially catch
|
||||
fn execute_one_step(&mut self) -> Result<bool, RuntimeError> {
|
||||
if self.frames.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Read opcode byte
|
||||
let op = OpCode::from_u8(proto.code[ip])
|
||||
.ok_or_else(|| RuntimeError::RuntimeError {
|
||||
message: format!("Unknown opcode: {}", proto.code[ip]),
|
||||
token: None,
|
||||
})?;
|
||||
// Snapshot frame state (must drop borrow before mutating self)
|
||||
let ip = self.frames.last().unwrap().ip;
|
||||
let code_len = self.frames.last().unwrap().closure.proto.code.len();
|
||||
|
||||
// Local reference to code (borrows from proto, not self)
|
||||
let code = &proto.code;
|
||||
if ip >= code_len {
|
||||
// End of function — implicit return nil
|
||||
let frame = self.frames.pop().unwrap();
|
||||
self.close_upvalues(frame.stack_base);
|
||||
if self.frames.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
// Remove callee + args + function locals, push nil result
|
||||
self.stack.truncate(frame.stack_base.saturating_sub(1));
|
||||
self.stack.push(Value::Nil);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
match op {
|
||||
// Clone Rc<FunctionProto> to access code without holding self.frames borrow
|
||||
let proto: Rc<FunctionProto> = Rc::clone(&self.frames.last().unwrap().closure.proto);
|
||||
|
||||
// Read opcode byte
|
||||
let op = OpCode::from_u8(proto.code[ip])
|
||||
.ok_or_else(|| RuntimeError::RuntimeError {
|
||||
message: format!("Unknown opcode: {}", proto.code[ip]),
|
||||
token: None,
|
||||
})?;
|
||||
|
||||
// Local reference to code (borrows from proto, not self)
|
||||
let code = &proto.code;
|
||||
|
||||
match op {
|
||||
OpCode::Pop => {
|
||||
self.stack.pop();
|
||||
self.advance_ip(SIZE_OP);
|
||||
@@ -193,15 +198,51 @@ impl Vm {
|
||||
// --- Locals ---
|
||||
OpCode::LoadLocal => {
|
||||
let slot = read_u8(code, ip) as usize;
|
||||
let val = self.stack[self.frame().stack_base + slot].clone();
|
||||
let idx = self.frames.last().unwrap().stack_base + slot;
|
||||
if idx >= self.stack.len() {
|
||||
return Err(RuntimeError::RuntimeError {
|
||||
message: format!("LoadLocal: slot {} uninitialized", slot),
|
||||
token: None,
|
||||
});
|
||||
}
|
||||
let val = self.stack[idx].clone();
|
||||
self.stack.push(val);
|
||||
self.advance_ip(SIZE_U8);
|
||||
}
|
||||
OpCode::StoreLocal => {
|
||||
let slot = read_u8(code, ip) as usize;
|
||||
let val = self.stack.last().unwrap().clone();
|
||||
let val = self.stack.last().unwrap().clone(); // peek
|
||||
let base = self.frames.last().unwrap().stack_base;
|
||||
self.stack[base + slot] = val;
|
||||
let idx = base + slot;
|
||||
if idx >= self.stack.len() {
|
||||
self.stack.resize(idx + 1, Value::Nil);
|
||||
}
|
||||
self.stack[idx] = val;
|
||||
self.advance_ip(SIZE_U8);
|
||||
}
|
||||
OpCode::LoadUpvalue => {
|
||||
let idx = read_u8(code, ip) as usize;
|
||||
let uv = Rc::clone(&self.frames.last().unwrap().closure.upvalues[idx]);
|
||||
let uv_ref = uv.borrow();
|
||||
let val = if let Some(ref closed) = uv_ref.closed {
|
||||
closed.clone()
|
||||
} else {
|
||||
self.stack[uv_ref.location].clone()
|
||||
};
|
||||
drop(uv_ref);
|
||||
self.stack.push(val);
|
||||
self.advance_ip(SIZE_U8);
|
||||
}
|
||||
OpCode::StoreUpvalue => {
|
||||
let idx = read_u8(code, ip) as usize;
|
||||
let val = self.stack.last().unwrap().clone(); // peek
|
||||
let uv = Rc::clone(&self.frames.last().unwrap().closure.upvalues[idx]);
|
||||
let mut uv_ref = uv.borrow_mut();
|
||||
if let Some(ref mut closed) = uv_ref.closed {
|
||||
*closed = val;
|
||||
} else {
|
||||
self.stack[uv_ref.location] = val;
|
||||
}
|
||||
self.advance_ip(SIZE_U8);
|
||||
}
|
||||
|
||||
@@ -209,7 +250,8 @@ impl Vm {
|
||||
OpCode::LoadGlobal => {
|
||||
let name_idx = read_u16(code, ip) as usize;
|
||||
let name = proto_string(&proto, name_idx)?;
|
||||
let val = self.globals.borrow().get(&name).cloned()
|
||||
let val = 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),
|
||||
@@ -222,16 +264,31 @@ impl Vm {
|
||||
let name_idx = read_u16(code, ip) as usize;
|
||||
let name = proto_string(&proto,name_idx)?;
|
||||
let val = self.stack.last().unwrap().clone();
|
||||
self.globals.borrow_mut().insert(name, val);
|
||||
{
|
||||
let mut globals = self.globals.borrow_mut();
|
||||
match globals.get(&name) {
|
||||
Some((_, false)) => {
|
||||
return Err(RuntimeError::RuntimeError {
|
||||
message: format!("Cannot reassign constant '{}'", name),
|
||||
token: None,
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
// Exists and mutable, or not yet defined — store as mutable
|
||||
globals.insert(name, (val, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
self.advance_ip(SIZE_U16);
|
||||
}
|
||||
OpCode::DefineGlobal => {
|
||||
let name_idx = read_u16(code, ip) as usize;
|
||||
let name = proto_string(&proto,name_idx)?;
|
||||
let mutable = code[ip + 3] != 0; // 0 = const, 1 = mutable
|
||||
let val = self.stack.pop().unwrap();
|
||||
self.globals.borrow_mut().insert(name, val.clone());
|
||||
self.globals.borrow_mut().insert(name, (val.clone(), mutable));
|
||||
self.stack.push(val);
|
||||
self.advance_ip(SIZE_U16);
|
||||
self.advance_ip(SIZE_U16 + 1); // 4 bytes: opcode + u16 + u8
|
||||
}
|
||||
|
||||
// --- Properties ---
|
||||
@@ -402,31 +459,34 @@ impl Vm {
|
||||
let result = self.stack.pop().unwrap();
|
||||
let frame = self.frames.pop().unwrap();
|
||||
self.close_upvalues(frame.stack_base);
|
||||
self.stack.truncate(frame.stack_base);
|
||||
// Remove callee + args + function locals, push return value
|
||||
self.stack.truncate(frame.stack_base.saturating_sub(1));
|
||||
self.stack.push(result);
|
||||
if self.frames.is_empty() {
|
||||
return Ok(());
|
||||
return Ok(false);
|
||||
}
|
||||
// IP of caller is unchanged (already at next instruction)
|
||||
}
|
||||
OpCode::Closure => {
|
||||
let proto_idx = read_u16(code, ip) as usize;
|
||||
let upvalue_count = code[ip + 3] as usize;
|
||||
let proto = Rc::clone(&self.protos[proto_idx]);
|
||||
let proto = proto.protos.get(proto_idx).ok_or_else(|| RuntimeError::RuntimeError {
|
||||
message: format!("Closure proto index {} out of bounds", proto_idx),
|
||||
token: None,
|
||||
})?.clone();
|
||||
|
||||
// Collect upvalue capture info from bytecode first
|
||||
// Collect upvalue capture info
|
||||
struct UpCapture { is_local: bool, index: usize }
|
||||
let mut captures: Vec<UpCapture> = Vec::new();
|
||||
let mut offset = ip + 4;
|
||||
let mut off = ip + 4;
|
||||
for _ in 0..upvalue_count {
|
||||
let is_local = code[offset] != 0;
|
||||
offset += 1;
|
||||
let index = code[offset] as usize;
|
||||
offset += 1;
|
||||
let is_local = code[off] != 0;
|
||||
off += 1;
|
||||
let index = code[off] as usize;
|
||||
off += 1;
|
||||
captures.push(UpCapture { is_local, index });
|
||||
}
|
||||
|
||||
// Now do the actual captures (no conflicting borrows)
|
||||
// Do the actual captures
|
||||
let base = self.frames.last().unwrap().stack_base;
|
||||
let parent_upvalues = self.frames.last().unwrap().closure.upvalues.clone();
|
||||
let mut upvalues = Vec::new();
|
||||
@@ -439,17 +499,9 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
|
||||
// Store closure (stub for now)
|
||||
let _closure = Rc::new(Closure { proto, upvalues });
|
||||
self.stack.push(Value::Function(Rc::new(
|
||||
crate::interpreter::Function {
|
||||
params: Vec::new(),
|
||||
body: Vec::new(),
|
||||
env: Rc::new(RefCell::new(crate::interpreter::Env::new(None))),
|
||||
name: None,
|
||||
}
|
||||
)));
|
||||
self.advance_ip_to(offset);
|
||||
let closure = Rc::new(Closure { proto, upvalues });
|
||||
self.stack.push(Value::Function(closure));
|
||||
self.advance_ip_to(off);
|
||||
}
|
||||
|
||||
// --- Object/Array ---
|
||||
@@ -471,32 +523,49 @@ impl Vm {
|
||||
// --- For-in ---
|
||||
OpCode::ForInInit => {
|
||||
let iterable = self.stack.pop().unwrap();
|
||||
// Push iterator state: (collection, index)
|
||||
let iter = Value::Number(0.0);
|
||||
self.stack.push(iterable);
|
||||
self.stack.push(iter);
|
||||
let items = match &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.advance_ip(SIZE_OP);
|
||||
}
|
||||
OpCode::ForInNext => {
|
||||
let exit_offset = read_i16(code, ip) as isize;
|
||||
let exit_ip = ((ip as isize) + exit_offset) as usize;
|
||||
let iter_idx = self.stack.pop().unwrap(); // current index
|
||||
let collection = self.stack.pop().unwrap(); // the iterable
|
||||
// Encoding: opcode(1) + items_slot(1) + idx_slot(1) + exit_offset(2) = 5
|
||||
let items_slot = code[ip + 1] as usize;
|
||||
let idx_slot = code[ip + 2] as usize;
|
||||
let exit_offset = ((code[ip + 3] as u16) | ((code[ip + 4] as u16) << 8)) as i16;
|
||||
let exit_ip = ((ip as isize) + exit_offset as isize) as usize;
|
||||
let base = self.frames.last().unwrap().stack_base;
|
||||
|
||||
// Read index from local slot
|
||||
let idx_val = self.stack[base + idx_slot].clone();
|
||||
let idx = self.as_usize(&idx_val, "for-in index")?;
|
||||
let items_val = self.stack[base + items_slot].clone();
|
||||
|
||||
let items = match &items_val {
|
||||
Value::Array(arr) => arr.borrow(),
|
||||
_ => return Err(RuntimeError::RuntimeError {
|
||||
message: "ForInNext: items is not an array".into(),
|
||||
token: None,
|
||||
}),
|
||||
};
|
||||
|
||||
let idx = self.as_number(&iter_idx)? as usize;
|
||||
let items = self.for_in_items(&collection);
|
||||
if idx >= items.len() {
|
||||
// Done iterating — push back state and jump to exit
|
||||
self.stack.push(collection);
|
||||
self.stack.push(iter_idx);
|
||||
// Done: push Nil (keeps stack balanced for Pop at exit)
|
||||
self.stack.push(Value::Nil);
|
||||
self.advance_ip_to(exit_ip);
|
||||
} else {
|
||||
// Push back incremented state
|
||||
self.stack.push(collection);
|
||||
self.stack.push(Value::Number((idx + 1) as f64));
|
||||
// Push the current value for the loop body
|
||||
// Push current element for loop body
|
||||
self.stack.push(items[idx].clone());
|
||||
self.advance_ip(SIZE_U16);
|
||||
// Increment index in local slot
|
||||
self.stack[base + idx_slot] = Value::Number((idx + 1) as f64);
|
||||
self.advance_ip(5); // SIZE_FORIN_NEXT = 5
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,9 +576,16 @@ impl Vm {
|
||||
let compound_op = CompoundOp::from_u8(op_tag).unwrap();
|
||||
let rhs = self.stack.pop().unwrap();
|
||||
let base = self.frames.last().unwrap().stack_base;
|
||||
let lhs = self.stack[base + slot].clone();
|
||||
let idx = base + slot;
|
||||
if idx >= self.stack.len() {
|
||||
return Err(RuntimeError::RuntimeError {
|
||||
message: format!("CompoundAssignLocal to uninitialized local {}", slot),
|
||||
token: None,
|
||||
});
|
||||
}
|
||||
let lhs = self.stack[idx].clone();
|
||||
let result = self.apply_compound_op(lhs, rhs, compound_op)?;
|
||||
self.stack[base + slot] = result.clone();
|
||||
self.stack[idx] = result.clone();
|
||||
self.stack.push(result);
|
||||
self.advance_ip(SIZE_U8 + 1);
|
||||
}
|
||||
@@ -538,18 +614,71 @@ impl Vm {
|
||||
self.stack.push(result);
|
||||
self.advance_ip(SIZE_OP + 1);
|
||||
}
|
||||
}
|
||||
OpCode::CompoundAssignUpvalue => {
|
||||
let uv_idx = read_u8(code, ip) as usize;
|
||||
let op_tag = code[ip + 2];
|
||||
let compound_op = CompoundOp::from_u8(op_tag).unwrap();
|
||||
let rhs = self.stack.pop().unwrap();
|
||||
let uv = Rc::clone(&self.frames.last().unwrap().closure.upvalues[uv_idx]);
|
||||
let mut uv_ref = uv.borrow_mut();
|
||||
let lhs = if let Some(ref closed) = uv_ref.closed {
|
||||
closed.clone()
|
||||
} else {
|
||||
self.stack[uv_ref.location].clone()
|
||||
};
|
||||
let result = self.apply_compound_op(lhs, rhs, compound_op)?;
|
||||
if let Some(ref mut closed) = uv_ref.closed {
|
||||
*closed = result.clone();
|
||||
} else {
|
||||
self.stack[uv_ref.location] = result.clone();
|
||||
}
|
||||
drop(uv_ref);
|
||||
self.stack.push(result);
|
||||
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);
|
||||
}
|
||||
OpCode::Throw => {
|
||||
let exc = self.stack.pop().unwrap();
|
||||
self.unwind(exc)?;
|
||||
// unwind() returns Ok only if it found a handler and jumped there
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
// All ops (except Throw) fall through to here
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// IP management
|
||||
// ========================================================================
|
||||
|
||||
fn frame(&self) -> &CallFrame {
|
||||
self.frames.last().unwrap()
|
||||
}
|
||||
|
||||
fn frame_mut(&mut self) -> &mut CallFrame {
|
||||
self.frames.last_mut().unwrap()
|
||||
}
|
||||
@@ -562,6 +691,63 @@ impl Vm {
|
||||
self.frame_mut().ip = target;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Exception handling (stack unwind)
|
||||
// ========================================================================
|
||||
|
||||
/// Search for an exception handler covering the current IP.
|
||||
/// Returns the handler and true if a handler was found.
|
||||
fn find_handler(proto: &FunctionProto, ip: usize) -> Option<&ExceptionHandler> {
|
||||
// Search in insertion order: innermost try is compiled (and registered) first,
|
||||
// so forward iteration finds the innermost matching handler first.
|
||||
proto.exception_handlers.iter().find(|h| ip >= h.try_start && ip < h.try_end)
|
||||
}
|
||||
|
||||
/// Unwind the call stack to find an exception handler. On success,
|
||||
/// the VM's IP is set to the handler and execution continues.
|
||||
/// Returns Err if no handler is found (uncaught exception).
|
||||
fn unwind(&mut self, exception: Value) -> Result<(), RuntimeError> {
|
||||
loop {
|
||||
if self.frames.is_empty() {
|
||||
return Err(RuntimeError::RuntimeError {
|
||||
message: format!("Uncaught exception: {}", exception),
|
||||
token: None,
|
||||
});
|
||||
}
|
||||
|
||||
let ip = self.frames.last().unwrap().ip;
|
||||
let proto = Rc::clone(&self.frames.last().unwrap().closure.proto);
|
||||
|
||||
if let Some(h) = Self::find_handler(&proto, ip) {
|
||||
if h.finally_ip != 0 {
|
||||
if h.catch_ip != 0 {
|
||||
// Has catch: push exception, jump to catch.
|
||||
// Catch body falls through to inline finally.
|
||||
self.stack.push(exception);
|
||||
self.frame_mut().ip = h.catch_ip;
|
||||
return Ok(());
|
||||
} else {
|
||||
// No catch, only finally: push exception, jump to finally
|
||||
// rethrow entry. The finally body runs, then Throw.
|
||||
self.stack.push(exception);
|
||||
self.frame_mut().ip = h.finally_ip;
|
||||
return Ok(());
|
||||
}
|
||||
} else if h.catch_ip != 0 {
|
||||
// Only catch, no finally
|
||||
self.stack.push(exception);
|
||||
self.frame_mut().ip = h.catch_ip;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// No handler — pop frame and continue in caller
|
||||
let frame = self.frames.pop().unwrap();
|
||||
self.close_upvalues(frame.stack_base);
|
||||
self.stack.truncate(frame.stack_base.saturating_sub(1));
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Function calls
|
||||
// ========================================================================
|
||||
@@ -570,8 +756,13 @@ impl Vm {
|
||||
let callee_idx = self.stack.len() - 1 - arg_count;
|
||||
let callee = self.stack[callee_idx].clone();
|
||||
|
||||
match callee {
|
||||
Value::NativeFunction(native_fn) => {
|
||||
match &callee {
|
||||
Value::NativeFunction(_) => {
|
||||
// Get the native function
|
||||
let native_fn = match self.stack[callee_idx].clone() {
|
||||
Value::NativeFunction(f) => f,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
// Pop arguments from stack
|
||||
let mut args = Vec::new();
|
||||
for _ in 0..arg_count {
|
||||
@@ -583,27 +774,18 @@ impl Vm {
|
||||
self.stack.push(result);
|
||||
self.advance_ip(SIZE_U8);
|
||||
}
|
||||
Value::Function(f) => {
|
||||
// User-defined function: create new call frame
|
||||
let base = callee_idx; // where the new frame's locals start
|
||||
let new_closure = Rc::new(Closure {
|
||||
proto: Rc::new(FunctionProto::new(f.name.clone())),
|
||||
upvalues: Vec::new(),
|
||||
});
|
||||
Value::Function(closure) => {
|
||||
let closure = Rc::clone(closure);
|
||||
// Advance caller's IP past the Call instruction before pushing new frame
|
||||
self.frame_mut().ip += SIZE_U8;
|
||||
|
||||
// Callee is at callee_idx, args start at callee_idx+1
|
||||
let base = callee_idx + 1;
|
||||
self.frames.push(CallFrame {
|
||||
closure: new_closure,
|
||||
closure,
|
||||
ip: 0,
|
||||
stack_base: base,
|
||||
});
|
||||
// Arguments are already on the stack at the right position
|
||||
// (they become locals 0..arg_count in the new frame)
|
||||
// The callee itself becomes slot 0 (TODO: handle properly)
|
||||
// For now, this is a stub — full function support needs
|
||||
// proper closure/proto integration
|
||||
return Err(RuntimeError::RuntimeError {
|
||||
message: "VM user-defined function calls not yet fully implemented".into(),
|
||||
token: None,
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
return Err(RuntimeError::RuntimeError {
|
||||
@@ -653,6 +835,78 @@ impl Vm {
|
||||
},
|
||||
Value::String(s) => match name {
|
||||
"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 {
|
||||
message: format!("String has no property '{}'", name),
|
||||
token: None,
|
||||
@@ -827,7 +1081,23 @@ impl Vm {
|
||||
(Value::Bool(x), Value::Bool(y)) => x == y,
|
||||
(Value::Number(x), Value::Number(y)) => x == y,
|
||||
(Value::String(x), Value::String(y)) => x == y,
|
||||
_ => false, // Simplified — full structural equality omitted for now
|
||||
(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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -856,13 +1126,6 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_constant(&self, idx: usize) -> Result<Value, RuntimeError> {
|
||||
let frame = self.frames.last().unwrap();
|
||||
frame.closure.proto.constants.get(idx).cloned().ok_or_else(|| RuntimeError::RuntimeError {
|
||||
message: format!("Constant index {} out of bounds", idx),
|
||||
token: None,
|
||||
})
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Upvalues
|
||||
@@ -900,19 +1163,11 @@ impl Vm {
|
||||
// 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 {
|
||||
fn require(&mut self, path: &str) -> Result<Value, RuntimeError> {
|
||||
// Simplified require for VM: lex → parse → compile → execute
|
||||
// 1. Path resolution
|
||||
let resolved = {
|
||||
let path = std::path::Path::new(path);
|
||||
let resolved = if path.is_absolute() {
|
||||
@@ -928,16 +1183,19 @@ impl Runtime for Vm {
|
||||
})?
|
||||
};
|
||||
|
||||
// 2. Cache check
|
||||
if let Some(cached) = self.module_cache.borrow().get(&resolved) {
|
||||
return Ok(cached.clone());
|
||||
}
|
||||
|
||||
// 3. Read file
|
||||
let src = std::fs::read_to_string(&resolved)
|
||||
.map_err(|e| RuntimeError::RuntimeError {
|
||||
message: format!("Cannot read module '{}': {}", path, e),
|
||||
token: None,
|
||||
})?;
|
||||
|
||||
// 4. Lex
|
||||
let (tokens, lex_errors) = Lexer::new(&src).tokenize();
|
||||
if !lex_errors.is_empty() {
|
||||
return Err(RuntimeError::RuntimeError {
|
||||
@@ -946,6 +1204,7 @@ impl Runtime for Vm {
|
||||
});
|
||||
}
|
||||
|
||||
// 5. Parse
|
||||
let mut parser = Parser::new(tokens);
|
||||
let (stmts, parse_errors) = parser.parse();
|
||||
if !parse_errors.is_empty() {
|
||||
@@ -955,7 +1214,13 @@ impl Runtime for Vm {
|
||||
});
|
||||
}
|
||||
|
||||
// Create isolated VM for module execution
|
||||
// 6. Compile
|
||||
let proto = Compiler::compile(&stmts).map_err(|e| RuntimeError::RuntimeError {
|
||||
message: format!("Compile error in module '{}': {}", path, e),
|
||||
token: None,
|
||||
})?;
|
||||
|
||||
// 7. Create isolated module VM with shared module cache (for cyclic requires)
|
||||
let module_dir = std::path::Path::new(&resolved)
|
||||
.parent()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
@@ -963,31 +1228,28 @@ impl Runtime for Vm {
|
||||
|
||||
let mut module_vm = Vm::new();
|
||||
module_vm.current_dir = module_dir;
|
||||
// Share module cache and builtins
|
||||
module_vm.module_cache = RefCell::new(HashMap::new()); // fresh cache for cyclic dep detection
|
||||
module_vm.builtins = Rc::clone(&self.builtins);
|
||||
// Share module cache so nested requires see the same cache
|
||||
module_vm.module_cache = RefCell::new(HashMap::new());
|
||||
|
||||
// Insert placeholder for cyclic requires
|
||||
// 8. Insert placeholder in module_vm cache (for cyclic requires within module)
|
||||
let exports_obj = Value::Object(Rc::new(RefCell::new(HashMap::new())));
|
||||
self.module_cache.borrow_mut().insert(resolved.clone(), exports_obj.clone());
|
||||
module_vm.module_cache.borrow_mut().insert(resolved.clone(), exports_obj.clone());
|
||||
|
||||
// Compile and run
|
||||
let proto = Compiler::compile(&stmts).map_err(|e| RuntimeError::RuntimeError {
|
||||
message: format!("Compile error in module '{}': {}", path, e),
|
||||
token: None,
|
||||
})?;
|
||||
// 9. Execute module
|
||||
module_vm.run(Rc::new(proto))?;
|
||||
|
||||
// Collect exports from module's globals
|
||||
// 10. Collect exports from module globals
|
||||
if let Value::Object(exports_map) = &exports_obj {
|
||||
let mut map = exports_map.borrow_mut();
|
||||
for (name, val) in module_vm.globals.borrow().iter() {
|
||||
for (name, (val, _)) in module_vm.globals.borrow().iter() {
|
||||
map.insert(name.clone(), val.clone());
|
||||
}
|
||||
// Update shared module cache
|
||||
self.module_cache.borrow_mut().insert(resolved, exports_obj.clone());
|
||||
}
|
||||
|
||||
// 11. Cache in parent
|
||||
self.module_cache.borrow_mut().insert(resolved, exports_obj.clone());
|
||||
|
||||
Ok(exports_obj)
|
||||
}
|
||||
}
|
||||
@@ -1007,43 +1269,16 @@ fn proto_string(proto: &FunctionProto, idx: usize) -> Result<String, RuntimeErro
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Builtin registration (reuses tree-walker's builtins)
|
||||
// Builtin registration (delegates to runtime::builtins)
|
||||
// ============================================================================
|
||||
|
||||
fn register_builtins(map: &Rc<RefCell<HashMap<String, Value>>>) {
|
||||
let mut m = map.borrow_mut();
|
||||
|
||||
// io
|
||||
let mut io = HashMap::new();
|
||||
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)));
|
||||
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)]
|
||||
#[path = "tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -9,6 +9,7 @@ use lsp_types::{
|
||||
const KEYWORDS: &[&str] = &[
|
||||
"let", "const", "fn", "if", "else", "while", "for", "in",
|
||||
"break", "continue", "return", "true", "false", "nil",
|
||||
"try", "catch", "finally", "throw",
|
||||
];
|
||||
|
||||
const BUILTINS: &[&str] = &[
|
||||
|
||||
@@ -41,14 +41,8 @@ pub fn handle_goto_definition(
|
||||
let location = Location {
|
||||
uri: uri.clone(),
|
||||
range: Range {
|
||||
start: Position {
|
||||
line: l,
|
||||
character: c,
|
||||
},
|
||||
end: Position {
|
||||
line: l,
|
||||
character: c + word.len() as u32,
|
||||
},
|
||||
start: Position { line: l, character: c },
|
||||
end: Position { line: l, character: c + word.len() as u32 },
|
||||
},
|
||||
};
|
||||
let response: GotoDefinitionResponse = GotoDefinitionResponse::Scalar(location);
|
||||
|
||||
+11
-1
@@ -28,7 +28,17 @@ pub fn run() {
|
||||
text_document_sync: Some(TextDocumentSyncCapability::Kind(
|
||||
TextDocumentSyncKind::FULL,
|
||||
)),
|
||||
completion_provider: Some(lsp_types::CompletionOptions::default()),
|
||||
completion_provider: Some(lsp_types::CompletionOptions {
|
||||
trigger_characters: Some(
|
||||
(b'a'..=b'z').chain(b'A'..=b'Z')
|
||||
.map(|c| (c as char).to_string())
|
||||
.chain(std::iter::once(".".to_string()))
|
||||
.chain(std::iter::once("_".to_string()))
|
||||
.collect()
|
||||
),
|
||||
resolve_provider: Some(false),
|
||||
..Default::default()
|
||||
}),
|
||||
hover_provider: Some(lsp_types::HoverProviderCapability::Simple(true)),
|
||||
signature_help_provider: Some(lsp_types::SignatureHelpOptions::default()),
|
||||
definition_provider: Some(lsp_types::OneOf::Left(true)),
|
||||
|
||||
+4
-12
@@ -3,21 +3,13 @@ use std::fs;
|
||||
|
||||
fn main() {
|
||||
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 file_args.len() {
|
||||
match args.len() {
|
||||
1 => aster_core::run_repl(),
|
||||
2 => {
|
||||
let filename = file_args[1];
|
||||
let filename = &args[1];
|
||||
match fs::read_to_string(filename) {
|
||||
Ok(src) => {
|
||||
if use_vm {
|
||||
aster_core::run_file_vm(filename, src)
|
||||
} else {
|
||||
aster_core::run_file(filename, src)
|
||||
}
|
||||
}
|
||||
Ok(src) => aster_core::run_file(filename, src),
|
||||
Err(e) => {
|
||||
eprintln!("Error reading file '{}': {}", filename, e);
|
||||
std::process::exit(1);
|
||||
@@ -25,7 +17,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
eprintln!("Usage: aster [--vm] [script]");
|
||||
eprintln!("Usage: aster [script]");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
"patterns": [
|
||||
{
|
||||
"name": "keyword.control.aster",
|
||||
"match": "\\b(let|const|fn|if|else|while|for|in|break|continue|return)\\b"
|
||||
"match": "\\b(let|const|fn|if|else|while|for|in|break|continue|return|try|catch|finally|throw)\\b"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user