2

I have the following macro and it should allow me to build a struct with one or 2 arguments only!

macro baseStruct(name, arg)
    if length(arg.args)==2 && isa(arg.args[1],Symbol) || length(arg.args)==1
        aakws = Pair{Symbol,Any}[]
        defaultValues=Array{Any}(nothing,1)
        field_define(aakws,defaultValues,1,arg,first=true)
        :(struct $name
            $(arg.args[1])
            function $name(;$(arg.args[1])=$(defaultValues[1]))
             new(check($name,$(arg.args[1]);$aakws...))
            end
        end)
    else length(arg.args)==2 && !isa(arg.args[1],Symbol)
        aakws1 = Pair{Symbol,Any}[]
        aakws2 = Pair{Symbol,Any}[]
        defaultValues=Array{Any}(nothing,2)
        field_define(aakws1,defaultValues,1,arg)
        field_define(aakws2,defaultValues,2,arg)
        :(struct $name
            $(arg.args[1].args[1])
            $(arg.args[2].args[1])
            function $name(;$(arg.args[1].args[1])=$(defaultValues[1]),$(arg.args[2].args[1])=$(defaultValues[2]))
                new(check($name,$(arg.args[1].args[1]);$aakws1...),check($name,$(arg.args[2].args[1]);$aakws2...))
            end
        end)
@baseStruct test(
    (arg1,(max=100.0,description="this arg1",min=5.5)),
    (arg2,(max=100,default=90))
)

The macro will expand to:

struct test 
  arg1
  arg2
end

and the following instance:

test1=test(arg1=80.5)

should give:

test1(arg1=80.5,arg2=90)

#check_function returns the argument. It allows me to check the argument in a specific way.

check(str,arg;type=DataType,max=nothing,min=nothing,default=nothing,description="")
      #do somthing
   return arg
end

#field_define_function it takes the second parameter from the macro as Expr. and convert it to a array with Pair{Symbol, Any} and then assign it to aakws.

I can extend this Code for more arguments, but as you can see it will be so long and complex. Have anybody a tip or other approach to implement this code for more/unultimate arguments in a more efficient way?

desertnaut
  • 57,590
  • 26
  • 140
  • 166
ahm5
  • 633
  • 5
  • 9

1 Answers1

2

I don't have the code for field_define or check, so I can't work your example directly. Instead I'll work with a simpler macro that demonstrates how you can splat-interpolate a sequence of Expr into a quoted Expr:

                     # series of Expr like :(a::Int=1)
macro kwstruct(name, arg_type_defaults...)
                 # :(a::Int)
    arg_types = [x.args[1]
                 for x in arg_type_defaults]
            # :a
    args = [y.args[1]
           for y in arg_types]
    # build :kw Expr because isolated :(a=1) is parsed as assignment
    arg_defaults = [Expr(:kw, x.args[1].args[1], x.args[2])
                    for x in arg_type_defaults]
    
    # splatting interpolation into a quoted Expr
    :(struct $name
          $(arg_types...)
          function $name(;$(arg_defaults...))
              new($(args...))
          end
      end
     )
end

This macro expands @kwstruct D a::Int=1 b::String="12" to:

struct D
    a::Int
    b::String
    function D(; a = 1, b = "12")
        new(a, b)
    end
end

And you can put as many arguments as you want, like @kwstruct(F, a::Int=1, b::String="12", c::Float64=1.2).

P.S. Splatting interpolation $(iterable...) only works in quoted Expr, which look like :(___) or quote ___ end. If you're constructing with an Expr() call, just use splatting:

vars = (:b, :c)
exq = :( f(a, $(vars...), d) )           # :(f(a, b, c, d))
ex  = Expr(:call, :f, :a, vars..., :d)   # :(f(a, b, c, d))
exq == ex                                # true
BatWannaBe
  • 4,330
  • 1
  • 14
  • 23