Files
aster/CLAUDE.md
T
2026-06-16 16:55:43 +08:00

3.4 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Build & Run

# Build
cargo build

# Run REPL (interactive)
cargo run

# Execute a script file
cargo run -- examples/script.ast

# Run tests (none yet; the project has no test/ directory)
cargo test

This is a pure-Rust project with no external dependencies (edition = "2024").

Architecture

Aster is a tree-walking interpreter for a custom dynamically-typed scripting language. The pipeline follows the classic compiler frontend pattern:

Source text → Lexer → Tokens → Parser → AST → Interpreter → Output

Module map

Module Responsibility
lexer/ Tokenizer. Lexer::tokenize() takes &str and returns (Vec<Token>, Vec<RuntimeError>). Tokens carry line/column for error reporting. Supports // single-line comments.
parser/ Recursive-descent parser (Pratt-style for expressions). Parser::parse() returns (Vec<Stmt>, Vec<RuntimeError>). Uses synchronize() for error recovery — skips to the next statement boundary on parse failure.
ast/ AST node definitions. Expr (in expr.rs) covers literals, variables, assignment, property access/indexing, unary/binary/logical ops, calls, lambdas, and object/array literals. Stmt (in stmt.rs) covers let, expression stmts, blocks, if/while/for, functions, return/break/continue.
interpreter/ Tree-walking evaluator. Interpreter holds an env: Rc<RefCell<Env>>. Env is a linked-list of scopes for lexical nesting. The Signal enum propagates Return/Break/Continue through the call stack. builtins/ registers native functions (print, input, clock) organized as io and os modules (also available as global functions).
error/ Three error variants: LexError (line+column), ParseError (token), RuntimeError (optional token). All implement Display and std::error::Error.
main.rs Two modes: run_file (parse-then-execute, exits with code 65 on lex/parse errors, 70 on runtime errors) and run_repl (per-line evaluation with :exit/:reset commands).

Key design decisions

  • No external crate dependencies. Everything is built on Rust's stdlib.
  • Rc<RefCell<>> for shared ownership. Env chains, Value::Object, Value::Array, and Value::Function all use Rc<RefCell<>> for interior mutability — matching the dynamic, garbage-collected feel without a GC.
  • Error-tolerant parsing. Both lexer and parser collect errors into a Vec rather than failing on the first error, so multiple diagnostics can be reported per run.
  • Closures capture by environment. Function structs store the Env at definition time; lambda expressions (fn(params) { body }) work the same way.
  • Compound assignment operators (+=, -=, *=, /=, %=) are parsed into Expr::Assign/Set/IndexSet with an AssignOp variant and evaluated by apply_assign_op.

Language syntax overview

Variables: let x = 42; Functions: fn add(a, b) { return a + b; } Closures: fn(x, y) { return x + y; } Control flow: if/while/for (C-style), break/continue, return Data: objects { key: val }, arrays [1, 2, 3], indexing arr[i], property access obj.prop Operators: + - * / %, == != < > <= >=, && || !, = += -= *= /= %= Builtins: print(...), input(prompt?), os.clock() (also clock() directly)