宏
宏使先进的编译时代码转换,但是它并不改变nim的语法。然而,这没有真正的限制,因为毕竟nim的语法是足够灵活的。宏必须在nim纯代码中实现如果外部函数接口(FFI)不在编译器中启用,但是除了那个限制(这一点在未来会消失),你可以写任何种类的nim代码,编译器将在编译的时候运行它。
有两种方法来创建一个宏,一种:生成nim的源代码,让编译器解析它;另一种是:你为编译器手动创建一个抽象语法树(AST)。为了创建AST,需要知道Nim怎样将具体语法转换为抽象语法树(AST)。AST在宏模块记录。
一旦宏创建完成,有两种方法调用它:
- 像调用过程一样调用宏(如:表达宏)
- 用特殊的macrostmt语法调用宏(声明宏)
表达宏
下面的例子实现了一个功能强大的debug命令,接受数目可变的参数:
# to work with Nim syntax trees, we need an API that is defined in the
# ``macros`` module: 为了使用nim语法树,我们需要一个被定义在 ``macros``模块的API
import macros
macro debug(n: varargs[expr]): stmt =
# 'n'是一个nim的AST包含一个表达式列表;这个宏返回一个语句列表
# this macro returns a list of statements:
result = newNimNode(nnkStmtList, n)
# 迭代器覆盖任何参数它被传递给这个宏
for i in 0..n.len-1:
#给语句列表添加一个调用,它写出表达式
# `toStrLit`将一个AST转换为它的字符串表示
result.add(newCall("write", newIdentNode("stdout"), toStrLit(n[i])))
# 给语句列表添加一个调用,它输出":"
result.add(newCall("write", newIdentNode("stdout"), newStrLitNode(": ")))
# 给语句列表添加一个调用,它输出表达式的值
result.add(newCall("writeln", newIdentNode("stdout"), n[i]))
var
a: array[0..10, int]
x = "some string"
a[0] = 42
a[1] = 45
debug(a[0], a[1], x)
宏调用拓展:
write(stdout, "a[0]")
write(stdout, ": ")
writeln(stdout, a[0])
write(stdout, "a[1]")
write(stdout, ": ")
writeln(stdout, a[1])
write(stdout, "x")
write(stdout, ": ")
writeln(stdout, x)
语句宏
声明宏的定义和表达宏一样。然而,它们通过一个表达式后跟一个冒号被调用
下面的示例概述,从一个正则表达式生成一个词法分析器的宏:
macro case_token(n: stmt): stmt =
#从一个正规表达式构造一个语法分析器,没有实现
# ... (implementation is an exercise for the reader :-)
discard
case_token: # this colon tells the parser it is a macro statement
case_token: # 这个冒号通知解析器,这是一个宏语句
of r"[A-Za-z_]+[A-Za-z_0-9]*":
return tkIdentifier
of r"0-9+":
return tkInteger
of r"[\+\-\*\?]+":
return tkOperator
else:
return tkUnknown
建立你的第一个宏
给一个footstart写宏,我们将展示如何将你的典型的动态代码转换为静态编译的代码,为了练习我们使用下面的代码片段作为出发点:
import strutils, tables
proc readCfgAtRuntime(cfgFilename: string): Table[string, string] =
let
inputString = readFile(cfgFilename)
var
source = ""
result = initTable[string, string]()
for line in inputString.splitLines:
# Ignore empty lines
if line.len < 1: continue
var chunks = split(line, ',')
if chunks.len != 2:
quit("Input needs comma split values, got: " & line)
result[chunks[0]] = chunks[1]
if result.len < 1: quit("Input file empty!")
let info = readCfgAtRuntime("data.cfg")
when isMainModule:
echo info["licenseOwner"]
echo info["licenseKey"]
echo info["version"]
想必这段代码可以用在商业软件,读取配置文件展示买软件的人的信息。这个外部文件将通过一个网上购物车网站包含许可信息的程序生成:
version,1.1
licenseOwner,Hyori Lee
licenseKey,M1Tl3PjBWO2CC48m