能否在Nim中全局修改AST?求类似Haxe@:build宏的实现方案
Nim中实现类似Haxe @:build宏的AST修改方案
Nim完全支持在显式宏调用之外修改项目AST,实现类似Haxe @:build宏的效果——无需手动调用宏,就能自动遍历、修改模块内的声明(比如重写函数)。主要可以通过模块级宏或自定义Pragma配合宏两种方式实现,以下是具体示例:
1. 模块级宏:自动修改整个模块的所有函数
模块级宏会接收当前模块的完整AST,可在类型检查前遍历并修改所有声明:
import macros # 定义模块级宏:遍历模块AST,给所有函数添加入口日志 macro rewriteModule*(m: NimNode): NimNode = result = newNimNode(nnkStmtList) for stmt in m: # 匹配函数声明节点 if stmt.kind == nnkProcDef: let funcName = stmt[0].strVal # 在函数体开头插入打印语句 let logStmt = newCall("echo", newStrLit(s"Entered function: ${funcName}")) stmt[4].add(logStmt) # stmt[4]对应函数体的语句列表 result.add(stmt) # 将宏应用到当前模块,自动处理所有声明 {.pragma: rewriteModule.} # 测试函数(会被自动修改) proc calculateSum(a, b: int): int = return a + b when isMainModule: echo calculateSum(10, 20)
运行这段代码时,calculateSum函数会自动被注入日志逻辑,执行后会先打印Entered function: calculateSum,再输出计算结果。
2. 自定义Pragma:仅修改标记的特定函数
如果不需要修改所有声明,可自定义Pragma,仅对标记的函数进行AST重写:
import macros # 定义宏:给标记的函数添加调用日志 macro addCallLog*(p: NimNode): NimNode = result = p if p.kind == nnkProcDef: let funcName = p[0].strVal let logStmt = newCall("echo", newStrLit(s"Calling function: ${funcName}")) # 在函数体最开头插入日志语句 p[4].insert(0, logStmt) # 注册自定义Pragma {.pragma: addCallLog.} # 标记需要修改的函数 proc multiply(x, y: int): int {.addCallLog.} = return x * y # 未标记的函数不会被修改 proc subtract(x, y: int): int = return x - y when isMainModule: echo multiply(5, 3) # 会先打印日志 echo subtract(10, 4) # 无日志输出
3. 进阶:遍历复杂AST结构
使用walkAst函数可以更便捷地遍历嵌套的AST节点,适合复杂场景的修改:
import macros # 模块级宏:给所有函数名添加前缀 macro renameAllProcs*(m: NimNode): NimNode = result = m walkAst(result): case n.kind: of nnkProcDef: n[0].strVal = "rewritten_" & n[0].strVal else: discard {.pragma: renameAllProcs.} proc greet(name: string): string = return s"Hello, ${name}" when isMainModule: # 函数名已被修改,需调用rewritten_greet echo rewritten_greet("Bob")
关键说明
- 模块级宏的执行时机是模块解析完成后、类型检查前,适合在类型检查前调整AST结构;
- 如果需要在类型检查后修改AST,可以使用
{.transform.}Pragma配合宏,不过这类宏只能读取类型信息,不能修改函数签名等核心结构; - 所有宏操作都基于Nim的
std/macros模块提供的API,可通过遍历节点类型、修改节点属性或子节点实现各种自定义逻辑。
内容的提问来源于stack exchange,提问作者Griffort
相关产品推荐
相关产品推荐

