Files
hakorune/docs/guides/macro-system.md

6.0 KiB
Raw Blame History

Macro System (Phase 16) — Quickstart

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 developerfacing 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.hako

Test runner (MVP)

  • CLI: --run-tests → enable macro engine and inject a test harness main
  • Discovery targets:
    • Toplevel 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)
  • Exit code: number of failed tests (0 = green)

Examples

# run all tests in a file
./target/release/nyash --run-tests apps/tests/my_tests.hako

# filter + wrap entry + default arg injection
NYASH_MACRO_ENABLE=1 NYASH_TEST_ARGS_DEFAULTS=1 \
./target/release/nyash --run-tests --test-filter http --test-entry wrap apps/tests/my_tests.hako

Expansion dump

./target/release/nyash --expand --dump-ast apps/tests/ternary_basic.hako

Shows pre/post expansion AST (debug only).

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.

Normalization order (within the core pass):

  1. for/foreach → 2) match(PeekExpr) → 3) loop tail alignment (carrier-like ordering; break/continue segments supported).

Notes:

  • Backward-compat function names ny_for / ny_foreach are also accepted but for / foreach are preferred.
  • This pass is part of the language pipeline; it is orthogonal to user-defined macros.

Developer API (preview)

  • 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).

MacroCtx (PoC)

User macros executed via the PyVM sandbox receive a second argument ctx in expand(json, ctx):

  • Shape (string): { "caps": { "io": bool, "net": bool, "env": bool } }
  • Policy: all caps default to false. The sandbox disables plugins and exposes a minimal Box API.
  • Do not print to stdout from macros: the child process stdout is reserved for the expanded JSON.
    • For diagnostics use stderr in the future; for now prefer silent operation or trace via the parent process.

Example (identity):

static box MacroBoxSpec {
  name() { return "MacroCtxDemo" }
  expand(json, ctx) { return json }
}

JSON test args (advanced)

For --run-tests, you can supply per-test arguments and instance construction details via NYASH_TEST_ARGS_JSON.

Shapes:

  • Simple list (as before): { "test_name": [1, "s", true], "Box.method": [ 0, 1 ] }
  • Detailed object:
    • { "Box.method": { "args": [ ... ], "instance": { "ctor": "new|birth", "args": [ ... ], "type_args": ["T", "U"] } } }
  • Typed values inside args:
    • { "i": 1 }, { "f": 1.2 }, { "s": "x" }, { "b": true }, null
    • Arrays/Maps as value: { "array": [1, 2, 3] }, { "map": { "k": 1, "k2": {"s":"v"} } }
    • Call/method/var literals: { "call": "fn", "args": [...] }, { "method": "m", "object": {"var":"obj"}, "args": [...] }, { "var": "name" }

Diagnostics:

  • 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
    • NYASH_MACRO_TRACE=1 — pass-by-pass logging