name: Nim元编程 user-invocable: false description: 使用于Nim的元编程,包括宏、模板、编译时计算、AST操作、代码生成、DSL创建,以及利用编译时计算来优化性能和抽象系统编程。 allowed-tools: []
Nim元编程
引言
Nim的元编程系统通过模板、宏和编译时计算提供了强大的编译时代码生成和操作。这实现了零开销抽象、领域特定语言和在编译时而非运行时执行的优化。
该系统操作Nim的抽象语法树(AST),允许检查和转换代码结构。模板提供卫生的类似宏的替换,而宏则支持完整的AST操作。编译时计算在编译期间执行Nim代码,用于常量折叠和验证。
本技能涵盖用于代码替换的模板、用于AST转换的宏、使用静态块的编译时计算、AST检查和操作、代码生成模式、DSL创建以及元编程最佳实践。
模板
模板提供卫生的代码替换,具有类型安全性和内联扩展。
# 基本模板
template max(a, b: untyped): untyped =
if a > b: a else: b
echo max(5, 10) # 内联扩展
# 多语句模板
template withFile(filename: string, body: untyped): untyped =
let file = open(filename, fmRead)
try:
body
finally:
file.close()
withFile("data.txt"):
for line in file.lines:
echo line
# 使用注入的模板
template defineProperty(name: untyped, typ: typedesc): untyped =
var `name Value`: typ
proc `get name`(): typ {.inject.} =
`name Value`
proc `set name`(value: typ) {.inject.} =
`name Value` = value
defineProperty(age, int)
setAge(30)
echo getAge() # 30
# 接受块的模板
template benchmark(name: string, code: untyped): untyped =
let start = cpuTime()
code
let elapsed = cpuTime() - start
echo name, ": ", elapsed, " seconds"
benchmark "computation":
var sum = 0
for i in 1..1000000:
sum += i
# 带类型参数的模板
template swap(a, b: typed): untyped =
let tmp = a
a = b
b = tmp
var x = 5
var y = 10
swap(x, y)
echo x, " ", y # 10 5
# DSL模板
template html(body: untyped): string =
var result = ""
template tag(name: string, content: untyped): untyped =
result.add "<" & name & ">"
content
result.add "</" & name & ">"
body
result
let page = html:
tag "html":
tag "body":
tag "h1":
result.add "Hello World"
# 带通用约束的模板
template findMax[T: Ordinal](items: openArray[T]): T =
var maxVal = items[0]
for item in items:
if item > maxVal:
maxVal = item
maxVal
echo findMax([1, 5, 3, 9, 2])
# 转发模板
template forward(call: untyped): untyped =
echo "Before call"
call
echo "After call"
proc myProc() =
echo "Inside proc"
forward myProc()
# 自定义操作符模板
template `~=`(a, b: float): bool =
abs(a - b) < 0.0001
echo 1.0 ~= 1.00001 # true
# 带立即参数的模板
template log(msg: string): untyped =
when defined(debug):
echo "[LOG] ", msg
log "Debug message" # 仅在调试构建中
模板提供编译时代码替换,具有卫生性和类型安全性。
宏和AST操作
宏在编译时转换AST,实现强大的代码生成和领域特定语言。
import macros
# 基本宏
macro debug(n: varargs[typed]): untyped =
result = newStmtList()
for arg in n:
result.add quote do:
echo astToStr(`arg`), " = ", `arg`
let x = 5
let y = 10
debug x, y, x + y
# 检查AST的宏
macro inspect(node: untyped): untyped =
echo node.treeRepr
result = node
inspect:
let x = 5 + 3
# 手动构建AST
macro makeProc(name: untyped, body: untyped): untyped =
result = newProc(
name = name,
params = [newEmptyNode()],
body = body
)
makeProc greet:
echo "Hello!"
greet()
# 属性生成宏
macro property(name: untyped, typ: typedesc): untyped =
let
fieldName = ident($name & "Field")
getName = ident("get" & $name)
setName = ident("set" & $name)
result = quote do:
var `fieldName`: `typ`
proc `getName`(): `typ` =
`fieldName`
proc `setName`(value: `typ`) =
`fieldName` = value
property(count, int)
setCount(42)
echo getCount()
# case语句宏
macro switch(value: typed, branches: varargs[untyped]): untyped =
result = nnkCaseStmt.newTree(value)
for branch in branches:
expectKind(branch, nnkCall)
let pattern = branch[0]
let body = branch[1]
result.add nnkOfBranch.newTree(pattern, body)
switch(5):
1: echo "one"
2: echo "two"
5: echo "five"
# 构建器模式宏
macro build(typ: typedesc, fields: varargs[untyped]): untyped =
result = nnkObjConstr.newTree(typ)
for field in fields:
expectKind(field, nnkExprEqExpr)
result.add field
type Person = object
name: string
age: int
let p = build(Person, name = "Alice", age = 30)
# 编译时断言宏
macro staticAssert(condition: bool, message: string): untyped =
if not condition.boolVal:
error(message.strVal)
result = newEmptyNode()
staticAssert sizeof(int) >= 4, "int must be at least 4 bytes"
# 循环展开宏
macro unroll(count: static[int], body: untyped): untyped =
result = newStmtList()
for i in 0..<count:
let iNode = newLit(i)
result.add body.replace(ident("it"), iNode)
unroll 5:
echo "Iteration: ", it
# 模式匹配宏
macro match(value: typed, patterns: varargs[untyped]): untyped =
result = nnkIfStmt.newTree()
for pattern in patterns:
expectMinLen(pattern, 2)
let condition = pattern[0]
let body = pattern[1]
let check = quote do:
`value` == `condition`
result.add nnkElifBranch.newTree(check, body)
宏通过AST操作实现编译时代码转换和生成。
编译时计算
Nim在编译时执行代码以用于常量、优化和验证。
# 编译时常量
const maxSize = 100
const computed = maxSize * 2 + 10 # 编译时计算
# 编译时函数计算
proc factorial(n: int): int =
if n <= 1: 1 else: n * factorial(n - 1)
const fact10 = factorial(10) # 编译时计算
# 静态块
static:
echo "This runs at compile time"
echo "Factorial of 10 is: ", factorial(10)
# 编译时类型信息
proc sizeInfo[T](x: T): string =
static:
"Size of " & $T & " is " & $sizeof(T) & " bytes"
echo sizeInfo(5)
echo sizeInfo(5.0)
# 编译时有条件编译
when sizeof(int) == 8:
proc printSize() = echo "64-bit platform"
else:
proc printSize() = echo "32-bit platform"
# 编译时字符串操作
const
version = "1.0.0"
parts = version.split('.')
major = parts[0].parseInt
when major >= 1:
echo "Version 1.0 or later"
# 编译时文件读取
const configData = staticRead("config.txt")
proc getConfig(): string =
configData
# 编译时HTTP请求(带标准库)
const apiResponse = staticExec("curl -s https://api.example.com/data")
# 编译时代码生成
proc generateAccessors(fields: seq[string]): string =
result = ""
for field in fields:
result.add &"""
proc get{field.capitalizeAscii}(): int = {field}
proc set{field.capitalizeAscii}(val: int) = {field} = val
"""
const accessors = generateAccessors(@["x", "y", "z"])
# 静态参数约束
proc processArray[T; N: static[int]](arr: array[N, T]) =
static:
echo "Array size: ", N
for item in arr:
echo item
processArray([1, 2, 3, 4, 5])
# 编译时断言
static:
doAssert sizeof(int) >= 4, "int too small"
doAssert sizeof(ptr) == sizeof(int), "pointer size mismatch"
# 编译时正则编译
import re
const emailPattern = re"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
proc validateEmail(email: string): bool =
email.match(emailPattern)
# 编译时验证
proc validateConfig() =
static:
when not fileExists("config.txt"):
{.fatal: "config.txt not found".}
# 编译时优化
proc optimizedPower(base: float, exp: static[int]): float =
when exp == 0:
1.0
elif exp == 1:
base
elif exp mod 2 == 0:
let half = optimizedPower(base, exp div 2)
half * half
else:
base * optimizedPower(base, exp - 1)
echo optimizedPower(2.0, 10)
编译时计算实现零运行时成本的抽象和构建时验证。
DSL创建
Nim的元编程能力使其能够创建具有自然语法的领域特定语言。
# HTML DSL
macro html(body: untyped): string =
proc processNode(node: NimNode): NimNode =
case node.kind
of nnkCall, nnkCommand:
let tag = node[0]
let attrs = newSeq[NimNode]()
var content = newStmtList()
for i in 1..<node.len:
if node[i].kind == nnkExprEqExpr:
attrs.add node[i]
else:
content.add processNode(node[i])
result = quote do:
result.add "<" & `tag`.astToStr & ">"
`content`
result.add "</" & `tag`.astToStr & ">"
of nnkStrLit:
result = quote do:
result.add `node`
else:
result = newStmtList()
result = quote do:
var result = ""
`processNode(body)`
result
let page = html:
html:
head:
title: "My Page"
body:
h1: "Welcome"
p: "Hello World"
# SQL DSL
macro select(fields: varargs[untyped]): string =
var fieldList = ""
for i, field in fields:
if i > 0: fieldList.add ", "
fieldList.add $field
result = newLit("SELECT " & fieldList)
macro fromTable(table: untyped): string =
newLit(" FROM " & $table)
let query = select(name, age) & fromTable(users)
# 测试DSL
macro describe(name: string, tests: untyped): untyped =
result = newStmtList()
for test in tests:
if test.kind == nnkCall and $test[0] == "it":
let testName = test[1]
let testBody = test[2]
result.add quote do:
echo "Testing: ", `testName`
try:
`testBody`
echo " ✓ Passed"
except:
echo " ✗ Failed"
describe "Math operations":
it "adds numbers":
doAssert 1 + 1 == 2
it "multiplies numbers":
doAssert 2 * 3 == 6
# 配置DSL
macro config(body: untyped): untyped =
result = nnkObjConstr.newTree(ident("Config"))
for stmt in body:
if stmt.kind == nnkCall:
let key = stmt[0]
let value = stmt[1]
result.add nnkExprColonExpr.newTree(key, value)
type Config = object
host: string
port: int
debug: bool
let cfg = config:
host("localhost")
port(8080)
debug(true)
# 构建器DSL
macro builder(typ: typedesc, body: untyped): untyped =
var assignments = newStmtList()
for stmt in body:
if stmt.kind == nnkCall:
let field = stmt[0]
let value = stmt[1]
assignments.add quote do:
result.`field` = `value`
result = quote do:
var result: `typ`
`assignments`
result
type Request = object
url: string
method: string
headers: seq[string]
let req = builder(Request):
url("https://api.example.com")
method("GET")
DSL使领域特定表示法能够在Nim中使用,同时保持类型安全性。
代码生成模式
元编程使得能够从规范或运行时数据自动生成代码。
# 从编译时列表生成枚举
macro generateEnum(name: untyped, values: static[seq[string]]): untyped =
result = nnkTypeSection.newTree(
nnkTypeDef.newTree(
name,
newEmptyNode(),
nnkEnumTy.newTree(newEmptyNode())
)
)
for value in values:
result[0][2].add ident(value)
generateEnum(Color, @["Red", "Green", "Blue"])
# 生成getters/setters
macro generateAccessors(typ: typedesc): untyped =
let impl = typ.getImpl
result = newStmtList()
for field in impl[2][2]:
let fieldName = field[0]
let fieldType = field[1]
let getter = ident("get" & ($fieldName).capitalizeAscii)
let setter = ident("set" & ($fieldName).capitalizeAscii)
result.add quote do:
proc `getter`(obj: `typ`): `fieldType` =
obj.`fieldName`
proc `setter`(obj: var `typ`, value: `fieldType`) =
obj.`fieldName` = value
type Person = object
name: string
age: int
generateAccessors(Person)
# 生成模式匹配
macro matchGen(value: typed, patterns: varargs[untyped]): untyped =
result = nnkCaseStmt.newTree(value)
for pattern in patterns:
let condition = pattern[0]
let body = pattern[1]
result.add nnkOfBranch.newTree(condition, body)
# 生成状态机
macro stateMachine(states: varargs[untyped]): untyped =
var stateEnum = nnkEnumTy.newTree(newEmptyNode())
for state in states:
stateEnum.add ident($state)
result = nnkTypeSection.newTree(
nnkTypeDef.newTree(
ident("State"),
newEmptyNode(),
stateEnum
)
)
stateMachine Idle, Running, Stopped
# 生成序列化
macro deriveJson(typ: typedesc): untyped =
result = newStmtList()
# 生成toJson过程
result.add quote do:
proc toJson(obj: `typ`): JsonNode =
result = newJObject()
# 添加字段...
# 生成验证器
macro validate(typ: typedesc, rules: untyped): untyped =
result = quote do:
proc validate(obj: `typ`): bool =
# 生成的验证逻辑
true
代码生成减少了样板代码并确保类似实现的一致性。
最佳实践
-
对简单替换使用模板以避免当不需要AST操作时的宏复杂性。
-
尽可能使用类型宏参数而非无类型参数以获得更好的类型检查。
-
彻底测试宏因为编译时错误比运行时错误更难调试。
-
记录宏使用并提供示例,因为展开的代码对用户不可见。
-
使用quote do进行AST生成以编写自然的Nim代码而非手动AST构造。
-
利用编译时计算进行验证和优化,无运行时成本。
-
保持宏的专注性于单一职责以提高可维护性。
-
使用静态块进行编译时副作用,如日志记录或验证。
-
在宏中提供错误消息使用error()以获得清晰的编译时失败信息。
-
测试宏展开通过使用dumpTree或expandMacros检查生成的代码。
常见陷阱
-
过度使用宏解决可通过模板解决的问题增加了不必要的复杂性。
-
未正确处理AST节点类型导致编译失败。
-
在模板中忘记卫生性可能捕获调用作用域中的非预期标识符。
-
创建过于复杂的宏使代码难以理解和维护。
-
未验证宏输入导致宏展开时混淆的错误消息。
-
混合运行时和编译时代码而没有静态块导致错误。
-
假设AST结构而不检查节点类型在输入不同时中断。
-
未使用结果变量在模板中意外返回最后语句。
-
创建过于神奇的DSL降低代码可读性和可维护性。
-
忘记从宏返回节点导致空代码生成。
何时使用此技能
应用模板以实现零开销抽象,替换重复模式。
使用宏以基于编译时信息转换或生成代码。
利用编译时计算进行常量、验证和构建时优化。
为需要特定表示法的领域特定问题创建DSL。
为序列化、getter或模式匹配等样板代码生成代码。
使用元编程以实现零运行时成本的高性能抽象。