tsrun 0.1.19

A TypeScript interpreter designed for embedding in applications
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
# CLAUDE.md

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

## Project Overview

A minimal TypeScript runtime in Rust designed for embedding in applications. The primary use case is configuration files where users benefit from IDE autocompletion, type checking, and error highlighting.

**Key characteristics:**
- **TypeScript-native** - Supports enums, interfaces, type annotations, and generics (types are stripped at runtime, not checked)
- **Minimal footprint** - No Node.js dependency, designed for embedding
- **Register-based bytecode VM** - Efficient execution with ES modules and async/await

## Quick Reference

### Build & Test Commands

```bash
cargo build                              # Build the project
cargo build --features c-api             # Build with C FFI support
cargo build --release --features c-api   # Release build with C FFI (creates libtsrun.so)
timeout 20 cargo test                    # Run all tests (always use timeout!)
timeout 20 cargo test --test interpreter # Run interpreter integration tests
timeout 20 cargo test test_name          # Run specific test
timeout 20 cargo test -- --nocapture     # Show test output

# Parser generator tests
cargo test -p trampoline-parser          # Library tests
cargo test -p trampoline-parser-tests    # Generated parser integration tests

# WASM tests
wasm-pack test --node --features wasm --no-default-features  # Run native WASM tests
cd examples/wasm-playground && ./build.sh --test             # Build and run playground e2e tests
```

### Test Timeout = Infinite Loop

**If any test takes more than 20 seconds, it indicates an infinite loop in the parser.**

To debug:
1. Use binary search to find the failing test: `timeout 20 cargo test test_name`
2. Once found, the issue is likely in the grammar (`src/grammar.rs`)
3. Add a minimal reproduction test to `trampoline-parser-tests/`
4. Fix the issue in `trampoline-parser/` with proper loop detection

Common causes of infinite loops in trampoline-parser:
- Nullable loops: `zero_or_more()` or `one_or_more()` with a body that can match empty input
- Left recursion: Rule A references itself as its first element without consuming input
- Postfix operators that don't consume input properly

### Key Files

| File/Directory | Purpose |
|----------------|---------|
| `src/lib.rs` | Public API - `Interpreter`, `InterpreterConfig` |
| `src/api.rs` | High-level API for stepping execution |
| `src/lexer.rs` | Tokenizer |
| `src/parser.rs` | **REWRITE IN PROGRESS** - Non-recursive stack-based parser (see `my/new_parser.md`) |
| `src/ast.rs` | AST node types |
| `src/value.rs` | Runtime values, object model |
| `src/gc.rs` | Garbage collector, Guard system, Heap |
| `src/error.rs` | JsError types |
| `src/compiler/` | Bytecode compiler |
| `src/interpreter/` | VM and builtins |
| `src/ffi/` | C FFI module (feature-gated: `c-api`) |
| `src/wasm/mod.rs` | WASM API (feature-gated: `wasm`) |
| `tests/interpreter/` | Integration tests by feature |
| `trampoline-parser/` | Parser generator DSL for scannerless parsing |
| `trampoline-parser-tests/` | Integration tests for trampoline-parser |
| `src/grammar.rs` | TypeScript grammar definition using trampoline-parser |
| `examples/c-embedding/` | C API usage examples |
| `examples/wasm-playground/` | WASM browser/Node.js playground |
| `examples/go-wazero/` | Go embedding via WASM (wazero) |

## Development Rules

- **Always use the Edit tool** - never shell commands like `echo >>` to modify files
- **Always use Read/Glob/Grep tools** - never `cat`, `sed`, `awk`, `head`, `tail`, or `find` for file operations
- **Use haiku agents for bulk changes** - for repetitive edits across multiple files (renames, pattern replacements), spawn Task agents with `model: "haiku"` instead of using sed/awk
- **Use TypeScript annotations in tests** - types are stripped at runtime but tests should use proper syntax
- **No tech debt** - fix failing tests immediately, no TODO/FIXME for known bugs
- **Use TDD** - if a test fails because a feature isn't implemented, implement the feature
- **Never change failing test cases** - write simpler tests to verify current scope, keep original as goal
- **Keep regression tests** - when debugging reveals a bug, add a minimal test case that reproduces it (prefix with `// Regression:` comment). Never delete these tests even after fixing - they catch future regressions
- **Fix pre-existing bugs** - write a test, fix it, then continue with your feature
- **Proper fixes over workarounds** - make architectural changes if needed
- **Debug via tests** - use `cargo test test_name -- --nocapture` with `console.log()`, not ad-hoc scripts. NEVER create temporary .ts/.js files for debugging (no `cat <<EOF`, `echo >`, or Write tool for test files). Note: tsrun CLI does not support stdin or -e argument - always write proper tests in `tests/`. Keep useful debug tests in the appropriate module for future regression detection
- **Log unexpected findings** - if something doesn't match expectations during implementation, add a note to `my/observations.md` and continue working on the task

### TDD Workflow

1. Write failing test in `tests/parser.rs` or `tests/interpreter/`
2. Implement minimal code to pass
3. Refactor while keeping tests green
4. Run `cargo test && cargo fmt && cargo clippy --all-targets --all-features -- -D warnings` before committing

**Note:** The parser is currently being rewritten to be fully non-recursive (stack-based). See `my/new_parser.md` for the implementation plan.

## Code Safety

### Zero-Panic Policy

These patterns are **denied** via Clippy lints:

| Pattern | Alternative |
|---------|-------------|
| `.unwrap()` | `.ok_or_else()`, `if let`, `match` |
| `.expect()` | `.ok_or_else()` with descriptive error |
| `[index]` | `.get(index)` with error handling |
| `panic!()` | `Err(JsError::...)` |
| `unreachable!()` | `Err(JsError::internal_error(...))` |
| `todo!()` | Implement the feature or return error |
| `&str[start..end]` | `.get(start..end)` for safe slicing |

Test code is exempt via `clippy.toml`.

### Safe Access Patterns

```rust
// Function arguments
let first = args.first().cloned().unwrap_or(JsValue::Undefined);
let second = args.get(1).cloned().unwrap_or(JsValue::Undefined);

// Array/vector access
let elem = elements.get(i).ok_or_else(|| JsError::internal_error("index out of bounds"))?;
let rest = args.get(i..).unwrap_or_default().to_vec();

// String slicing
let slice = s.get(start..end).unwrap_or("");

// Option unwrapping
let value = opt.ok_or_else(|| JsError::internal_error("expected value"))?;
```

### Clone Conventions

Use `.cheap_clone()` for O(1) reference-counted clones:

| Type | Clone Cost | Method |
|------|-----------|--------|
| `Gc<JsObject>` | Cheap | `.cheap_clone()` |
| `JsString` | Cheap | `.cheap_clone()` |
| `Rc<T>` | Cheap | `.cheap_clone()` |
| `String`, `Vec<T>`, AST | Expensive | `.clone()` with comment |

## GC & Guards

### Overview

The `Guarded` struct wraps a `JsValue` with a `Guard` that keeps objects alive during GC:

```rust
pub struct Guarded {
    pub value: JsValue,
    pub guard: Option<Guard<JsObject>>,
}
```

The VM maintains a `register_guard` that keeps all register values alive. When returning from the VM, values are wrapped in `Guarded`.

### Object Creation API

Caller provides guard, method allocates from it:

```rust
let guard = self.heap.create_guard();
let obj = self.create_object(&guard);           // With prototype
let raw = self.create_object_raw(&guard);       // Without prototype
let arr = self.create_array_from(&guard, elements);
let func = self.create_native_fn(&guard, "name", native_fn, arity);

// Multiple objects can share one guard
let guard = self.heap.create_guard();
let obj1 = self.create_object(&guard);
let obj2 = self.create_object(&guard);
```

### Critical GC Rules

**1. Guard before allocate** - GC runs BEFORE allocation when threshold is reached:
```rust
// CORRECT
let guard = interp.heap.create_guard();
interp.guard_value_with(&guard, &input_value);  // Guard input FIRST
let obj = interp.create_object(&guard);         // Then allocate

// WRONG - input_value may be collected during allocation!
let obj = interp.create_object(&guard);
```

**2. Return Guarded when returning objects**:
```rust
// CORRECT
pub fn some_builtin(...) -> Result<Guarded, JsError> {
    let guard = interp.heap.create_guard();
    let arr = interp.create_array(&guard, elements);
    Ok(Guarded { value: JsValue::Object(arr), guard: Some(guard) })
}

// WRONG - guard dropped, object may be collected!
pub fn some_builtin(...) -> Result<JsValue, JsError> { ... }
```

**3. Guard scope in collect-then-store loops**:
```rust
// CORRECT - guards at outer scope
let mut all_guards: Vec<Guard<JsObject>> = Vec::new();
let mut methods: Vec<(String, Gc<JsObject>)> = Vec::new();

for item in items {
    let (func, guard) = create_function(...)?;
    if let Some(g) = guard { all_guards.push(g); }
    methods.push((name, func));
}
// Store methods - guards still alive
for (name, func) in methods {
    prototype.borrow_mut().set_property(name, JsValue::Object(func));
}

// WRONG - guards dropped each iteration, funcs may be GC'd before storage
```

**4. Never allocate temporary objects from root_guard** - they'll never be collected (memory leak).

### Aggressive Test Defaults

Common GC bugs caught: "X is not a function", missing array elements, undefined properties.

## Architecture

### Pipeline

```
Source → Lexer → Parser → AST → Compiler → Bytecode → BytecodeVM → RuntimeResult
                                              ┌──────────────────────────┼──────────────────────────┐
                                              ▼                          ▼                          ▼
                                         Complete                   NeedImports                 Suspended
```

### Register-Based VM

The VM uses registers instead of a stack:
- Fewer instructions (no push/pop overhead)
- Better cache locality
- State capture for suspension at await/yield

### Key Types

| Type | Description |
|------|-------------|
| `JsValue` | Enum: Undefined, Null, Boolean, Number, String, Object, Symbol |
| `Gc<JsObject>` | GC-managed object pointer |
| `JsString` | `Rc<str>` reference-counted string |
| `JsSymbol` | Symbol primitive with description |
| `Op` | Bytecode instruction (100+ variants) |
| `BytecodeChunk` | Compiled function with instructions + constants |
| `Register` | Virtual register index (u8, 0-255 per frame) |

### Runtime Result

```rust
pub enum RuntimeResult {
    Complete(RuntimeValue),              // Finished
    NeedImports(Vec<ImportRequest>),     // Need modules loaded
    Suspended { pending, cancelled },    // Waiting for orders
}
```

### Module Structure

**Compiler** (`src/compiler/`):
- `compile_stmt.rs` / `compile_expr.rs` - Statement/expression compilation
- `compile_pattern.rs` - Destructuring patterns
- `bytecode.rs` - Bytecode instruction definitions (Op enum)
- `builder.rs` - Bytecode builder with register allocation
- `hoist.rs` - Variable hoisting

**Interpreter** (`src/interpreter/`):
- `mod.rs` - Main interpreter, environment management
- `bytecode_vm.rs` - Register-based bytecode VM execution engine

**Builtins** (`src/interpreter/builtins/`):
- `array.rs`, `string.rs`, `number.rs`, `object.rs` - Core types
- `function.rs`, `math.rs`, `json.rs`, `date.rs` - Standard objects
- `regexp.rs`, `map.rs`, `set.rs`, `error.rs` - Other builtins
- `promise.rs`, `generator.rs` - Async primitives
- `proxy.rs` - Proxy and Reflect objects
- `symbol.rs`, `boolean.rs`, `console.rs` - Additional builtins
- `global.rs` - Global functions (parseInt, parseFloat, etc.)

**C FFI** (`src/ffi/`, feature-gated):
- `mod.rs` - Types, result structs, utility functions
- `context.rs` - Context lifecycle and step-based execution
- `value.rs` - Value creation, inspection, object/array operations
- `native.rs` - Native C function callback system
- `module.rs` - Module system (provide_module, exports)
- `order.rs` - Async order system (pending orders, fulfillment)

## Trampoline Parser

A DSL for generating scannerless, trampoline-based parsers. Uses iterative work stacks instead of recursion to prevent stack overflow on deeply nested input.

### Structure

| Location | Purpose |
|----------|---------|
| `trampoline-parser/src/lib.rs` | Public API (`Grammar`, `Assoc`) |
| `trampoline-parser/src/parser_dsl.rs` | DSL builder methods |
| `trampoline-parser/src/codegen.rs` | Rust code generator |
| `trampoline-parser/src/ir.rs` | Intermediate representation |
| `trampoline-parser-tests/` | Integration test crate |
| `src/grammar.rs` | TypeScript grammar using the DSL |

### Usage

```rust
use trampoline_parser::{Grammar, Assoc};

let grammar = Grammar::new()
    .rule("expr", |r| {
        r.pratt(r.parse("number"), |ops| {
            ops.infix("+", 1, Assoc::Left, "|l, r, _| Ok(binary(l, r))")
               .infix("*", 2, Assoc::Left, "|l, r, _| Ok(binary(l, r))")
        })
    })
    .rule("number", |r| r.capture(r.one_or_more(r.digit())))
    .build();

let code = grammar.generate();  // Generates Rust parser code
```

### Testing Flow

The `trampoline-parser-tests` crate tests the generated parsers:

```bash
cargo test -p trampoline-parser        # Library unit tests (10 tests)
cargo test -p trampoline-parser-tests  # Integration tests (54 tests)
```

Tests are organized by feature:
- `tests/combinators.rs` - Basic combinators (literal, sequence, choice, repetition)
- `tests/pratt.rs` - Pratt expression parsing (precedence, associativity, prefix ops)
- `tests/edge_cases.rs` - Deep nesting, long input, error handling

The test crate uses `build.rs` to generate parsers at compile time, then tests them:

```rust
// In build.rs - generates parser code
let grammar = Grammar::new()
    .rule("number", |r| r.capture(r.one_or_more(r.digit())))
    .build();
write_parser(out_path, "number_parser", &grammar.generate());

// In src/lib.rs - includes generated code
pub mod number_parser {
    include!(concat!(env!("OUT_DIR"), "/number_parser.rs"));
}

// In tests - uses the generated parser
let mut parser = number_parser::Parser::new("123");
let result = parser.parse().expect("should parse");
```

### Key Combinators

| Method | Description |
|--------|-------------|
| `lit("x")` | Match literal string |
| `digit()`, `alpha()` | Character classes |
| `sequence((a, b, c))` | Match in order |
| `choice((a, b))` | Try alternatives with backtracking |
| `zero_or_more(x)` | Match 0+ times |
| `one_or_more(x)` | Match 1+ times |
| `optional(x)` | Match 0 or 1 time |
| `capture(x)` | Capture matched text as `ParseResult::Text` |
| `parse("rule")` | Reference another rule |
| `pratt(operand, ops)` | Pratt parsing for expressions |
| `separated_by(item, sep)` | Comma-separated lists |

**Note:** When `choice()` has many alternatives (>12), use `vec![]` instead of tuples to avoid Rust's tuple dimension limits:
```rust
// Instead of nested choice() tuples:
r.choice((a, b, r.choice((c, d, r.choice((e, f))))))

// Use vec![]:
r.choice(vec![a, b, c, d, e, f])
```

### Pratt Parsing with Whitespace

Whitespace handling in Pratt parsing requires careful coordination between operators and operands. See `trampoline-parser-tests/grammars/lua_expr.rs` for a complete example.

**Pattern:**
1. Infix operators need **leading whitespace** in their patterns
2. The operand rule needs **leading whitespace** to consume ws after operators

```rust
// Helper for infix operators with leading ws
fn ws_infix(r: &RuleBuilder, op: &str) -> Combinator {
    r.sequence((r.parse("ws"), r.lit(op)))
}

// Expression rule with Pratt parsing
.rule("expr", |r| {
    r.pratt(r.parse("primary"), |ops| {
        ops.infix(ws_infix(r, "+"), 1, Assoc::Left, "|l, r, _| Ok(binary(l, r))")
           .infix(ws_infix(r, "*"), 2, Assoc::Left, "|l, r, _| Ok(binary(l, r))")
    })
})

// Primary must consume leading ws (for ws between operator and right operand)
.rule("primary", |r| {
    r.sequence((r.parse("ws"), r.parse("primary_inner")))
        .ast("|r, _| { if let ParseResult::List(mut items) = r { Ok(items.pop().unwrap_or(ParseResult::None)) } else { Ok(r) } }")
})
```

**Why:** After parsing left operand (which consumes trailing ws), the parser position is at potential operator. Infix pattern `ws + "+"` matches leading ws then operator. Then right operand's `ws` consumes space between operator and right operand.

### Sparse Arrays

Arrays with holes like `[1, , 3]` require custom handling. `separated_by_trailing` doesn't work because it expects non-empty items between separators.

```rust
// WRONG - fails on [1, , 3]
r.separated_by_trailing(r.parse("element"), op(r, ","))

// CORRECT - handles elisions
r.sequence((
    op(r, "["),
    r.optional(r.sequence((
        r.optional(r.parse("element")),  // First element (may be elided)
        r.zero_or_more(r.sequence((
            op(r, ","),
            r.optional(r.parse("element")),  // Subsequent elements (may be elided)
        ))),
    ))),
    op(r, "]"),
))
```

## Implementation Patterns

### Adding Built-in Methods

1. **Write test** in `tests/interpreter/<type>.rs`:
```rust
#[test]
fn test_array_mymethod() {
    assert_eq!(eval("[1,2,3].myMethod()"), JsValue::Number(expected));
}
```

2. **Implement** in `src/interpreter/builtins/<type>.rs`:
```rust
pub fn array_my_method(interp: &mut Interpreter, this: JsValue, args: Vec<JsValue>) -> Result<JsValue, JsError> {
    let JsValue::Object(arr) = this else {
        return Err(JsError::type_error("Array.prototype.myMethod called on non-object"));
    };
    // Implementation
    Ok(result)
}
```

3. **Register** in `create_*_prototype()`:
```rust
let fn_obj = create_native_fn(&guard, "myMethod", array_my_method, 1);
p.set_property(PropertyKey::from("myMethod"), JsValue::Object(fn_obj));
```

### Common Patterns

```rust
// Get array length
let length = match &arr.borrow().exotic {
    ExoticObject::Array { length } => *length,
    _ => return Err(JsError::type_error("Not an array")),
};

// Update array length (must update both!)
if let ExoticObject::Array { ref mut length } = arr_ref.exotic {
    *length = new_length;
}
arr_ref.set_property(PropertyKey::from("length"), JsValue::Number(new_length as f64));

// Call a callback
let result = interp.call_function(
    callback.clone(),
    this_arg.clone(),
    vec![elem, JsValue::Number(index as f64), this.clone()],
)?;
```

### Prototype Chain

- Objects → `object_prototype` (hasOwnProperty, toString)
- Arrays → `array_prototype``object_prototype`
- Strings → `string_prototype` (looked up in evaluate_member)
- Numbers → `number_prototype` (looked up in evaluate_member)

## Testing

### Test Organization

| Location | Contents |
|----------|----------|
| `tests/interpreter/*.rs` | Integration tests by feature |
| `tests/compiler.rs` | Compiler integration tests |
| `tests/parser.rs` | Parser integration tests |
| `tests/lexer.rs` | Lexer integration tests |
| `src/value.rs` (bottom) | Value type unit tests |

Each test file uses the shared `eval()` helper:
```rust
use super::eval;
assert_eq!(eval("1 + 2"), JsValue::Number(3.0));
```

### Test262 Conformance

```bash
git submodule update --init --depth 1
cargo build --release --bin test262-runner
./target/release/test262-runner --strict-only language/types
```

The interpreter runs all code in strict mode - use `--strict-only` for meaningful results.

### TypeScript Features

**Supported:**
- Type annotations, interfaces, type aliases → parsed but stripped at runtime
- `enum` declarations → compile to object literals with reverse mappings
- Generic functions and classes → type parameters parsed and stripped
- Type assertions (`x as T`, `<T>x`) → evaluate to just the expression
- Parameter properties (`constructor(public x: number)`) → desugared to assignments
- Optional parameters (`x?: number`) and default values

**Also supported:**
- Decorators (class, method, property, parameter)
- Namespaces
- `eval()` for dynamic code evaluation

**Not supported:**
- Type checking (no type errors at runtime)

## C FFI

The interpreter can be embedded in C/C++ applications via a C API (feature-gated behind `c-api`).

### Building

```bash
cargo build --release --features c-api
# Output: target/release/libtsrun.so (Linux), .dylib (macOS), .dll (Windows)
```

### Key Concepts

- **Opaque handles**: `TsRunContext*`, `TsRunValue*` - all types are opaque pointers
- **Step-based execution**: `tsrun_prepare()``tsrun_run()` loop handling `NeedImports`/`Suspended`
- **Native callbacks**: C functions callable from JS via `tsrun_native_function()`
- **Order system**: Async operations via `tsrun_create_pending_order()` + `tsrun_fulfill_orders()`

### Examples

See `examples/c-embedding/` for working examples:
- `basic.c` - Value creation, objects, arrays, JSON
- `native_functions.c` - C callbacks, stateful functions, error handling
- `module_loading.c` - ES module loading with virtual filesystem
- `async_orders.c` - Async operations via pending orders

### Building and Running C Examples

Use the Makefile in `examples/c-embedding/`:

```bash
cd examples/c-embedding
make lib        # Build the Rust library (release)
make all        # Build all C examples
make run-all    # Run all examples
make run-basic  # Run a specific example
make clean      # Remove built examples
```

### Header

The C header is at `examples/c-embedding/tsrun.h`. Key functions:

```c
// Lifecycle
TsRunContext* tsrun_new(void);
void tsrun_free(TsRunContext* ctx);

// Execution
TsRunResult tsrun_prepare(TsRunContext* ctx, const char* code, const char* path);
TsRunStepResult tsrun_run(TsRunContext* ctx);

// Values
TsRunValue* tsrun_number(TsRunContext* ctx, double n);
TsRunValue* tsrun_string(TsRunContext* ctx, const char* s);
TsRunValueResult tsrun_get(TsRunContext* ctx, TsRunValue* obj, const char* key);

// Native functions
TsRunValueResult tsrun_native_function(TsRunContext* ctx, const char* name,
                                        TsRunNativeFn func, size_t arity, void* userdata);

// Async orders
TsRunValueResult tsrun_create_pending_order(TsRunContext* ctx, TsRunValue* payload,
                                             TsRunOrderId* order_id_out);
TsRunResult tsrun_fulfill_orders(TsRunContext* ctx, const TsRunOrderResponse* responses,
                                  size_t count);
```

## WASM

The interpreter compiles to WebAssembly with a C-style FFI for use across multiple runtimes: browsers, Node.js, Go (via wazero), Rust (via wasmtime), and others.

### Building

```bash
cd examples/wasm-playground
./build.sh              # Build WASM module and copy to site/playground/pkg
./build.sh --test       # Build and run e2e tests (uses Puppeteer)
```

### Files

| Location | Purpose |
|----------|---------|
| `examples/wasm-playground/` | Browser/Node.js playground with HTML, JS, and build scripts |
| `examples/wasm-playground/pkg/` | Built WASM output (`tsrun.wasm`, `tsrun.js`) |
| `examples/go-wazero/` | Go embedding via wazero runtime |
| `site/playground/` | Copy of playground for website (synced by build.sh) |
| `src/wasm/mod.rs` | WASM API with C-style FFI exports |
| `src/platform/wasm_impl.rs` | WASM-specific platform code |

### Go Embedding (wazero)

The `examples/go-wazero/` directory provides a Go wrapper using the wazero runtime:

```bash
cd examples/go-wazero
./build.sh              # Build the WASM module
go run ./basic          # Run basic example
go run ./async          # Run async orders example
go run ./modules        # Run ES modules example
go run ./native         # Run native functions example
```

```go
import "github.com/example/tsrun-go/tsrun"

ctx := context.Background()
rt, _ := tsrun.New(ctx, tsrun.ConsoleOption(func(level tsrun.ConsoleLevel, msg string) {
    fmt.Printf("[%s] %s\n", level, msg)
}))
defer rt.Close(ctx)

interp, _ := rt.NewContext(ctx)
defer interp.Free(ctx)

interp.Prepare(ctx, `console.log("Hello from Go!")`, "/main.ts")
result, _ := interp.Run(ctx)
```

### Browser/JavaScript API

The WASM module exposes a step-based execution API where JavaScript controls the execution loop:

```javascript
import init, { TsRunner, STEP_CONTINUE, STEP_COMPLETE, STEP_ERROR, STEP_SUSPENDED } from './pkg/tsrun.js';

await init();
const runner = new TsRunner();

// Load constants (they're functions that return values)
const StepStatus = {
    CONTINUE: STEP_CONTINUE(),
    COMPLETE: STEP_COMPLETE(),
    NEED_IMPORTS: STEP_NEED_IMPORTS(),
    SUSPENDED: STEP_SUSPENDED(),
    DONE: STEP_DONE(),
    ERROR: STEP_ERROR()
};

// Prepare code
const prepResult = runner.prepare(code, 'script.ts');
if (prepResult.status === StepStatus.ERROR) {
    console.error(prepResult.error);
    return;
}

// Main execution loop
while (true) {
    const result = runner.step();

    // Display console output from this step
    for (const entry of result.console_output) {
        console.log(`[${entry.level}] ${entry.message}`);
    }

    switch (result.status) {
        case StepStatus.CONTINUE:
            continue;
        case StepStatus.COMPLETE:
            console.log('Result:', result.value);
            return;
        case StepStatus.DONE:
            return;
        case StepStatus.ERROR:
            console.error(result.error);
            return;
        case StepStatus.NEED_IMPORTS:
            console.error('Imports:', runner.get_import_requests());
            return;
        case StepStatus.SUSPENDED:
            // Handle async orders (see below)
            const orders = runner.get_pending_orders();
            const responses = await handleOrders(orders);
            runner.fulfill_orders(responses);
            continue;
    }
}
```

### Status Constants

| Function | Value | Meaning |
|----------|-------|---------|
| `STEP_CONTINUE()` | 0 | More to execute, call step() again |
| `STEP_COMPLETE()` | 1 | Finished with a value in `result.value` |
| `STEP_NEED_IMPORTS()` | 2 | Waiting for modules (call `get_import_requests()`) |
| `STEP_SUSPENDED()` | 3 | Waiting for orders (call `get_pending_orders()`) |
| `STEP_DONE()` | 4 | Finished, no return value |
| `STEP_ERROR()` | 5 | Error in `result.error` |

### Async Order System

For async operations, TypeScript code uses `order` from the `tsrun:host` module:

```typescript
import { order } from "tsrun:host";

function fetch(url: string): Promise<any> {
    return order({ type: "fetch", url });
}

const data = await fetch("/api/users");
```

JavaScript handles orders by returning unresolved Promises immediately, enabling parallel async operations:

```javascript
// Handle orders by returning unresolved Promises immediately.
// This enables parallel async operations - each order gets a Promise that
// resolves after async work, but execution continues immediately.
function handleOrders(orderIds) {
    for (const orderId of orderIds) {
        const payload = runner.get_order_payload(orderId);

        // Create unresolved Promise and fulfill order immediately
        const promiseHandle = runner.create_promise();
        runner.set_order_result(orderId, promiseHandle);

        // Schedule resolution based on order type (runs concurrently!)
        if (payload.type === "fetch") {
            fetch(payload.url)
                .then(r => r.json())
                .then(data => runner.resolve_promise(promiseHandle, toHandle(data)));
        } else if (payload.type === "timeout") {
            setTimeout(() => runner.resolve_promise(promiseHandle, undefined), payload.ms);
        } else {
            runner.reject_promise(promiseHandle, `Unknown type: ${payload.type}`);
        }
    }
    // Return immediately - don't wait for async operations
}
```

### TsRunner Methods

| Method | Description |
|--------|-------------|
| `new TsRunner()` | Create new runner instance |
| `prepare(code, filename)` | Compile code, returns WasmStepResult |
| `step()` | Execute one step, returns WasmStepResult |
| `get_pending_orders()` | Get orders when Suspended (returns `[{id, payload}]`) |
| `get_import_requests()` | Get module specifiers when NeedImports |
| `fulfill_orders(responses)` | Provide order responses (`[{id, result?, error?}]`) |

### WasmStepResult Properties

| Property | Type | Description |
|----------|------|-------------|
| `status` | number | StepStatus enum value |
| `value` | string? | Result value (for Complete status) |
| `error` | string? | Error message (for Error status) |
| `console_output` | ConsoleEntry[] | Console output from this step |

### Notes

- Builds with `cargo build --target wasm32-unknown-unknown --features wasm --no-default-features`
- Uses C-style FFI exports for compatibility across all WASM runtimes
- Feature-gated: `--features wasm` and `--no-default-features`
- Imports in builtins must use `crate::prelude::Box` (not `std::boxed::Box`) for `no_std` compatibility
- Each interpreter instance is independent (no shared state)

## Implementation Status

**TypeScript Features:** enums, interfaces, type annotations, generics, type assertions, parameter properties, optional parameters, decorators, namespaces.

**Language Features:** variables, functions, closures, control flow, classes with inheritance/static blocks, destructuring, spread, template literals, all operators, generators, async/await, Promises, eval().

**Built-in Objects:** Array, String, Object, Number, Math, JSON, Map, Set, WeakMap, WeakSet, Date, RegExp, Function, Error types, Symbol, Proxy, Reflect, console.

**Embedding:** Rust API, C FFI with native callbacks, module loading, async order system, and WASM support (browser, Node.js, Go/wazero, and other runtimes).