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

742 lines
15 KiB
Markdown

# 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)
```hakorune
// 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)
```hakorune
// 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)
```hakorune
// 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)
```hakorune
// 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)
```hakorune
// 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)
```hakorune
// 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)
```hakorune
// 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)
```hakorune
// 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)
```hakorune
// 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)
```hakorune
// 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)
```hakorune
// 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)
```hakorune
// 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
```hakorune
// 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:
```bash
# 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](practical-boxes-proposals.md) for runtime library proposals.