diff --git a/README.md b/README.md index e2c22a7..2a83313 100644 --- a/README.md +++ b/README.md @@ -1,105 +1,167 @@ # Aster -用 Rust 实现的脚本语言解释器。 +Aster 是一门使用 Rust 编写的动态类型脚本语言解释器,基于树遍历(tree-walker)实现。无外部依赖,纯标准库。 -## 项目结构 - -``` -aster/ -├── src/ -│ ├── lexer/ # 词法分析器 -│ ├── ast/ # 抽象语法树 -│ ├── parser/ # 语法分析器 -│ ├── interpreter/# 解释器 -│ ├── error/ # 错误处理 -│ └── main.rs -├── script.ast # 示例脚本 -└── Cargo.toml -``` - -## 运行方式 +## 快速开始 ```bash -# 运行脚本文件 -cargo run -- script.ast +# 构建 +cargo build -# 从标准输入读取 +# 启动 REPL(交互式) cargo run + +# 执行脚本文件 +cargo run -- examples/script.ast + +# 运行测试 +cargo test ``` ## 语言特性 -### 数据类型 - -- 数字(`Number`) -- 字符串(`String`) -- 布尔值(`true` / `false`) -- 空值(`nil`) -- 对象(`Object`)——以字典形式保存键值对 - ### 变量与赋值 -```rust -let x = 42; +```js +let x = 42; // 可变绑定 +const y = 100; // 不可变绑定(不能重新赋值) let name = "aster"; +x = x + 1; +x += 2; // 复合赋值:+= -= *= /= %= +// y = 200; // ✗ 运行时错误:不能对 const 重新赋值 ``` -### 控制流 +`const` 阻止对绑定的重新赋值,但不阻止对象属性或数组元素的修改。 -- **条件分支**:`if` / `else` -- **循环**:`while` +### 数据类型 -### 函数 - -```rust -fn fib(n) { - if (n <= 1) { - return n - } - return fib(n - 1) + fib(n - 2) -} - -print(fib(10)) +```js +let n = 3.14; // 数字(f64) +let s = "hello\nworld"; // 字符串(支持 \n \t \r \" \\ 转义) +let b = true; // 布尔值 +let empty = nil; // nil +let arr = [1, 2, 3]; // 数组 +let obj = { name: "aster", v:1 }; // 对象 ``` ### 运算符 -- 算术:`+` `-` `*` `/` -- 比较:`<` `<=` `>` `>=` `==` `!=` -- 逻辑:`&&` `||` -- 一元:`!` `-` - -### 对象与属性 - -使用花括号即可声明对象字面量,并通过 `.` 访问或更新属性: - -```rust -let user = { name: "aster", version: 1 }; -print(user.name); -user.version = user.version + 1; -print(user.version); +``` +算术: + - * / % +比较: == != < > <= >= +逻辑: && || ! ``` -对象可以嵌套,也可以与命名空间组合使用(例如 `io.print`、`os.clock`)。 +- `+` 同时支持数字加法和字符串拼接 +- `==` 和 `!=` 对数组和对象进行深度比较 +- `&&` 和 `||` 短路求值 +- 除零和取模零会产生运行时错误 -### 内置函数与命名空间 +### 控制流 -- `io.print(value)` / `print(value)`:打印值(为了兼容,`print` 仍暴露在全局命名空间) -- `io.input()` / `input()`:读取一行输入 -- `os.clock()`:返回当前时间戳(秒) +```js +// if / else +if (x > 0) { + print("positive"); +} else { + print("non-positive"); +} -## 构建 +// while 循环 +let i = 0; +while (i < 10) { + print(i); + i = i + 1; +} -```bash -cargo build --release +// for 循环(C 风格) +for (let i = 0; i < 10; i = i + 1) { + print(i); +} + +// break / continue +while (true) { + if (condition) { break; } + if (skip) { continue; } +} ``` -构建产物位于 `target/release/aster`,可直接执行: +### 函数与闭包 -```bash -./target/release/aster script.ast +```js +// 函数声明 +fn add(a, b) { + return a + b; +} + +// 递归 +fn fib(n) { + if (n <= 1) { return n; } + return fib(n - 1) + fib(n - 2); +} + +// 闭包 +fn make_adder(x) { + return fn(y) { return x + y; }; +} +let add5 = make_adder(5); +print(add5(10)); // 15 ``` -## 依赖 +### 数组与对象 -无外部依赖,仅使用 Rust 标准库。 +```js +let arr = [10, 20, 30]; +print(arr[0]); // 索引访问 +arr[1] = 99; // 索引赋值 +arr[0] += 5; // 复合索引赋值 + +let obj = { name: "aster", version: 1 }; +print(obj.name); // 属性访问 +obj.version = 2; // 属性赋值 +``` + +### 内置函数 + +```js +// I/O +print("hello", 42); // 打印,自动换行 +let name = input("name: "); // 读取输入(可选提示符) +let name = io.input("name:"); // 模块化调用 + +// 时间 +let t = clock(); // Unix 时间戳(秒) +let t = os.clock(); // 模块化调用 +``` + +## REPL 命令 + +| 命令 | 说明 | +|------|------| +| `:exit` | 退出 REPL | +| `:reset` | 重置解释器状态(清除所有变量和函数) | + +## 架构 + +``` +源码文本 → Lexer → Tokens → Parser → AST → Interpreter → 输出 +``` + +| 模块 | 职责 | +|------|------| +| `lexer/` | 词法分析。`Lexer::tokenize()` 将源码转为 `(Vec, Vec)`,Token 记录行列号用于错误报告,支持 `//` 单行注释 | +| `parser/` | 递归下降 + Pratt 解析器。`Parser::parse()` 返回 `(Vec, Vec)`,遇到语法错误通过 `synchronize()` 跳过至下一条语句边界继续解析 | +| `ast/` | AST 节点定义。`Expr`(表达式)涵盖字面量、变量、赋值、属性/索引访问、一元/二元/逻辑运算、函数调用、lambda 和对象/数组字面量。`Stmt`(语句)涵盖 let、表达式语句、块、if/while/for、函数、return/break/continue | +| `interpreter/` | 树遍历求值器。`Env` 是基于 `Rc>` 的链式作用域。`Signal` 枚举通过调用栈传播 `Return`/`Break`/`Continue`。`builtins/` 按 `io` 和 `os` 模块组织原生函数,同时注册为全局函数以方便使用 | +| `error/` | 三种错误:`LexError`(行列号)、`ParseError`(Token)、`RuntimeError`(可选 Token) | + +### 关键设计决策 + +- **零外部依赖** — 全部基于 Rust 标准库构建 +- **错误容忍解析** — 词法分析器和解析器均将错误收集到 `Vec` 中,单次运行可报告多个诊断信息,而非在第一个错误处就中止 +- **`Rc>` 共享所有权** — 用于 `Env` 链、`Value::Object`、`Value::Array` 和 `Value::Function`,提供动态可变语义 +- **词法作用域闭包** — `Function` 在定义时捕获 `Env`,lambda 表达式同理 + +## 许可证 + +MIT diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index bd249da..17f54f3 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -2,10 +2,11 @@ use crate::ast::expr::Expr; #[derive(Debug, Clone)] pub enum Stmt { - /// let x = expr; + /// let x = expr; or const x = expr; Let { name: String, initializer: Expr, + mutable: bool, // true for let, false for const }, /// 表达式语句:expr; diff --git a/src/interpreter/builtins/mod.rs b/src/interpreter/builtins/mod.rs index 018f913..66f3b79 100644 --- a/src/interpreter/builtins/mod.rs +++ b/src/interpreter/builtins/mod.rs @@ -16,15 +16,15 @@ pub fn register_all(env: &Rc>) { let mut io = HashMap::new(); io.insert("print".into(), Value::NativeFunction(io::print)); io.insert("input".into(), Value::NativeFunction(io::input)); - e.define("io".into(), Value::Object(Rc::new(RefCell::new(io)))); + 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(io::print)); - e.define("input".into(), Value::NativeFunction(io::input)); + e.define("print".into(), Value::NativeFunction(io::print), true); + e.define("input".into(), Value::NativeFunction(io::input), true); // os let mut os = HashMap::new(); os.insert("clock".into(), Value::NativeFunction(os::clock)); - e.define("os".into(), Value::Object(Rc::new(RefCell::new(os)))); + 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(os::clock)); + e.define("clock".into(), Value::NativeFunction(os::clock), true); } diff --git a/src/interpreter/env.rs b/src/interpreter/env.rs index 666c77d..194da3c 100644 --- a/src/interpreter/env.rs +++ b/src/interpreter/env.rs @@ -5,7 +5,8 @@ use std::cell::RefCell; #[derive(Debug)] pub struct Env { - pub values: HashMap, + /// (value, is_mutable) — `is_mutable` is true for `let`, false for `const`. + pub values: HashMap, pub parent: Option>>, } @@ -17,23 +18,28 @@ impl Env { } } - pub fn define(&mut self, name: String, val: Value) { - self.values.insert(name, val); + pub fn define(&mut self, name: String, val: Value, mutable: bool) { + self.values.insert(name, (val, mutable)); } - pub fn assign(&mut self, name: &str, val: Value) -> bool { + /// 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 { + 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 + self.values.insert(name.to_string(), (val, true)); + Ok(true) } else if let Some(parent) = &self.parent { parent.borrow_mut().assign(name, val) } else { - false + Ok(false) } } pub fn get(&self, name: &str) -> Option { - if let Some(val) = self.values.get(name) { + if let Some((val, _)) = self.values.get(name) { Some(val.clone()) } else if let Some(parent) = &self.parent { parent.borrow().get(name) diff --git a/src/interpreter/interpreter.rs b/src/interpreter/interpreter.rs index 3fb090c..ba607ee 100644 --- a/src/interpreter/interpreter.rs +++ b/src/interpreter/interpreter.rs @@ -26,9 +26,9 @@ impl Interpreter { fn execute(&mut self, stmt: Stmt) -> Result { match stmt { - Stmt::Let { name, initializer } => { + Stmt::Let { name, initializer, mutable } => { let val = self.evaluate(initializer)?; - self.env.borrow_mut().define(name, val); + self.env.borrow_mut().define(name, val, mutable); Ok(Signal::None) } Stmt::ExprStmt(expr) => { @@ -109,7 +109,7 @@ impl Interpreter { env: Rc::clone(&self.env), name: Some(name.clone()), })); - self.env.borrow_mut().define(name, func); + self.env.borrow_mut().define(name, func, true); Ok(Signal::None) } Stmt::Return(expr_opt) => { @@ -150,11 +150,20 @@ impl Interpreter { self.apply_assign_op(current.clone(), rhs, op)? } }; - if !self.env.borrow_mut().assign(&name, val.clone()) { - return Err(RuntimeError::RuntimeError { - message: format!("Undefined variable '{}'", name), - token: None, - }); + 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) } @@ -380,12 +389,12 @@ impl Interpreter { 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))); + 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); + env.borrow_mut().define(param.clone(), val, true); } let previous = Rc::clone(&self.env); @@ -784,6 +793,64 @@ mod tests { assert_num(&get_var(&interp, "x"), 1.0); } + // ========================================================================= + // Const + // ========================================================================= + + #[test] + fn eval_const_definition() { + let interp = run("const x = 42;"); + assert_num(&get_var(&interp, "x"), 42.0); + } + + #[test] + fn error_const_reassignment() { + let result = std::panic::catch_unwind(|| { + run("const x = 5; x = 10;"); + }); + assert!(result.is_err(), "Expected error for reassigning const"); + } + + #[test] + fn error_const_compound_assignment() { + let result = std::panic::catch_unwind(|| { + run("const x = 5; x += 1;"); + }); + assert!(result.is_err(), "Expected error for compound assignment to const"); + } + + #[test] + fn eval_const_shadowing() { + // Inner block can shadow outer const with its own binding + let interp = run("const x = 10; { let x = 20; }"); + assert_num(&get_var(&interp, "x"), 10.0); // Outer unchanged + } + + #[test] + fn eval_const_object_mutation() { + // const prevents rebinding, not property mutation + let interp = run("const obj = { a: 1 }; obj.a = 2;"); + match get_var(&interp, "obj") { + Value::Object(obj) => { + let obj = obj.borrow(); + assert_num(obj.get("a").unwrap(), 2.0); + } + v => panic!("Expected Object, got {:?}", v), + } + } + + #[test] + fn eval_const_array_mutation() { + // const prevents rebinding, not index mutation + let interp = run("const arr = [1, 2]; arr[0] = 99;"); + match get_var(&interp, "arr") { + Value::Array(arr) => { + assert_num(&arr.borrow()[0], 99.0); + } + v => panic!("Expected Array, got {:?}", v), + } + } + // ========================================================================= // Scope // ========================================================================= diff --git a/src/lexer/lexer.rs b/src/lexer/lexer.rs index 52d2136..4ae1bb9 100644 --- a/src/lexer/lexer.rs +++ b/src/lexer/lexer.rs @@ -315,6 +315,7 @@ impl Lexer { let kind = match s.as_str() { "let" => TokenKind::Let, + "const" => TokenKind::Const, "fn" => TokenKind::Fn, "if" => TokenKind::If, "else" => TokenKind::Else, @@ -532,9 +533,9 @@ mod tests { #[test] fn keywords() { - let kinds = tokenize("let fn if else while for break continue return true false nil"); + let kinds = tokenize("let const fn if else while for break continue return true false nil"); assert_eq!(kinds, vec![ - TokenKind::Let, TokenKind::Fn, + TokenKind::Let, TokenKind::Const, TokenKind::Fn, TokenKind::If, TokenKind::Else, TokenKind::While, TokenKind::For, TokenKind::Break, TokenKind::Continue, diff --git a/src/lexer/token.rs b/src/lexer/token.rs index 4fb196b..7c90a9a 100644 --- a/src/lexer/token.rs +++ b/src/lexer/token.rs @@ -25,6 +25,7 @@ pub enum TokenKind { // 关键字 Let, + Const, Fn, If, Else, diff --git a/src/parser/parser.rs b/src/parser/parser.rs index ed71857..9bf8b2e 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -31,7 +31,9 @@ impl Parser { fn declaration(&mut self) -> Result { if self.match_kind(&[TokenKind::Let]) { - self.let_declaration() + self.let_declaration(true) + } else if self.match_kind(&[TokenKind::Const]) { + self.let_declaration(false) } else if self.match_kind(&[TokenKind::Fn]) { self.fn_declaration() } else { @@ -59,13 +61,13 @@ impl Parser { } } - fn let_declaration(&mut self) -> Result { + fn let_declaration(&mut self, mutable: bool) -> Result { let name = self.consume_ident("Expected variable name.")?; self.consume(TokenKind::Equal, "Expected '=' after variable name.")?; let initializer = self.expression()?; self.match_kind(&[TokenKind::Semicolon]); // 分号可选 - Ok(Stmt::Let { name, initializer }) + Ok(Stmt::Let { name, initializer, mutable }) } fn fn_declaration(&mut self) -> Result { @@ -135,7 +137,7 @@ impl Parser { let initializer = if self.match_kind(&[TokenKind::Semicolon]) { None } else if self.match_kind(&[TokenKind::Let]) { - Some(self.let_declaration()?) + Some(self.let_declaration(true)?) } else { Some(self.expression_statement()?) }; @@ -565,6 +567,7 @@ impl Parser { match self.peek().kind { TokenKind::Let + | TokenKind::Const | TokenKind::Fn | TokenKind::If | TokenKind::While @@ -1106,7 +1109,7 @@ mod tests { #[test] fn parse_let_statement() { match parse_stmt("let x = 5;") { - Stmt::Let { name, initializer } => { + Stmt::Let { name, initializer, .. } => { assert_eq!(name, "x"); assert!(matches!(initializer, Expr::Literal(Literal::Number(5.0)))); } @@ -1123,6 +1126,17 @@ mod tests { } } + #[test] + fn parse_const_statement() { + match parse_stmt("const x = 5;") { + Stmt::Let { name, mutable, .. } => { + assert_eq!(name, "x"); + assert!(!mutable, "const should set mutable=false"); + } + e => panic!("Expected Let (const), got {:?}", e), + } + } + #[test] fn parse_expression_statement() { match parse_stmt("x;") {