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:
- Parser recognizes
@test("...")annotation before function - Transform function into test case Box
- Generate test registry registration
- 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:
- Parser recognizes
@assert(...)as statement - Extract condition and optional message
- 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:
- Parser recognizes
@benchmark(...)annotation - Extract warmup/repeat parameters
- Generate wrapper Box with timing logic
- 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:
- Parser recognizes
@traceannotation - Inject enter/exit logging
- Inject intermediate value logging
- 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:
- Parser recognizes
@guard(...)annotations - Collect all guards for function
- 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:
- Parser recognizes
@defer(...)as statement - Transform to
cleanup { ... }block - 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:
- Parser recognizes
@derive(...)annotation on box - For each trait, generate appropriate methods
- 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:
- Parser recognizes
@memoannotation - Generate wrapper Box with cache MapBox
- Hash function arguments for cache key
- 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:
- Parser recognizes
@asyncannotation - Transform
await exprtonowait fut = expr; await fut - 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:
- Parser recognizes
@builderannotation - Generate
with_*methods for each field - 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:
- Parser recognizes
@singletonannotation - Generate wrapper static box with instance management
- 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:
- Parser recognizes
@inlineannotation - VM backend: ignore (no effect)
- LLVM backend: add
alwaysinlinefunction attribute - WASM backend: add inline hint
🎯 Implementation Priority Recommendation
Phase 1: Testing & Debugging (Week 1-2)
- ✅ @test
- ✅ @assert
- ✅ @trace
- ✅ @benchmark
Phase 2: Code Quality (Week 3-4)
- ✅ @guard
- ✅ @defer
- ✅ @derive (Debug, Equals, Clone)
Phase 3: Performance & Patterns (Week 5-6)
- ✅ @memo
- ✅ @async
- ✅ @inline
Phase 4: Advanced Patterns (Week 7+)
- ✅ @builder
- ✅ @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:
- ✅ Expansion test (verify correct code generation)
- ✅ Runtime test (verify expanded code works)
- ✅ Error test (verify bad usage gives good errors)
- ✅ 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.