# CLAUDE.md

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

## Project Overview

dexpr is an embeddable expression evaluator and bytecode VM written in Rust. It parses expressions and simple scripts (`.dexpr` files), compiles to bytecode, and executes on a register-based virtual machine. Designed to be embedded in other projects as a rule engine, formula evaluator, or expression calculator.

## Common Commands

```bash
# Run tests
cargo test

# Run benchmarks
cargo bench --bench my_benchmark

# Run the main program (executes basic_long.dexpr by default)
cargo run --release

# Profile with samply
cargo build --profile profiling
samply record ./target/profiling/dexpr
```

## Architecture

The pipeline flows: **Parser → AST → Compiler → Bytecode → VM**

### Core Modules

- **`parser/`**: PEG-based parser (`grammar.rs`) that produces AST nodes
- **`ast/`**: Expression (`expr.rs`), statement (`stmt.rs`), and value (`value.rs`) types
- **`compiler.rs`**: Transforms AST into bytecode; single-pass, globals-only, register allocation
- **`bytecode.rs`**: `BytecodeWriter` for compilation, `BytecodeReader` for VM consumption
- **`opcodes.rs`**: Defines `OpCodeByte` enum and `Register` type
- **`vm/vm.rs`**: Register-based VM with global variable support; executes bytecode

### Key Types

- `Value` (ast/value.rs): Runtime values (Number using rust_decimal, String, Boolean, Object using indexmap, etc.)
- `Compiler`: Stateful compiler with register allocation
- `VM<'a>`: Bytecode interpreter with global variable support via `set_global`/`get_global`

### Typical Usage Pattern

```rust
use dexpr::{ast::value::Value, compiler::Compiler, parser, vm::VM};

let ast = parser::program(source_code)?;
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast)?;
let mut vm = VM::new(&bytecode);
vm.set_global("input", Value::Number(dec!(42)));
vm.register_function("getRate", |args| Ok(Value::Number(dec!(34.5))));
let result = vm.execute()?; // Returns last expression's value
// Or use globals: let output = vm.get_global("output");
```

## Language Features

The dexpr language supports:
- If/else conditionals with `if ... then ... else ... end`
- String methods (e.g., `.upper()`, `.lower()`, `.trim()`, `.trimStart()`, `.trimEnd()`, `.split()`, `.replace()`, `.contains()`, `.startsWith()`, `.endsWith()`, `.length`, `.charAt()`, `.substring()`)
- Arithmetic (`+`, `-`, `*`, `/`, `%`, `**`), comparison, and logical operators
- `in` operator for membership testing (`"finans" in categories`, `5 in numbers`, `"hello" in "hello world"`, `"key" in obj`)
- Compound assignments (`+=`, `-=`, `*=`, `/=`, `%=`)
- Built-in `log()` function for output and `rand(min, max)` for random integers
- External (host) function registration via `vm.register_function()`
- External (host) method registration via `vm.register_method()`
- Expression return value: `execute()` returns the last expression's value
- Line comments (`//`) and block comments (`/* */`)
- Lists: `NumberList` and `StringList` types with methods (`sum`, `avg`, `min`, `max`, `first`, `last`, `get`, `join`, `contains`, `indexOf`, `slice`, `reverse`, `sort`, `isEmpty`, etc.)
- Objects: `Object` type (provided externally via `set_global`) with property access (`obj.field`), nested access (`obj.a.b`), property assignment (`obj.field = value`), and methods (`keys()`, `values()`, `length()`, `contains(key)`, `get(key)`)
- Lists: `List` type for heterogeneous arrays (including array of objects), with methods (`length`, `isEmpty`, `first`, `last`, `get`, `contains`, `indexOf`, `slice`, `reverse`, `join`, `map(field)`, `filter(field, value?)`, `find(field, value?)`, `sort(field)`). Property projection: `kalemler.tutar` extracts field from each Object element, returning NumberList/StringList/List. `map("field")` also available as explicit alternative

## Detailed Module Documentation

**IMPORTANT:** Before making any changes to the codebase, read the relevant documentation files in the `docs/` folder to understand how that module works in detail. After making changes to any module, update the corresponding documentation file to keep it in sync.

- **[docs/architecture.md](docs/architecture.md)** — Overall architecture, pipeline, design decisions, dependencies
- **[docs/ast.md](docs/ast.md)** — AST module: Expr, Stmt, Value types, Span, serialization format
- **[docs/parser.md](docs/parser.md)** — Parser: PEG grammar rules, operator precedence, reserved keywords
- **[docs/opcodes.md](docs/opcodes.md)** — Opcodes: full instruction set with hex values and categories
- **[docs/bytecode.md](docs/bytecode.md)** — Bytecode: BytecodeWriter/Reader API, data format, disassembler
- **[docs/compiler.md](docs/compiler.md)** — Compiler: single-pass compilation, register allocation, label/jump system
- **[docs/vm.md](docs/vm.md)** — VM: execution loop, opcode handlers, error types, DebugInfo
- **[docs/language_info.md](docs/language_info.md)** — Language Info: editor metadata generation, JSON format, host integration
- **[docs/editor.md](docs/editor.md)** — Editor: CodeMirror 6 language support, Lezer grammar, type-aware autocomplete

**Rule:** Any code change that modifies the behavior, API, or structure of a module MUST be accompanied by an update to the corresponding `docs/` file. Documentation and code must always stay in sync.
