朱莉娅:宏如何知道它们的参数何时停止?

Julia: How do macros know when their arguments stop?

这是一个初学者问题,但我在文档(或 Whosebug 上)中找到答案时遇到了一些问题,我认为它对其他人也有帮助。问题很简单:

Julia 宏调用如何知道宏的参数何时完成?

用括号标明论点时,应该直截了当(我觉得……)。然而,在另一种情况下,它似乎更微妙。我怀疑它与“表达式数量”有关(这本身可能是一个有点棘手的概念)但我不确定并且我想拥有记录在案的官方规则。

为什么我认为这不明显的一些例子:

julia> macro a(arg...)
print(arg)
end;
julia> @a gg=3 if true # the :(gg = 3) is the first argument of the macro
              print("val")
              a = 1;;; # the macro does not see the semicolons (which makes sense to me)
              end  # the if statement is the second argument of the macro
# note: replacing "true" by "@a true" makes the number of arguments of 
# the second macro depend on the following newline being there or not

# next: the first two assignments are two arguments. The last assignment is not an argument at all
julia> @a a = 4 f=3; aaa = "asdf" 

julia> @a af = 4 (f=3; aaa = "asdf") # two arguments; the second one is a quote block
julia> @a af = 4; (f=3; aaa = "asdf") # one argument.

# edit:
julia> @a @a aa # One argument, counterexample to last claim
(:(#= REPL[37]:1 =# @a aa),)

例如,是否可以将两个连续的 if 块作为单独的参数?

我似乎注意到的一条规则是(当不使用圆括号括起参数时)如果宏部分 (@a) 被删除,任何具有多个参数的宏调用将变为无效语法。一般来说是这样吗?

编辑: 否,请参阅代码段中的反例。

规则:

@name expr1 expr2 ...

宏调用是一个宏,提供所有白色 space 分隔表达式,直到到达语句末尾。通常,当您到达换行符、; 或其他一些字符(如 ]) 表示语句结束时,语句结束。

以下是一些使用 @a 宏的示例。

julia> @a @a aa # here @a aa produces one expression which is passed to outer @a call
(:(#= REPL[37]:1 =# @a aa),)

julia> @a x = 1 y = 2 # two expressions are passed to a macro
(:(x = 1), :(y = 2))

julia> @a x = 1 y = 2; # the same, but we terminated the statement using ;
(:(x = 1), :(y = 2))

julia> @a x = 1 y = 2; 1; # 1 does not go into the macro as ; terminated the statement
(:(x = 1), :(y = 2))

julia> @a (@a x) y # outer @a gets two expressions as we delimited the @a x with parentheses
(:(#= REPL[40]:1 =# @a x), :y)

julia> @a begin
       @a x
       end y # similar but with begin-end block
(quote
    #= REPL[44]:2 =#
    #= REPL[44]:2 =# @a x
end, :y)

julia> @a [@a x] y # this time we used square brackets
(:([#= REPL[39]:1 =# @a(x)]), :y)

我认为在上面的例子中,例如重要的是要看到 @a x = 1 y = 2 Julia 解析器将 x = 1 视为一个整体,尽管在 = 之间存在 space,因为它将其视为单个表达式。

另请注意,您只能将有效表达式的内容提供给宏,例如:

julia> @a =
ERROR: syntax: unexpected "="

失败为:

julia> =
ERROR: syntax: unexpected "="

(在使用 Julia 宏设计 DSL 时,这实际上是一个重要的考虑因素,因为您必须确保您设计的语法包含有效的表达式)

另请注意,宏是 all 白色 space 分隔表达式,因此这是一个错误:

julia> macro b(x)
       print(x)
       end
@b (macro with 1 method)

julia> @b 1 2
ERROR: LoadError: MethodError: no method matching @b(::LineNumberNode, ::Module, ::Int64, ::Int64)

最后注意:

julia> @a [@a x] y
(:([#= REPL[64]:1 =# @a(x)]), :y)
julia> @a[@a x] y
ERROR: syntax: extra token "y" after end of expression

因为 Julia 在 @a 之后以一种特殊的方式解析带有 [ 的宏调用,在这种情况下仅将一个参数传递给宏。