Macro to generate 2 keyword function parameters

Let’s say I have this function

import Dates: DateTime
function myfun1(; ena::Int=1, offsettime::DateTime, entrytime::DateTime, dio::Int)
    return nothing
end

with the parameters being

:($(Expr(:parameters, :($(Expr(:kw, :(ena::Int), 1))), :(offsettime::Main.DateTime), :(entrytime::Main.DateTime), :(dio::Int))))

I wanted to create a macro that will automate writing offsetime, and entrytime.

When I do it one by one it works:

macro recvtime1()
    return :($(esc(:offsettime))::DateTime)
end
macro recvtime2()
    return :($(esc(:entrytime))::DateTime)
end
function myfun2(; ena=1, @recvtime1, @recvtime2, dio=2)
    return nothing
end

But when I do it together it complains for what I think is an automatically generated parenthesis:

macro recvtime()
    return :($(esc(:offsettime))::DateTime, $(esc(:entrytime))::DateTime)
end

The error:

julia> function myfun3(; ena=1, @recvtime, dio=2)
           return nothing
       end
ERROR: syntax: invalid keyword argument syntax "(offsettime::Main.DateTime, entrytime::Main.DateTime)" around REPL[47]:1
Stacktrace:
 [1] top-level scope
   @ REPL[47]:1

Is there a way I can adapt @recvtime such that myfun3 could work as is ?

@macroexpand indeed shows the extra parenthesis. I think if I can get them out it should work but I don’t see how.

julia> @macroexpand @recvtime
:((offsettime::Main.DateTime, entrytime::Main.DateTime))

I know I can pass to the macro the whole keyword parameter list and just return an Expr(:parameters, ) with adding the two keywords, but I prefer not to operate on that level.

I would not expect this to be possible, since a macro returns a single Expression, but you’d need two.

Put differently, compare the ASTs of

julia> using MacroTools: prewalk, rmlines  # To get rid of the line number clutter

julia> ex_macro = prewalk(rmlines, :(function f(@m, c) end)); dump(ex_macro)
Expr
  head: Symbol function
  args: Array{Any}((2,))
    1: Expr
      head: Symbol call
      args: Array{Any}((3,))
        1: Symbol f
        2: Expr
          head: Symbol macrocall
          args: Array{Any}((2,))
            1: Symbol @m
            2: Nothing nothing
        3: Symbol c
    2: Expr
      head: Symbol block
      args: Array{Any}((0,))

and

julia> ex_goal = prewalk(rmlines, :(function f(a, b, c) end)); dump(ex_goal)
Expr
  head: Symbol function
  args: Array{Any}((2,))
    1: Expr
      head: Symbol call
      args: Array{Any}((4,))
        1: Symbol f
        2: Symbol a
        3: Symbol b
        4: Symbol c
    2: Expr
      head: Symbol block
      args: Array{Any}((0,))

We need the macro m to alter ex_macro.args[1].args, while it can only see ex_macro.args[1].args[2].

1 Like

Yeah. I had the impression this might be impossible to do. And splatting with ... doesn’t seem to do the trick as well.

I know I can pass to the macro the whole keyword parameter list and just return an Expr(:parameters, ) with adding the two keywords, but I prefer not to operate on that level.

I ended up passing the whole function to the macro irregardless my preference:

macro recvtime(funexpr)
    parametersdad = funexpr.args[1]
    # search
    ff = findfirst(ex -> Base.isexpr(ex, :parameters), parametersdad.args)
    if isnothing(ff)
        newpars = Expr(:parameters, :($(esc(:offsettime))::DateTime), :($(esc(:entrytime))::DateTime) )
        insert!(funexpr.args[1].args, 2, newpars)
    else
        exprpar = parametersdad.args[ff]
        newpars = Expr(:parameters, exprpar.args..., :($(esc(:offsettime))::DateTime), :($(esc(:entrytime))::DateTime) )
        funexpr.args[1].args[ff] = newpars
    end
    return funexpr
end

Puts the two extra keyword parameters in the function

julia> @recvtime function myfun3(; ena=1, dio=2)
           return nothing
       end
2 Likes