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"
我一直在尝试制作 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"