LemongrabThree
LemongrabThree

Reputation: 129

Nim Macros: How do I Name Body Parameter

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

Answers (1)

haxscramper
haxscramper

Reputation: 775

Solution 1

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"

Solution 2

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

Related Questions