javascript 0.3.0

A JavaScript engine implementation in Rust
Documentation
# Copilot Instructions


## What This Project Is


A complete JavaScript engine written in Rust — a tree-walking bytecode interpreter targeting ECMAScript 2024+. It supports async/await, generators, classes, modules, closures, BigInt, TypedArrays, Proxy/Reflect, WeakRef, and more. It runs in strict mode only. Test262 conformance is validated against 74,000+ test files.

## Build, Test, Lint


```bash
# Build

cargo build --all-features
cargo build -p js --release

# Full test suite

cargo test --all-features --tests

# Single Rust integration test

cargo test --test builtin_functions -- test_name
cargo test --test promise_tests -- promise_resolve

# Lint / format

cargo clippy --all-features --all-targets -- -D warnings
cargo fmt --all

# Benchmarks

cargo bench
cargo bench --bench promise_benchmarks
cargo bench --bench bigint_bench

# Run a single JS script

cargo run -p js -- js-scripts/promise_tests.js
cargo run -p js -- --module js-scripts/es6_module.mjs

# Run all JS self-tests, categorized PASS/FAIL/SKIP/TIMEOUT

bash ./test-vm.sh

# Test262 CI runner (Node.js)

node ci/runner.js --limit 10000 --focus "language/expressions/addition"
node ci/runner.js --limit 1 --focus "language/literals/string"
```

## Architecture


Source code flows through four stages:

```
Source → Lexer (tokenizer.rs) → Parser (parser.rs) → Compiler (compiler.rs) → Chunk (bytecode) → VM (vm.rs)
```

| File | Role |
|---|---|
| `src/core/tokenizer.rs` | Tokenizer; produces `Vec<TokenData>` |
| `src/core/parser.rs` | Recursive-descent parser; produces `Vec<Statement>` AST |
| `src/core/statement.rs` | AST node definitions (`StatementKind`, `Expr` enums) |
| `src/core/compiler.rs` | Compiles AST to stack-based bytecode (`Chunk`) |
| `src/core/opcode.rs` | 90 opcode definitions |
| `src/core/vm.rs` | Executes bytecode; ~35k lines; contains all builtins |
| `src/core/value.rs` | `Value<'gc>` enum — all JS value types |
| `src/core/mod.rs` | Module wiring + public entry points |
| `src/lib.rs` | Crate public API (`evaluate_script`, `Repl`, etc.) |
| `src/error.rs` | `JSError` / `EvalError` types |
| `src/repl.rs` | Persistent REPL environment |
| `src/js_bigint.rs` | BigInt implementation |
| `src/js_regexp.rs` | RegExp engine (uses `regress` crate) |
| `src/unicode.rs` | UTF-8 ↔ UTF-16 conversion |
| `js/src/main.rs` | CLI binary: file execution, eval, REPL |

### Entry Point


```rust
// Public API
evaluate_script(source, run_as_module, script_path) -> Result<String, JSError>
```

Internally: parse → create GC arena → compile → `VM::run()` → format result.

### Memory / GC


All JS objects live behind `Gc<'gc, GcCell<...>>` pointers managed by `gc-arena`. Every type that can be inside a `Gc` must implement `gc_arena::Collect`. The lifetime `'gc` threads through all types that hold GC'd values.

## Key Conventions


### Value Representation


```rust
pub enum Value<'gc> {
    Number(f64),           // IEEE-754; no integer type
    String(Vec<u16>),      // UTF-16 internally
    BigInt(Box<BigInt>),
    Boolean(bool),
    Undefined,
    Null,
    Object(...),         // IndexMap<String, Value> for properties
    Array(...),          // Vec<Value> + named props
    Function(...),       // bytecode function
    Closure(...),        // function + captured upvalues
    NativeFunction(FunctionID),  // builtin function by numeric ID
    Symbol(...),
    Property { value, getter, setter },  // internal property descriptor
}
```

Strings are `Vec<u16>` (UTF-16). Use `utf8_to_utf16` / `utf16_to_utf8` from `src/unicode.rs` to convert.

### Property Descriptors


Property descriptors are not a separate struct. They are encoded as `Value::Property { value, getter, setter }` and stored in the object's `IndexMap` alongside normal values. Getter/setter keys use the naming convention `__get_<key>` / `__set_<key>`.

### Opcode Handlers


Each opcode has a dedicated method on `VM`: `run_opcode_<name>`. The return type is `Result<OpcodeAction<'gc>, JSError>` where:

```rust
enum OpcodeAction<'gc> {
    Continue,          // VM loop advances ip and continues
    Exit(Value<'gc>),  // VM exits the current run loop with this value
}
```

Most handlers pop operands from `self.stack`, compute a result, push it back, and return `Ok(OpcodeAction::Continue)`. Handlers that terminate execution (e.g., `Return`, `Yield`) return `Ok(OpcodeAction::Exit(value))`.

### Builtin Functions


Builtins are identified by `FunctionID` (a `usize` constant, e.g., `BUILTIN_CONSOLE_LOG = 0`). All builtins are dispatched in `call_native_function` inside `vm.rs`. New builtins require:
1. A new `const BUILTIN_*: FunctionID` constant
2. A `Value::NativeFunction(BUILTIN_*)` entry set in `initialize_global_constructors`
3. A match arm in `call_native_function`

### Closures / Upvalues


The compiler tracks captured variables in `UpvalueInfo` entries and emits `MakeClosure` opcodes encoding the function index plus the list of captured variables. At runtime, captured variables live in `UpvalueCells` — shared mutable cells so mutations are reflected across all closures.

### Bytecode Encoding


`Chunk` stores a flat `Vec<u8>` code stream. Opcodes are emitted as single bytes; multi-byte operands follow immediately (e.g., `emit_u16` for constant table indices). The constant table (`chunk.constants: Vec<Value>`) holds literals referenced by index from the bytecode.

### Error Handling


All fallible operations return `Result<_, JSError>`. `JSError` carries kind (`JSErrorKind`), message, and optional source location. The `?` operator propagates errors up through the pipeline.

### Adding a New Opcode


1. Add variant to `Opcode` enum in `src/core/opcode.rs`
2. Add `compile_*` logic in `src/core/compiler.rs` to emit it
3. Add `run_opcode_*` handler in `src/core/vm.rs`
4. Add match arm in the main dispatch in `vm.rs`'s `run_inner` (or equivalent)

### Test Conventions


- **Rust integration tests** live in `tests/*.rs`; use the `assert_eval_eq!(js_source, expected)` macro which calls `evaluate_script` and compares the string result.
- **JS self-tests** live in `js-scripts/*.js`; they `throw` on failure or rely on exit code. Run one with `cargo run -p js -- js-scripts/<file>.js`.
- **Test262** is run via `node ci/runner.js`; feature probes in `ci/feature_probes/` auto-skip tests for unsupported features.