Status: MVP available behind environment gates (default OFF). This page describes how to enable and use the initial features (derive, test runner, expansion dump) and the developer‑facing Pattern/Quote API preview.
## Enabling/disabling macros
- Default: ON
- Disable: `NYASH_MACRO_DISABLE=1` or `NYASH_MACRO_ENABLE=0|false|off`
- Debug trace: `NYASH_MACRO_TRACE=1`
- CLI shortcut for debug: `--expand` (prints pre/post AST, and sets trace)
## Derive (MVP)
- Provided derives: `Equals`, `ToString`
- Control via env:
-`NYASH_MACRO_DERIVE=Equals,ToString` (default when unset)
-`NYASH_MACRO_DERIVE_ALL=1` (force all derives)
- Behavior:
- Injects `equals(__ny_other)` and `toString()` into Box declarations when not already present.
- Public-only: derives operate on public fields only (Philosophy: Box independence / loose coupling).
- No changes to MIR instruction set; expansion happens on AST, then compiled as usual.
Example
```
NYASH_MACRO_ENABLE=1 ./target/release/nyash --backend vm apps/APP/main.nyash
```
## Test runner (MVP)
- CLI: `--run-tests` → enable macro engine and inject a test harness main
- Discovery targets:
- Top‑level functions with names `test_*`
- Box static/instance methods `test_*`
- Filtering: `--test-filter SUBSTR` or `NYASH_TEST_FILTER=SUBSTR`
- Entry policy when a main exists:
-`--test-entry wrap` → rename original `main` to `__ny_orig_main`, run tests then call original
-`--test-entry override` → replace entry with test harness only
- Force apply even if a main exists: `NYASH_TEST_FORCE=1`
- Return policy in wrap mode:
-`--test-return {tests|original}` or `NYASH_TEST_RETURN`
-`tests` (default): harness returns number of failed tests
-`original`: return the original main result (tests still run)
- Parameterized tests (MVP): `NYASH_TEST_ARGS_DEFAULTS=1` injects integer `0` defaults for each parameter (static/instance)
## Core Normalization (always-on when macros enabled)
Certain language sugars are normalized before MIR across all runners when the macro gate is enabled. These are not user macros and do not require any registration:
- for sugar → Loop
-`for(fn(){ init }, cond, fn(){ step }, fn(){ body })`
- Emits: `init; loop(cond){ body; step }`
-`init/step` also accept a single Assignment or Local instead of `fn(){..}`.
- foreach sugar → Loop
-`foreach(array_expr, "x", fn(){ body_with_x })`
- Expands into an index-based loop with `__ny_i`, and substitutes `x` with `array_expr.get(__ny_i)` inside body.
- Pattern/Quote primitives are available to bootstrap macro authorship.
- TemplatePattern:
- Bind placeholders using `$name` inside a template AST.
- OrPattern: alternation of templates.
- Variadic `$...name` supported at any position inside call/array argument lists; binds the variable-length segment to a pseudo list (`ASTNode::Program`).
- Unquote:
- Replace `$name` with the bound AST.
- Splice `$...name` into call/array argument lists.
- Array/Map nodes participate in pattern/unquote (map keys must match literally; values can bind via `$name`).
- When JSON cannot be mapped or arity mismatches, warnings are printed with `[macro][test][args]` and the specific test may be skipped unless `NYASH_TEST_ARGS_DEFAULTS=1` is set.
Notes
- Textual `quote(code)` is supported as a convenience via the existing parser, but `$name` placeholders should be built in AST templates directly.
- This API will evolve; treat as experimental until the stable `macro_box` interface is finalized.
## Expansion Safety (Depth/Cycle)
- The macro engine applies a bounded number of expansion passes and stops on fixpoint.
- Environment tuning:
-`NYASH_MACRO_MAX_PASSES` (default 32)
-`NYASH_MACRO_CYCLE_WINDOW` (default 8) — detect cycles across recent states