JSRUN 用代码说话

宏使先进的编译时代码转换,但是它并不改变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
JSRUN闪电教程系统是国内最先开创的教程维护系统, 所有工程师都可以参与共同维护的闪电教程,让知识的积累变得统一完整、自成体系。 大家可以一起参与进共编,让零散的知识点帮助更多的人。
X
支付宝
9.99
无法付款,请点击这里
金额: 0
备注:
转账时请填写正确的金额和备注信息,到账由人工处理,可能需要较长时间
如有疑问请联系QQ:565830900
正在生成二维码, 此过程可能需要15秒钟