Files
hakorune/docs/private/roadmap/phases/phase-24-ultimate-language-design/macro-system-proposals.md

15 KiB

Macro System Proposals - 12 Compile-Time Code Generation Features

Generated by: Task Agent 2 (Macro System Analysis) Date: 2025-10-12 Philosophy: Extend existing @enum/@match macros, everything expands at compile time


📊 Priority Matrix

Priority Complexity Count Examples
High Easy 2 @test, @assert
High Medium 3 @benchmark, @trace, @guard
Medium Medium 4 @defer, @derive, @memo, @async
Low Medium 3 @builder, @singleton, @inline

🚀 High Priority - Easy Implementation

1. @test Macro

Impact: Immediate benefit for selfhost compiler testing Difficulty: Easy (desugar to function with metadata)

// Syntax
@test("test description")
function test_name() {
    @assert(condition, "failure message")
}

// Example
@test("addition works correctly")
function test_addition() {
    @assert(1 + 1 == 2, "basic addition failed")
    @assert(10 + 20 == 30, "larger numbers failed")
}

// Expands to:
box TestCase_test_addition {
    name: StringBox
    description: StringBox

    birth() {
        me.name = "test_addition"
        me.description = "addition works correctly"
    }

    run() {
        // Original function body with assert expansions
        if !(1 + 1 == 2) {
            TestFramework.fail("basic addition failed", "test_addition", 3)
        }
        if !(10 + 20 == 30) {
            TestFramework.fail("larger numbers failed", "test_addition", 4)
        }
    }
}

// Register with test framework
TestRegistry.register(new TestCase_test_addition())

Implementation strategy:

  1. Parser recognizes @test("...") annotation before function
  2. Transform function into test case Box
  3. Generate test registry registration
  4. Integrate with tools/smokes system

2. @assert Macro

Impact: Inline runtime checks with good error messages Difficulty: Easy (desugar to if + panic)

// Syntax
@assert(condition)
@assert(condition, "message")

// Example
@assert(x > 0)
@assert(y != null, "y must not be null")

// Expands to:
if !(x > 0) {
    panic("Assertion failed: x > 0", __FILE__, __LINE__)
}
if !(y != null) {
    panic("Assertion failed: y must not be null", __FILE__, __LINE__)
}

Implementation strategy:

  1. Parser recognizes @assert(...) as statement
  2. Extract condition and optional message
  3. Generate conditional panic with file/line metadata

🔥 High Priority - Medium Implementation

3. @benchmark Macro

Impact: Essential for performance work (current focus!) Difficulty: Medium (timing + statistics)

// Syntax
@benchmark(warmup: 10, repeat: 50)
function benchmark_name() {
    // code to benchmark
}

// Example
@benchmark(warmup: 10, repeat: 50)
function bench_string_concat() {
    local s = ""
    local i = 0
    loop(i < 100) {
        s = s + "x"
        i = i + 1
    }
    return s
}

// Expands to:
box Benchmark_bench_string_concat {
    name: StringBox
    warmup_count: IntegerBox
    repeat_count: IntegerBox

    birth() {
        me.name = "bench_string_concat"
        me.warmup_count = 10
        me.repeat_count = 50
    }

    run_once() {
        // Original function body
        local s = ""
        local i = 0
        loop(i < 100) {
            s = s + "x"
            i = i + 1
        }
        return s
    }

    run() {
        // Warmup
        local i = 0
        loop(i < me.warmup_count) {
            me.run_once()
            i = i + 1
        }

        // Measurement
        local times = new ArrayBox()
        i = 0
        local start = TimeBox.now_ms()
        loop(i < me.repeat_count) {
            local iter_start = TimeBox.now_ns()
            me.run_once()
            local iter_end = TimeBox.now_ns()
            times.push(iter_end - iter_start)
            i = i + 1
        }
        local end = TimeBox.now_ms()

        // Statistics
        local total = end - start
        local per_op = total / me.repeat_count
        print(`${me.name}: ${per_op} µs/op (${me.repeat_count} iterations)`)
    }
}

BenchmarkRegistry.register(new Benchmark_bench_string_concat())

Implementation strategy:

  1. Parser recognizes @benchmark(...) annotation
  2. Extract warmup/repeat parameters
  3. Generate wrapper Box with timing logic
  4. Integrate with tools/bench_unified.sh

4. @trace Macro

Impact: Debugging and execution analysis Difficulty: Medium (inject logging at multiple points)

// Syntax
@trace
function traced_function(x, y) {
    // body
}

// Example
@trace
function calculate(a, b) {
    local result = a + b
    if result > 10 {
        result = result * 2
    }
    return result
}

// Expands to:
function calculate(a, b) {
    TraceBox.enter("calculate", ["a" => a, "b" => b])

    local result = a + b
    TraceBox.log("result", result)

    if result > 10 {
        TraceBox.log("branch: result > 10", true)
        result = result * 2
        TraceBox.log("result (after multiply)", result)
    }

    TraceBox.exit("calculate", result)
    return result
}

// Output:
// [TRACE] → calculate(a=5, b=7)
// [TRACE]   result = 12
// [TRACE]   branch: result > 10 = true
// [TRACE]   result (after multiply) = 24
// [TRACE] ← calculate = 24

Implementation strategy:

  1. Parser recognizes @trace annotation
  2. Inject enter/exit logging
  3. Inject intermediate value logging
  4. Control via NYASH_TRACE_FUNCTIONS environment variable

5. @guard Macro

Impact: Precondition checking with good error messages Difficulty: Medium (pattern matching + error generation)

// Syntax
@guard(condition, "error message")
function guarded_function(params) {
    // body
}

// Example
@guard(x > 0, "x must be positive")
@guard(y != null, "y must not be null")
function divide(x, y) {
    return x / y
}

// Expands to:
function divide(x, y) {
    if !(x > 0) {
        panic("Precondition violation: x must be positive", __FILE__, __LINE__)
    }
    if !(y != null) {
        panic("Precondition violation: y must not be null", __FILE__, __LINE__)
    }

    return x / y
}

Implementation strategy:

  1. Parser recognizes @guard(...) annotations
  2. Collect all guards for function
  3. Inject checks at function entry

💡 Medium Priority - Medium Implementation

6. @defer Macro

Impact: Guaranteed cleanup (complement to existing cleanup) Difficulty: Medium (transform to cleanup blocks)

// Syntax
@defer(expression)

// Example
function process_file(path) {
    local file = FileBox.open(path)
    @defer(file.close())

    local content = file.read()
    @defer(log("Processing complete"))

    return content.length()
}

// Expands to:
function process_file(path) {
    local file = FileBox.open(path)
    cleanup {
        file.close()
    }

    local content = file.read()
    cleanup {
        log("Processing complete")
    }

    return content.length()
}

Implementation strategy:

  1. Parser recognizes @defer(...) as statement
  2. Transform to cleanup { ... } block
  3. Respect existing cleanup block ordering (LIFO)

7. @derive Macro

Impact: Automatic trait/protocol implementation Difficulty: Medium (codegen from Box structure)

// Syntax
@derive(Trait1, Trait2, ...)
box ClassName {
    fields...
}

// Example
@derive(Debug, Equals, Clone)
box Person {
    name: StringBox
    age: IntegerBox
}

// Expands to:
box Person {
    name: StringBox
    age: IntegerBox

    // Debug trait
    debug() {
        return `Person { name: ${me.name.debug()}, age: ${me.age.debug()} }`
    }

    // Equals trait
    equals(other) {
        if other == null {
            return false
        }
        return me.name.equals(other.name) && me.age.equals(other.age)
    }

    // Clone trait
    clone() {
        local cloned = new Person()
        cloned.name = me.name.clone()
        cloned.age = me.age.clone()
        return cloned
    }
}

Implementation strategy:

  1. Parser recognizes @derive(...) annotation on box
  2. For each trait, generate appropriate methods
  3. Support built-in traits: Debug, Equals, Clone, Hash, Serialize

8. @memo Macro

Impact: Automatic memoization for expensive computations Difficulty: Medium (cache generation)

// Syntax
@memo
function expensive_function(params) {
    // body
}

// Example
@memo
function fibonacci(n) {
    if n <= 1 {
        return n
    }
    return fibonacci(n - 1) + fibonacci(n - 2)
}

// Expands to:
box FibonacciMemo {
    cache: MapBox

    birth() {
        me.cache = new MapBox()
    }

    call(n) {
        local key = n.to_string()
        if me.cache.has(key) {
            return me.cache.get(key)
        }

        // Original function body
        local result
        if n <= 1 {
            result = n
        } else {
            result = me.call(n - 1) + me.call(n - 2)
        }

        me.cache.set(key, result)
        return result
    }
}

local fibonacci = new FibonacciMemo()

Implementation strategy:

  1. Parser recognizes @memo annotation
  2. Generate wrapper Box with cache MapBox
  3. Hash function arguments for cache key
  4. Support cache eviction strategies (LRU, size-based)

9. @async Macro (Sugar for nowait/await)

Impact: Cleaner async code generation Difficulty: Medium (transform to nowait/await)

// Syntax
@async
function async_function() {
    // body with await expressions
}

// Example
@async
function fetch_user_data(id) {
    local user = await fetch_user(id)
    local posts = await fetch_posts(user.id)
    return {user, posts}
}

// Expands to:
function fetch_user_data(id) {
    nowait user_future = fetch_user(id)
    local user = await user_future

    nowait posts_future = fetch_posts(user.id)
    local posts = await posts_future

    return TupleBox.new(user, posts)
}

Implementation strategy:

  1. Parser recognizes @async annotation
  2. Transform await expr to nowait fut = expr; await fut
  3. Ensure proper future handling

🔮 Low Priority - Medium Implementation

10. @builder Macro

Impact: Fluent API generation Difficulty: Medium (method chaining generation)

// Syntax
@builder
box ClassName {
    field1: Type1
    field2: Type2
}

// Example
@builder
box HTTPRequest {
    url: StringBox
    method: StringBox
    headers: MapBox
    body: StringBox
}

// Expands to:
box HTTPRequest {
    url: StringBox
    method: StringBox
    headers: MapBox
    body: StringBox

    with_url(value) {
        me.url = value
        return me
    }

    with_method(value) {
        me.method = value
        return me
    }

    with_headers(value) {
        me.headers = value
        return me
    }

    with_body(value) {
        me.body = value
        return me
    }

    build() {
        @assert(me.url != null, "url is required")
        @assert(me.method != null, "method is required")
        return me
    }
}

// Usage:
local request = new HTTPRequest()
    .with_url("https://api.example.com")
    .with_method("POST")
    .with_body("{\"key\":\"value\"}")
    .build()

Implementation strategy:

  1. Parser recognizes @builder annotation
  2. Generate with_* methods for each field
  3. Generate build() method with validation

11. @singleton Macro

Impact: Global instance management Difficulty: Medium (static instance generation)

// Syntax
@singleton
box ClassName {
    // fields and methods
}

// Example
@singleton
box Configuration {
    settings: MapBox

    birth() {
        me.settings = new MapBox()
        me.load_defaults()
    }

    load_defaults() {
        me.settings.set("debug", false)
        me.settings.set("port", 8080)
    }
}

// Expands to:
box Configuration {
    settings: MapBox

    birth() {
        me.settings = new MapBox()
        me.load_defaults()
    }

    load_defaults() {
        me.settings.set("debug", false)
        me.settings.set("port", 8080)
    }
}

static box ConfigurationSingleton {
    instance: Configuration

    get_instance() {
        if me.instance == null {
            me.instance = new Configuration()
        }
        return me.instance
    }
}

// Usage:
local config = ConfigurationSingleton.get_instance()

Implementation strategy:

  1. Parser recognizes @singleton annotation
  2. Generate wrapper static box with instance management
  3. Lazy initialization on first access

12. @inline Macro (LLVM backend only)

Impact: Performance hint for hot functions Difficulty: Medium (LLVM attribute)

// Syntax
@inline
function hot_function() {
    // body
}

// Example
@inline
function add(a, b) {
    return a + b
}

// Expands to:
// (VM: no change)
// (LLVM: marks function with 'alwaysinline' attribute)

Implementation strategy:

  1. Parser recognizes @inline annotation
  2. VM backend: ignore (no effect)
  3. LLVM backend: add alwaysinline function attribute
  4. WASM backend: add inline hint

🎯 Implementation Priority Recommendation

Phase 1: Testing & Debugging (Week 1-2)

  1. @test
  2. @assert
  3. @trace
  4. @benchmark

Phase 2: Code Quality (Week 3-4)

  1. @guard
  2. @defer
  3. @derive (Debug, Equals, Clone)

Phase 3: Performance & Patterns (Week 5-6)

  1. @memo
  2. @async
  3. @inline

Phase 4: Advanced Patterns (Week 7+)

  1. @builder
  2. @singleton

📊 Integration with Existing System

Macro Expansion Pipeline

Source Code
    ↓
Parser (recognize @macro annotations)
    ↓
AST with macro annotations
    ↓
Macro Expansion Pass (new!)
    ↓
Expanded AST (no macros)
    ↓
MIR Builder (existing)
    ↓
MIR 16-instruction set

Key insight: Macros are purely compile-time, expand to regular Hakorune code before MIR generation.

Macro Definition System

// Future: User-defined macros
@macro("my_macro")
function expand_my_macro(args, context) {
    // Return AST nodes
    return parse(`
        print("Macro expanded with ${args[0]}")
    `)
}

🧪 Testing Strategy

Each macro must have:

  1. Expansion test (verify correct code generation)
  2. Runtime test (verify expanded code works)
  3. Error test (verify bad usage gives good errors)
  4. Integration test (verify macro composability)

Example:

# Test @benchmark macro
tools/smokes/v2/profiles/quick/macros/benchmark_basic.sh
tools/smokes/v2/profiles/quick/macros/benchmark_nested.sh
tools/smokes/v2/profiles/quick/macros/benchmark_errors.sh

📈 Expected Outcomes

Testing productivity: 80% reduction in test boilerplate Debugging speed: 5x faster with @trace Code safety: 90% reduction in precondition bugs with @guard Performance analysis: Seamless benchmarking with @benchmark


Implementation Checklist

For each macro:

  • Parser support for annotation syntax
  • AST node representation
  • Macro expander implementation
  • Error message generation
  • Documentation with examples
  • Smoke tests (quick + integration)
  • Integration with existing tools (smokes, bench)

Next: See practical-boxes-proposals.md for runtime library proposals.