Reputation: 129
I've been trying to make an altered Version of Neel, which uses Jester and adds functionality. After registering some procedures that can be conveniently called from the front-end, you start the Neel app with a macro called startApp
, which has this signature:
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
creates a Jester router with the router
macro and some hard-coded routes:
router theRouter:
get "/":
resp(Http200,NOCACHE_HEADER,readFile(getCurrentDir() / `assetsDir` / startURL`))#is this most efficient?
get "/neel.js":
...
Now I've been trying to modify this and add a parameter to allow the caller of startApp
to pass in extra routes, via an untyped "body" sort of parameter (is there a name for this, by the way?), like this:
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
)=
... so you could do
startApp(startUrl="index.html", assetsDir="web", appMode=false):
get "/extra.js":
resp(Http200,`noCacheHeader`,"console.log('loaded extra.js')")
post "/login":
...
But now the above code leads to an error
Error: type mismatch: got <string, void>
but expected one of:
proc get[T](self: Option[T]): lent T
first type mismatch at position: 1
which means that the compiler is trying to evaluate the get
expression instead of simply passing its unprocessed syntax tree into startApp
, in contrast to The Whole Point of untyped macro arguments. I have figured out that it works fine if all the parameters are passed. So I guess the fact that I'm not naming the body is causing Nim to think I must be trying to pass it to portNo or something. Fair enough. But what do I do now? Is there any way I can do like extraRoutes=...
here? I tried doing extraRoutes=quote do: ...
and similar, but I couldn't find any way that works.
So... can this be fixed? Or will I have to go with a hack like manually passing in copies of the default args?
And if you have any better ideas for implementing this, I'm open, but please be detailed. I've been at this for something like five hours in total, I've already tried e.g. making a StmtList
beforehand instead of tying this extra route business up in startApp
, but gave up on that because the errors were even more cryptic.
Upvotes: 1
Views: 344
Reputation: 775
Default arguments can be passed to macro, but for blocks it does not seem particularly pretty:
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)
Outputs
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"
If you want to pass a block of code using call(<arguments>): body
syntax, I would suggest the following approach: accept a syntax for tuple (can mix positional and named arguments) and doing some post-processing yourself. In this particular case you would get (Par (Ident "positional") (ExprColonExpr (Ident "test") (IntLit 123)))
as an arglist, which can be processed to get necessary arguments, or you can call error to indicate misuse.
macro namedArgs(arglist: untyped, body: untyped) =
echo argList.lispRepr()
namedArgs (positional, test: 123):
echo "ACtual body"
One thing that should be noted is that tuple-like argument approach would give quite cryptic errors if argument is passed incorrectly (like Error: undeclared identifier: 'positional'
). This can be fixed using
macro vras(arglist: varargs[untyped]) =
echo argList.lispRepr()
vras(positional, test = 123):
echo "ACtual body"
Which gives you the following tree as a result, but in this case, you would have to implement all argument handling manually.
Arglist
Ident "positional"
ExprEqExpr
Ident "test"
IntLit 123
StmtList
Command
Ident "echo"
StrLit "ACtual body"
Upvotes: 2