Compare commits

13 Commits

Author SHA1 Message Date
elmma 10c429cb68 Merge pull request 'try-catch' (#3) from try-catch into vm-bytecode
Reviewed-on: #3
2026-06-26 15:14:45 +08:00
0264408 af63957b11 fix: LSP补全触发和语法高亮更新
- completion_provider 注册 a-z A-Z _ . 为触发字符,解决 Copilot 拦截补全问题
- 语法高亮新增 try catch finally throw 关键字

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-26 15:13:51 +08:00
0264408 a70e5fbc30 feat: try-catch-finally 异常处理
语法: try { ... } catch (e) { ... } finally { ... }
catch 和 finally 都是可选的,但至少需要一个。throw expr 抛出任意值。

- Lexer: 新增 try, catch, finally, throw 关键字
- AST: Stmt::Try { body, catch_var, catch_body, finally_body } + Stmt::Throw(Expr)
- Parser: try/catch/finally/throw 语句解析,更新 synchronize()
- Opcode: Throw = 46,FunctionProto 新增 exception_handlers 表
- ExceptionHandler: { try_start, try_end, catch_ip, catch_slot, finally_ip }
- Compiler: compile_try 将 finally 在成功/异常两条路径各内联一次
- VM: unwind() 栈展开 + find_handler() 处理器搜索
- RuntimeError(除零、未定义变量等)在 try 块内自动转为可捕获异常
- LSP: 补全关键字列表追加

已知限制: return/break/continue 在 try-finally 内部不会先执行 finally

16 个新测试,全部 343 个测试通过。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-26 14:37:38 +08:00
0264408 deab894864 doc: 修正README中upvalue捕获层数限制的表述
实际机制是增量逐层建立,通过enclosing_locals合并链
理论上不限深度;仅一次性链创建受限于3层穿透

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-26 10:49:41 +08:00
0264408 9f75ec68ee doc: 更新README,反映VM-only架构
- 移除树遍历解释器相关描述
- 移除 --vm 命令行标志
- 更新架构图和模块表,添加 runtime/ 模块
- 更新关键设计决策

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-26 10:45:50 +08:00
0264408 fa6ea512b3 refactor: VM-only architecture — remove tree-walking interpreter
Eliminate dual-engine architecture (Interpreter + Vm) in favor of
a single bytecode VM executor.

- Extract shared types (Value, FunctionProto, Closure, UpvalueObj,
  Runtime trait, NativeFn) into new  module
- Consolidate builtin registration in  — single
   used by VM, eliminates 40-line duplicate
- Delete tree-walker: exec.rs, eval.rs, interpreter.rs, env.rs, module.rs
- Change Value::Function(Rc<Function>) → Value::Function(Rc<Closure>)
  eliminating the closures HashMap pointer-key hack
- Fix VM semantic gaps found during migration:
  * Structural equality for Object/Array in is_equal
  * CompoundAssignGlobal opcode (global compound assigns were broken)
  * 9 string methods added to VM get_property
  * ForInInit error on non-iterable values
- Switch run_file/run_repl to VM; remove --vm CLI flag
- Move 327 tests from interpreter/ to vm/ — all pass

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-26 10:31:32 +08:00
0264408 abe0844816 fix: CompoundAssignUpvalue支持 + const变量赋值检查
- 新增 CompoundAssignUpvalue 字节码,闭包中 x+=5 不再报错
- Local/Upvalue 上的 is_const 编译期检查
- globals 改为 HashMap<String, (Value, bool)> 追踪 const,StoreGlobal 运行时拒绝
- DefineGlobal 指令新增 1 字节 mutable 标志
- 更新 README:双执行模式、for-in、require()、VM 模块说明

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-26 09:42:38 +08:00
elmma 7b9bded5ee chore: 消除所有编译器警告
- 移除unused方法: frame(), get_constant(), emit_i16_placeholder
- 移除LoopContext未使用字段: start_ip, scope_depth
- Vm字段改为私有: frames, open_upvalues
- is_const字段添加#[allow(dead_code)]
- 测试: 327/327 通过 | 0 warnings
2026-06-26 00:09:02 +08:00
elmma 9a193fd7ca feat: VM版require()模块加载支持
- Vm实现Runtime::require: lex→parse→compile→execute
- 模块隔离执行: 独立Vm实例,共享builtins
- Closure传递: 模块导出的函数闭包注册到父Vm
- 循环require支持: 缓存占位符机制

测试: 327/327 通过
验证: use_math.ast (add=42, mul=42, add(mul(2,3),1)=7)
2026-06-25 23:47:33 +08:00
elmma ddee395f52 feat: 传递式upvalue — 3层嵌套闭包支持
- upvalues改为Rc<RefCell<Vec<Upvalue>>>支持跨编译器共享
- resolve_upvalue: 通过grandparent_upvalues级联创建upvalue链
- compile_nested_function: 传递完整enclosing_locals链 + grandparent_upvalues
- Upvalue添加name字段用于传递式查找

测试: 327/327 通过 | Benchmark: 18/18 全部通过
2026-06-25 23:41:36 +08:00
elmma 9c9a17b149 feat: 重构for-in为预计算+隐藏局部变量模式
ForInInit: 改为预计算所有迭代元素为数组
ForInNext: 接收items_slot+idx_slot操作数,从局部变量读取
编译器: for-in迭代器使用隐藏局部变量(items, idx),隔离于value stack
修复: for-in在函数调用嵌套场景下栈对齐问题
修复: break路径在for-in中正确弹出循环变量
修复: ForInNext退出时push Nil以保持栈平衡
修复: 跳转偏移计算(移除emit_loop_jump中错误的-3)

通过: 17/18 benchmark测试 (全部for-in相关测试通过)
已知限制: 3层嵌套闭包(10c)需传递式upvalue支持
2026-06-25 23:32:23 +08:00
elmma 299003a718 feat: VM函数调用、闭包、upvalue捕获支持
- 实现用户定义函数调用 (CallFrame创建、参数传递、返回)
- 实现闭包/upvalue捕获 (编译器解析、VM捕获、LoadUpvalue/StoreUpvalue)
- 修复栈管理: StoreLocal peek语义、局部变量槽位分配
- 修复跳转偏移计算 (移除错误的-3偏置)
- 修复对象字面量编译 (每字段后Pop)
- 修复函数声明栈泄漏 (DefineGlobal后Pop)
- 添加ForInNext值Pop (避免栈累积)
- 添加OpCode::from_u8对非连续值的支持

通过: 算术、循环、条件、数组、对象、字符串、递归、闭包、upvalue
部分通过: for-in迭代器 (基本工作,复杂嵌套场景待修复)
未实现: VM版require()、复合upvalue赋值

测试: 327/327 通过
2026-06-25 23:16:59 +08:00
elmma d854b22006 feat: 字节码VM基础设施 — 编译器、VM执行循环、Runtime trait
Phase 1-3: 基础VM架构
- 新增 Runtime trait: 抽象树遍历解释器和VM的共同接口
- NativeFn 改为接受 &mut dyn Runtime
- 重构所有内置函数使用新签名
- vm/opcode.rs: 33个字节码指令 + 编码/解码辅助函数
- vm/compiler.rs: AST→字节码编译器,支持变量解析、跳转回填、作用域
- vm/vm.rs: 栈式VM执行循环,支持全局变量、原生函数调用
- lib.rs: 新增 run_file_vm() + --vm CLI标志
- 修复: 跳转偏移计算、对象字面量编译

工作特性: 算术、变量、while/for循环、条件、数组、对象、字符串
待完成: 用户定义函数调用、闭包/upvalue捕获、完整require支持

Release模式: 1M算术循环 VM 0.44s vs 树遍历 0.89s (2.0x加速)
2026-06-25 01:08:49 +08:00
30 changed files with 3229 additions and 1296 deletions
+23 -6
View File
@@ -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 层穿透(本地→父级→祖级),更深层级通过逐层编译自然建立
## 许可证
+13
View File
@@ -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),
}
}
+1 -1
View File
@@ -130,7 +130,7 @@ pub enum LogicalOp {
Or,
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssignOp {
Equal, // =
PlusEqual, // +=
+11
View File
@@ -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 @@
//! 内置函数模块:按功能分组注册,便于扩展和维护。
mod core;
mod io;
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);
}
-50
View File
@@ -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
}
}
}
-624
View File
@@ -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 |_interp: &mut Self, 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 |_interp: &mut Self, _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 |_interp: &mut Self, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::upper(_interp, all_args)
})))
}
"lower" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::lower(_interp, all_args)
})))
}
"trim" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::trim(_interp, all_args)
})))
}
"substring" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::substring(_interp, all_args)
})))
}
"replace" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::replace(_interp, all_args)
})))
}
"contains" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::contains(_interp, all_args)
})))
}
"starts_with" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::starts_with(_interp, all_args)
})))
}
"ends_with" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::ends_with(_interp, all_args)
})))
}
"split" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::split(_interp, 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, args),
Value::Function(f) => self.call_user_function(f, args),
_ => Err(RuntimeError::RuntimeError {
message: "Attempt to call non-function".to_string(),
token: None,
}),
}
}
fn call_user_function(&mut self, f: Rc<Function>, args: Vec<Value>) -> Result<Value, RuntimeError> {
let env = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&f.env)))));
if let Some(name) = &f.name {
env.borrow_mut().define(name.clone(), Value::Function(Rc::clone(&f)), true);
}
for (i, param) in f.params.iter().enumerate() {
let val = args.get(i).cloned().unwrap_or(Value::Nil);
env.borrow_mut().define(param.clone(), val, true);
}
let previous = Rc::clone(&self.env);
self.env = env;
let mut ret = Value::Nil;
for stmt in &f.body {
match self.execute(stmt.clone())? {
Signal::Return(val) => { ret = val; break; }
Signal::None => {}
Signal::Break | Signal::Continue => {
return Err(RuntimeError::RuntimeError {
message: "break/continue outside of loop".to_string(),
token: None,
});
}
}
}
self.env = previous;
Ok(ret)
}
// ========================================================================
// Helpers
// ========================================================================
pub fn is_truthy(&self, val: &Value) -> bool {
match val {
Value::Nil => false,
Value::Bool(b) => *b,
_ => true,
}
}
pub fn is_equal(&self, a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Nil, Value::Nil) => true,
(Value::Bool(x), Value::Bool(y)) => x == y,
(Value::Number(x), Value::Number(y)) => x == y,
(Value::String(x), Value::String(y)) => x == y,
(Value::Array(x), Value::Array(y)) => {
let x = x.borrow();
let y = y.borrow();
if x.len() != y.len() { return false; }
x.iter().zip(y.iter()).all(|(a, b)| self.is_equal(a, b))
}
(Value::Object(x), Value::Object(y)) => {
let x = x.borrow();
let y = y.borrow();
if x.len() != y.len() { return false; }
x.iter().all(|(k, v)| {
y.get(k).map_or(false, |yv| self.is_equal(v, yv))
})
}
(Value::Function(x), Value::Function(y)) => Rc::ptr_eq(x, y),
(Value::NativeFunction(x), Value::NativeFunction(y)) => Rc::ptr_eq(x, y),
_ => false,
}
}
pub fn apply_assign_op(&self, left: Value, right: Value, op: AssignOp) -> Result<Value, RuntimeError> {
match op {
AssignOp::Equal => Ok(right),
AssignOp::PlusEqual => {
if let Value::String(s) = &left {
Ok(Value::String(format!("{}{}", s, right)))
} else {
let left_num = self.as_number(&left)?;
let right_num = self.as_number(&right)?;
Ok(Value::Number(left_num + right_num))
}
}
AssignOp::MinusEqual => {
let left_num = self.as_number(&left)?;
let right_num = self.as_number(&right)?;
Ok(Value::Number(left_num - right_num))
}
AssignOp::StarEqual => {
let left_num = self.as_number(&left)?;
let right_num = self.as_number(&right)?;
Ok(Value::Number(left_num * right_num))
}
AssignOp::SlashEqual => {
let left_num = self.as_number(&left)?;
let right_num = self.as_number(&right)?;
Ok(Value::Number(left_num / right_num))
}
AssignOp::PercentEqual => {
let left_num = self.as_number(&left)?;
let right_num = self.as_number(&right)?;
Ok(Value::Number(left_num % right_num))
}
}
}
pub fn as_number(&self, val: &Value) -> Result<f64, RuntimeError> {
if let Value::Number(n) = val {
Ok(*n)
} else {
Err(RuntimeError::RuntimeError {
message: "Expected number".to_string(),
token: None,
})
}
}
pub fn as_array_index(&self, val: &Value) -> Result<usize, RuntimeError> {
if let Value::Number(n) = val {
if *n < 0.0 || n.fract() != 0.0 {
return Err(RuntimeError::RuntimeError {
message: format!("Index must be a non-negative integer, got {}", n),
token: None,
});
}
Ok(*n as usize)
} else {
Err(RuntimeError::RuntimeError {
message: "Index must be a number".to_string(),
token: None,
})
}
}
}
-192
View File
@@ -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))
}
}
}
-55
View File
@@ -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 {
/// 当前作用域(用户代码所在 envparent 指向 builtins_env
pub env: Rc<RefCell<Env>>,
/// 内置函数根作用域(parent = None,所有内置函数注册在此)
pub builtins_env: Rc<RefCell<Env>>,
/// 模块缓存:规范路径 → exports 对象
pub module_cache: RefCell<HashMap<String, Value>>,
/// 当前执行文件的目录,用于 require() 解析相对路径
pub current_dir: String,
}
impl Interpreter {
pub fn new() -> Self {
// 根层:仅包含内置函数
let builtins_env = Rc::new(RefCell::new(Env::new(None)));
super::builtins::register_all(&builtins_env);
// 用户层:parent 指向 builtins_env,用户定义的变量都在这层
let script_env = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&builtins_env)))));
Self {
env: script_env,
builtins_env,
module_cache: RefCell::new(HashMap::new()),
current_dir: std::env::current_dir()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| ".".to_string()),
}
}
/// 指定当前目录的构造器,用于 `run_file` 时设置脚本所在目录
pub fn with_current_dir(dir: String) -> Self {
let mut interp = Self::new();
interp.current_dir = dir;
interp
}
pub fn interpret(&mut self, statements: Vec<Stmt>) -> Result<(), RuntimeError> {
for stmt in statements {
self.execute(stmt)?;
}
Ok(())
}
}
#[cfg(test)]
#[path = "tests.rs"]
mod tests;
+3 -124
View File
@@ -1,124 +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;
pub type NativeFn = Rc<dyn Fn(&mut super::Interpreter, 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;
-140
View File
@@ -1,140 +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};
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::Path;
use std::rc::Rc;
/// require() 的内置函数实现
pub fn require_fn(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
// 1. 参数验证
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)")),
};
// 2. 路径解析
let resolved = resolve_path(&interp.current_dir, &path_str)?;
// 3. 缓存查找
if let Some(cached) = interp.module_cache.borrow().get(&resolved) {
return Ok(cached.clone());
}
// 4. 读取文件
let src = std::fs::read_to_string(&resolved)
.map_err(|e| runtime_error(format!(
"Module '{}' not found: {}", path_str, e)))?;
// 5. 词法分析
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])));
}
// 6. 语法分析
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])));
}
// 7. 创建隔离的模块 env(父级 = builtins_env,看不到调用者的变量)
let module_env = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&interp.builtins_env)))));
// 8. 在缓存中插入占位符(支持循环 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());
// 9. 保存调用者状态
let previous_env = Rc::clone(&interp.env);
let previous_dir = interp.current_dir.clone();
// 10. 切换到模块上下文
interp.env = module_env.clone();
interp.current_dir = module_dir(&resolved);
// 11. 执行模块
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(_)) => {
// 正常执行或顶层 return,继续
}
}
}
// 12. 恢复调用者状态
interp.env = previous_env;
interp.current_dir = previous_dir;
// 13. 收集 exports(模块 env 中的所有直接绑定)
for (name, (val, _mutable)) in module_env.borrow().values.clone() {
exports_map.borrow_mut().insert(name, val);
}
Ok(exports)
}
// ============================================================================
// Helpers
// ============================================================================
/// 解析模块路径:相对路径基于 current_dir,用 canonicalize 规范化
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,
}
}
+4
View File
@@ -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),
};
+4
View File
@@ -39,6 +39,10 @@ pub enum TokenKind {
True,
False,
Nil,
Try,
Catch,
Finally,
Throw,
EOF,
}
+26 -8
View File
@@ -1,13 +1,16 @@
pub mod lexer;
pub mod ast;
pub mod parser;
pub mod runtime;
pub mod interpreter;
pub mod vm;
pub mod error;
pub mod analysis;
use lexer::Lexer;
use parser::Parser;
use interpreter::Interpreter;
use vm::compiler::Compiler;
use vm::vm::Vm;
use error::RuntimeError;
use std::io::Write;
@@ -39,8 +42,16 @@ pub fn run_file(filename: &str, src: String) {
.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) {
let proto = match Compiler::compile(&stmts) {
Ok(p) => p,
Err(e) => {
eprintln!("Compile Error: {}", e);
std::process::exit(70);
}
};
let mut vm = Vm::with_current_dir(script_dir);
if let Err(e) = vm.run(std::rc::Rc::new(proto)) {
eprintln!("Error: {}", e);
std::process::exit(70);
}
@@ -49,9 +60,9 @@ pub fn run_file(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 {
@@ -75,8 +86,8 @@ pub fn run_repl() {
break;
}
":reset" => {
interpreter = Interpreter::new();
println!("Interpreter reset.");
vm = Vm::new();
println!("VM reset.");
continue;
}
_ => {}
@@ -95,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);
}
}
+40 -1
View File
@@ -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,9 +1,9 @@
//! 标准库:corelen, typeof, push, pop
use crate::error::RuntimeError;
use crate::interpreter::{Interpreter, Value};
use crate::runtime::{Runtime, Value};
pub fn len(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn len(_runtime: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "len() expects 1 argument".into(),
@@ -21,7 +21,7 @@ pub fn len(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runtime
}
}
pub fn typeof_fn(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn typeof_fn(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "typeof() expects 1 argument".into(),
@@ -41,7 +41,7 @@ pub fn typeof_fn(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, R
Ok(Value::String(s.into()))
}
pub fn push(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn push(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.len() < 2 {
return Err(RuntimeError::RuntimeError {
message: "push() expects 2 arguments (array, value)".into(),
@@ -61,7 +61,7 @@ pub fn push(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runtim
}
}
pub fn pop(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn pop(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "pop() expects 1 argument (array)".into(),
@@ -1,9 +1,9 @@
//! 标准库:ioprint, input
use crate::error::RuntimeError;
use crate::interpreter::{Interpreter, Value};
use crate::runtime::{Runtime, Value};
pub fn print(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn print(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
for arg in args.iter() {
print!("{}", arg);
}
@@ -11,7 +11,7 @@ pub fn print(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runti
Ok(Value::Nil)
}
pub fn input(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn input(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if let Some(prompt) = args.get(0) {
print!("{}", prompt);
std::io::Write::flush(&mut std::io::stdout()).unwrap();
+45
View File
@@ -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,9 +1,9 @@
//! 标准库:osclock
use crate::error::RuntimeError;
use crate::interpreter::{Interpreter, Value};
use crate::runtime::{Runtime, Value};
pub fn clock(_interp: &mut Interpreter, _args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn clock(_interp: &mut dyn Runtime, _args: Vec<Value>) -> Result<Value, RuntimeError> {
Ok(Value::Number(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
@@ -1,7 +1,7 @@
//! 标准库:stringsplit, trim, substring, replace, contains, upper, lower, starts_with, ends_with
use crate::error::RuntimeError;
use crate::interpreter::{Interpreter, Value};
use crate::runtime::{Runtime, Value};
use std::cell::RefCell;
use std::rc::Rc;
@@ -19,7 +19,7 @@ fn require_string(val: &Value, arg_name: &str) -> Result<String, RuntimeError> {
// split(str, delim) → array of substrings
// ============================================================================
pub fn split(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn split(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.len() < 2 {
return Err(RuntimeError::RuntimeError {
message: "split() expects 2 arguments (string, delimiter)".into(),
@@ -43,7 +43,7 @@ pub fn split(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runti
// trim(str) → string with leading/trailing whitespace removed
// ============================================================================
pub fn trim(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn trim(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "trim() expects 1 argument (string)".into(),
@@ -58,7 +58,7 @@ pub fn trim(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runtim
// substring(str, start, len?) → substring
// ============================================================================
pub fn substring(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn substring(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "substring() expects at least 2 arguments (string, start, len?)".into(),
@@ -81,7 +81,7 @@ pub fn substring(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, R
// replace(str, old, new) → string with all occurrences replaced
// ============================================================================
pub fn replace(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn replace(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.len() < 3 {
return Err(RuntimeError::RuntimeError {
message: "replace() expects 3 arguments (string, old, new)".into(),
@@ -106,7 +106,7 @@ pub fn replace(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Run
// contains(str, needle) → bool
// ============================================================================
pub fn contains(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn contains(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.len() < 2 {
return Err(RuntimeError::RuntimeError {
message: "contains() expects 2 arguments (string, needle)".into(),
@@ -122,7 +122,7 @@ pub fn contains(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Ru
// upper(str) → uppercase (ASCII)
// ============================================================================
pub fn upper(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn upper(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "upper() expects 1 argument (string)".into(),
@@ -137,7 +137,7 @@ pub fn upper(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runti
// lower(str) → lowercase (ASCII)
// ============================================================================
pub fn lower(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn lower(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "lower() expects 1 argument (string)".into(),
@@ -152,7 +152,7 @@ pub fn lower(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runti
// starts_with(str, prefix) → bool
// ============================================================================
pub fn starts_with(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn starts_with(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.len() < 2 {
return Err(RuntimeError::RuntimeError {
message: "starts_with() expects 2 arguments (string, prefix)".into(),
@@ -168,7 +168,7 @@ pub fn starts_with(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value,
// ends_with(str, suffix) → bool
// ============================================================================
pub fn ends_with(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn ends_with(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.len() < 2 {
return Err(RuntimeError::RuntimeError {
message: "ends_with() expects 2 arguments (string, suffix)".into(),
+203
View File
@@ -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)
}
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
pub mod opcode;
pub mod compiler;
pub mod vm;
+177
View File
@@ -0,0 +1,177 @@
//! Bytecode opcode definitions for the Aster VM.
//!
//! Each instruction is encoded as a 1-byte opcode tag followed by
//! variable-length operands (u8 for local slots, u16 for constant pool
//! indices, i16 for relative jump offsets).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum OpCode {
// --- Stack manipulation ---
Pop = 0,
Dup = 1,
// --- Constants (operand: u16 index into constant pool) ---
LoadConst = 2,
LoadNil = 3,
LoadTrue = 4,
LoadFalse = 5,
// --- 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,
StoreGlobal = 9,
DefineGlobal = 10,
// --- Property access (operand: u16 index into constant pool for name) ---
GetProperty = 11,
SetProperty = 12,
// --- Index access (operands on stack) ---
GetIndex = 13,
SetIndex = 14,
// --- Binary arithmetic ---
Add = 15,
Sub = 16,
Mul = 17,
Div = 18,
Mod = 19,
// --- Unary ---
Negate = 20,
Not = 21,
// --- Comparison ---
Equal = 22,
NotEqual = 23,
Greater = 24,
GreaterEqual = 25,
Less = 26,
LessEqual = 27,
// --- Control flow (operand: i16 relative jump offset) ---
Jump = 28,
JumpIfFalse = 29,
JumpIfTrue = 30,
PopJumpIfFalse = 31,
// --- Functions ---
Call = 32, // operand: u8 arg count
Return = 33,
Closure = 34, // operand: u16 proto idx, then N*(u8 is_local, u8 index)
// --- Object/Array construction ---
NewObject = 35,
NewArray = 36, // operand: u16 element count
// --- For-in support ---
ForInInit = 37,
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
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> {
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 = 47;
}
/// Compound assignment operator tags (used as operand byte after
/// CompoundAssignLocal/Prop/Index).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum CompoundOp {
PlusEqual = 0,
MinusEqual = 1,
StarEqual = 2,
SlashEqual = 3,
PercentEqual = 4,
}
impl CompoundOp {
pub fn from_u8(byte: u8) -> Option<Self> {
if byte <= 4 {
Some(unsafe { std::mem::transmute::<u8, CompoundOp>(byte) })
} else {
None
}
}
}
// ============================================================================
// Instruction encoding helpers (for compiler use)
// ============================================================================
/// Write a u8 operand after the opcode byte.
pub fn emit_u8(code: &mut Vec<u8>, op: OpCode, operand: u8) {
code.push(op as u8);
code.push(operand);
}
/// Write a u16 operand after the opcode byte (little-endian).
pub fn emit_u16(code: &mut Vec<u8>, op: OpCode, operand: u16) {
code.push(op as u8);
code.push((operand & 0xFF) as u8);
code.push(((operand >> 8) & 0xFF) as u8);
}
/// Write an i16 operand after the opcode byte (little-endian).
pub fn emit_i16(code: &mut Vec<u8>, op: OpCode, offset: i16) {
code.push(op as u8);
code.push((offset & 0xFF) as u8);
code.push(((offset >> 8) & 0xFF) as u8);
}
/// Write a standalone opcode byte.
pub fn emit_op(code: &mut Vec<u8>, op: OpCode) {
code.push(op as u8);
}
// ============================================================================
// Instruction decoding helpers (for VM use)
// ============================================================================
/// Read a u8 at position `ip + 1` (right after the opcode byte).
pub fn read_u8(code: &[u8], ip: usize) -> u8 {
code[ip + 1]
}
/// Read a u16 at position `ip + 1` (little-endian, right after the opcode byte).
pub fn read_u16(code: &[u8], ip: usize) -> u16 {
(code[ip + 1] as u16) | ((code[ip + 2] as u16) << 8)
}
/// Read an i16 at position `ip + 1` (little-endian, right after the opcode byte).
pub fn read_i16(code: &[u8], ip: usize) -> i16 {
((code[ip + 1] as u16) | ((code[ip + 2] as u16) << 8)) as i16
}
/// Size in bytes of an opcode with no operand.
pub const SIZE_OP: usize = 1;
/// Size in bytes of an opcode with a u8 operand.
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);
}
File diff suppressed because it is too large Load Diff
+1
View File
@@ -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] = &[
+2 -8
View File
@@ -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
View File
@@ -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)),
+1 -1
View File
@@ -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"
}
]
},