3.4 KiB
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.
Envchains,Value::Object,Value::Array, andValue::Functionall useRc<RefCell<>>for interior mutability — matching the dynamic, garbage-collected feel without a GC. - Error-tolerant parsing. Both lexer and parser collect errors into a
Vecrather than failing on the first error, so multiple diagnostics can be reported per run. - Closures capture by environment.
Functionstructs store theEnvat definition time; lambda expressions (fn(params) { body }) work the same way. - Compound assignment operators (
+=,-=,*=,/=,%=) are parsed intoExpr::Assign/Set/IndexSetwith anAssignOpvariant and evaluated byapply_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)