Nim教程【十五】【完结】
模版
模版是Nim语言中的抽象语法树,它是一种简单的替换机制,在编译期被处理
这个特性使Nim语言可以和C语言很好的运行在一起
像调用一个方法一样调用一个模版
请看如下代码:
1 2 3 4 5 | template `! = ` (a, b: expr): expr = # this definition exists in the System module not (a = = b) assert ( 5 ! = 6 ) # the compiler rewrites that to: assert(not (5 == 6)) |
类似下面这些符号,其实都是模版
=,>,>=,in,notin
这一个好处,如果你重载==操作符,
!=运算符也就自动提供出来了
并可以做正确的事!
A>B被变换到b<a。 b in a被变换成含有(b,a)。 notin和IsNot运算有明显的意义。
模板为懒人提供了很大帮助。考虑一个简单的PROC进行日志记录:
1 2 3 4 5 6 7 8 9 | const debug = true proc log(msg: string) {.inline.} = if debug: stdout.writeln(msg) var x = 4 log( "x has the value: " & $x) |
这段代码有个缺点,如果有一天把debug变量设置为了false
那么&操作和$操作还是会执行的,而这些操作的资源消耗是非常大的。
(调用方法的时候,会先执行方法参数位置处的表达式)
这个时候就可以考虑用模版来解决这个问题:
1 2 3 4 5 6 7 8 9 | const debug = true template log(msg: string) = if debug: stdout.writeln(msg) var x = 4 log( "x has the value: " & $x) |
模版的参数类型可以是普通的类型,也可以是表达式;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | template withFile(f: expr, filename: string, mode: FileMode, body: stmt): stmt {.immediate.} = let fn = filename var f: File if open (f, fn, mode): try : body finally : close(f) else : quit( "cannot open: " & fn) withFile(txt, "ttempl3.txt" , fmWrite): txt.writeln( "line 1" ) txt.writeln( "line 2" ) |
在这个例子中,两个writeln语句绑定到的是body参数
这段代码可以帮助开发人员避免“忘记关闭文件”的错误
宏
Nim语言的宏提供了一个高级的编译期的替换功能
Nim语言的宏不能替换语言本身的语法,
但这并不是什么缺憾,因为Nim语言本身已经足够灵活了。
如果外部接口在编译期不可用,那么你就必须用纯Nim语言写宏
(这估计就是在说Nim和C混合编程的时候要注意的事情)
你可以使用Nim代码编写任何形式的宏,编译器会在编译期把他们翻译成真正的Nim代码。
可以有两种办法写一个宏
用Nim代码编写宏,让编译器解析它
手动创建抽象语法树AST,你告诉编译器
如果你想建立抽象语法树AST,那么你一定要知道Nim语言的语法是怎么转换为抽象语法树的
在N关于宏的帮助说明文档,你可以找到关于AST的帮助说明
你一旦写了一个宏,
那么你有两种办法可以使用这个宏
像调用一个方法一样调用一个宏
通过一种特殊的语法调用宏(macrostmt声明宏)
表达式宏
下面的代码实现了一个可变参数数量的宏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | # to work with Nim syntax trees, we need an API that is defined in the # ``macros`` module: import macros macro debug(n: varargs[expr]): stmt = # `n` is a Nim AST that contains a list of expressions; # this macro returns a list of statements: result = newNimNode(nnkStmtList, n) # iterate over any argument that is passed to this macro: for i in 0. .n. len - 1 : # add a call to the statement list that writes the expression; # `toStrLit` converts an AST to its string representation: result.add(newCall( "write" , newIdentNode( "stdout" ), toStrLit(n[i]))) # add a call to the statement list that writes ": " result.add(newCall( "write" , newIdentNode( "stdout" ), newStrLitNode( ": " ))) # add a call to the statement list that writes the expressions value: 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) |
编译完之后,最终展开的代码为:
1 2 3 4 5 6 7 8 9 10 11 | 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) |
声明宏
声明宏在某种意义上就是表达式宏
声明宏是用冒号表达式调用的
下面的例子展示了正则表达式词法分析宏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | macro case_token(n: stmt): stmt = # creates a lexical analyzer from regular expressions # ... (implementation is an exercise for the reader :-) discard case_token: # this colon tells the parser it is a macro statement 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 |
后面还有个例子,不翻译了
至此整个系列写完了
喜欢的请点推荐
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统