Nim 宏:如何命名 Body 参数

Nim Macros: How do I Name Body Parameter

我一直在尝试制作 Neel 的更改版本,它使用 Jester 并添加了功能。在注册了一些可以方便地从 front-end 调用的程序后,您可以使用一个名为 startApp 的宏启动 Neel 应用程序,它具有以下签名:

macro startApp*(startURL, assetsDir: string, portNo: int = 5000,
                position: array[2, int] = [500,150], size: array[2, int] = [600,600],
                chromeFlags: seq[string] = @[""], appMode: bool = true) =
...

startApp 使用 router 宏和一些 hard-coded 路由创建 Jester 路由器:

router theRouter:
  get "/":
    resp(Http200,NOCACHE_HEADER,readFile(getCurrentDir() / `assetsDir` / startURL`))#is this most efficient?
  get "/neel.js":
    ...

现在我一直在尝试修改它并添加一个参数以允许 startApp 的调用者通过未类型化的“body”类型的参数(是否有顺便给这个起个名字?),像这样:

macro startApp*(startURL, assetsDir: string, portNo: int = 5000,
                position: array[2, int] = [500,150],size: array[2, int] = [600,600],
                chromeFlags: seq[string] = @[""], appMode: bool = true,
                extraRoutes : untyped
               )=

...所以你可以做到

startApp(startUrl="index.html", assetsDir="web", appMode=false):
  get "/extra.js":
    resp(Http200,`noCacheHeader`,"console.log('loaded extra.js')")
  post "/login":
    ...

但是现在上面的代码会报错

Error: type mismatch: got <string, void>
but expected one of: 
proc get[T](self: Option[T]): lent T
first type mismatch at position: 1

这意味着编译器正在尝试评估 get 表达式,而不是简单地将其未处理的语法树传递给 startApp,这与无类型宏参数的整点相反。我发现如果传递了所有参数,它就可以正常工作。所以我想我没有命名 body 的事实导致 Nim 认为我必须尝试将它传递给 portNo 或其他东西。很公平。但是我现在怎么办?有什么办法可以像 extraRoutes=... 一样吗?我试过 extraRoutes=quote do: ... 和类似的方法,但我找不到任何可行的方法。

所以...这可以解决吗?还是我必须使用 hack,比如手动传递默认参数的副本?

如果您有任何更好的实施想法,我很乐意,但请详细说明。我总共花了大约五个小时,我已经尝试过,例如事先做一个 StmtList 而不是在 startApp 中捆绑这个额外的路由业务,但放弃了,因为错误更加神秘。

解决方案 1

可以将默认参数传递给宏,但对于块来说它似乎不是特别漂亮:

import std/macros

macro optArgs(arg1: static[string], arg2: static[string] = "hello", arg3: typed = nil, body: untyped = nil) =
  if not isNil(body):
    echo body.treeRepr()


optArgs(
  "test", 
  body = (
    quote do:
      some untyped arguments that are not checked
  )
)

optArgs("test")
optArgs("test", arg3 = int)
optArgs("test", body = int)

产出

Call
  Ident "quote"
  StmtList
    Command
      Command
        Ident "some"
        Command
          Ident "untyped"
          Command
            Ident "arguments"
            Command
              Ident "that"
              Ident "are"
      Prefix
        Ident "not"
        Ident "checked"
NilLit
NilLit
Ident "int"

如果您想使用 call(<arguments>): body 语法传递代码块,我建议采用以下方法:接受元组语法(可以混合位置和命名参数)并执行一些 post - 处理自己。在这种特殊情况下,您将获得 (Par (Ident "positional") (ExprColonExpr (Ident "test") (IntLit 123))) 作为参数列表,可以对其进行处理以获得必要的参数,或者您可以调用 error 来指示误用。

macro namedArgs(arglist: untyped, body: untyped) =
  echo argList.lispRepr()
  
namedArgs (positional, test: 123):
  echo "ACtual body"

解决方案 2

应该注意的一件事是,如果参数传递不正确(如 Error: undeclared identifier: 'positional'),类似元组的参数方法会产生非常神秘的错误。这可以使用

修复
macro vras(arglist: varargs[untyped]) =
  echo argList.lispRepr()
  
vras(positional, test = 123):
  echo "ACtual body"

结果为您提供以下树,但在这种情况下,您必须手动实现所有参数处理。

Arglist
  Ident "positional"
  ExprEqExpr
    Ident "test"
    IntLit 123
  StmtList
    Command
      Ident "echo"
      StrLit "ACtual body"