查看源代码 宏 (Elixir v1.16.2)
用于操作 AST 和实现宏的函数。
宏是编译时结构,它们接收 Elixir 的 AST 作为输入,并返回 Elixir 的 AST 作为输出。
此模块中的许多函数的存在正是为了与 Elixir AST 协作,以便遍历、查询和转换它。
让我们看一个简单的示例,它展示了函数和宏之间的区别
defmodule Example do
defmacro macro_inspect(value) do
IO.inspect(value)
value
end
def fun_inspect(value) do
IO.inspect(value)
value
end
end
现在让我们试试
import Example
macro_inspect(1)
#=> 1
#=> 1
fun_inspect(1)
#=> 1
#=> 1
到目前为止,它们的运行方式相同,因为我们传递了一个整数作为参数。但让我们看看当我们传递一个表达式时会发生什么
macro_inspect(1 + 2)
#=> {:+, [line: 3], [1, 2]}
#=> 3
fun_inspect(1 + 2)
#=> 3
#=> 3
宏接收作为参数给出的代码的表示,而函数接收作为参数给出的代码的结果。宏必须返回代码表示的超集。有关更多信息,请参见 input/0
和 output/0
。
要了解有关 Elixir AST 的更多信息以及如何以编程方式构建它们,请参见 quote/2
。
评估代码
此模块中的函数不评估代码。事实上,从宏中评估代码通常是一种反模式。有关代码评估,请参见
Code
模块。
总结
函数
将给定字符串转换为 CamelCase 格式。
根据其可能的 AST 位置对 atom
进行分类。
在 caller
中编译时应用 mod
、function
和 args
。
对 Kernel.dbg/2
的默认后端。
将本地或远程调用分解为其远程部分(如果提供)、函数名称和参数列表。
递归地转义一个值,以便可以将其插入语法树中。
接收一个 AST 节点,并对其进行扩展,直到它不再能够被扩展。
使用给定的 env
扩展 ast
中的所有字面量。
使用给定的 acc
和 fun
扩展 ast
中的所有字面量。
接收一个 AST 节点,并对其进行一次扩展。
使用 Macro.var/2
为给定数量的必需参数变量生成 AST 节点。
使用 Macro.unique_var/2
为给定数量的必需参数变量生成 AST 节点。
根据不同的源格式检查 atom
。
如果给定的名称和元数是运算符,则返回 true
。
返回 ast
中节点的路径,对于该节点,fun
返回一个真值。
将 expr
管道到给定 position
上的 call_args
中。
此函数的行为类似于 prewalk/2
,但对引用的表达式执行深度优先的后序遍历。
此函数的行为类似于 prewalk/3
,但对引用的表达式执行深度优先的后序遍历,使用累加器。
返回一个可枚举对象,该对象以深度优先的后序遍历遍历 ast
。
对引用的表达式执行深度优先的先序遍历。
对引用的表达式执行深度优先的先序遍历,使用累加器。
返回一个可枚举对象,该对象以深度优先的先序遍历遍历 ast
。
如果给定的引用表达式表示一个引用字面量,则返回 true
。
如果给定的名称和元数是特殊形式,则返回 true
。
在给定的 env
中扩展由 module
给出的结构。
将给定的表达式 AST 转换为字符串。
将给定的表达式 AST 转换为字符串。
使用累加器对引用的表达式执行深度优先的遍历。
将给定的参数转换为使用下划线斜杠格式的字符串。
取消转义字符串中的字符。
根据给定的映射取消转义字符串中的字符。
生成一个 AST 节点,该节点表示由原子 var
和 context
给出的唯一变量。
将管道表达式分解为列表。
如果节点包含元数据,则将给定的函数应用于节点元数据。
验证给定的表达式是有效的引用表达式。
生成一个 AST 节点,该节点表示由原子 var
和 context
给出的变量。
类型
@type captured_remote_function() :: (... -> any())
以 &Mod.fun/arity 格式捕获的远程函数
宏的输入
@type metadata() :: keyword()
AST 元数据的关键字列表。
Elixir AST 中的元数据是值的关键字列表。可以使用任何键,编译器的不同部分可能会使用不同的键。例如,宏接收到的 AST 将始终包含 :line
注释,而由 quote/2
发出的 AST 将只有在提供 :line
选项时才具有 :line
注释。
以下元数据键是公开的
:context
- 定义生成 AST 的上下文。例如,quote/2
将包含调用quote/2
的模块作为上下文。这通常用于区分常规代码和由宏或quote/2
生成的代码。:counter
- 用于变量卫生管理的变量计数器。就编译器而言,每个变量都由name
和metadata[:counter]
或name
和context
的组合来识别。:from_brackets
- 用于确定对Access.get/3
的调用是否来自括号语法。:from_interpolation
- 用于确定对Kernel.to_string/1
的调用是否来自插值。:generated
- 代码是否应被视为由编译器生成的。这意味着编译器和 Dialyzer 等工具可能不会发出某些警告。:if_undefined
- 如何扩展未定义的变量。如果要将变量变为不带警告的零元调用,请将其设置为:apply
,或者设置为:raise
:keep
- 由quote/2
与选项location: :keep
一起使用,以注释引用源的文件和行号。:line
- AST 节点的行号。请注意,行信息将从引用的代码中丢弃,但可以通过:line
选项重新启用。
以下元数据键由 Code.string_to_quoted/2
启用
:closing
- 包含有关闭合对的元数据,例如元组或映射中的}
,或带括号的函数调用中的闭合)
(当:token_metadata
为真时)。如果函数调用附加了 do-end 块,则其元数据位于:do
和:end
元数据下:column
- AST 节点的列号(当:columns
为真时)。请注意,列信息始终从引用的代码中丢弃。:delimiter
- 包含 sigil、字符串和字符列表的开始分隔符,作为字符串(例如"{"
、"/"
、"'"
等):format
- 当原子定义为关键字时,将其设置为:keyword
:do
- 包含有关带do
-end
块的函数调用中do
位置的元数据(当:token_metadata
为真时):end
- 包含有关带do
-end
块的函数调用中end
位置的元数据(当:token_metadata
为真时):end_of_expression
- 表示表达式结束的有效位置(当:token_metadata
为真时)。这仅适用于__block__
的直接子项,它要么是换行符的位置,要么是;
字符的位置。__block__
的最后一个表达式没有此元数据。:indentation
- sigil heredoc 的缩进
以下元数据键是私有的
:alias
- 用于别名卫生管理。:ambiguous_op
- 用于改进编译器中的错误消息。:imports
- 用于导入卫生管理。:var
- 用于改进未定义变量的错误消息。
不要依赖它们,因为它们可能会在语言的未来版本中更改或完全删除。它们通常由 quote/2
和编译器用来提供卫生管理、更好的错误消息等功能。
如果你在 AST 元数据中引入了自定义键,请确保使用你的库或应用程序的名称作为前缀,这样它们就不会与将来可能由编译器引入的键发生冲突。
@type output() :: output_expr() | {output(), output()} | [output()] | atom() | number() | binary() | captured_remote_function() | pid()
宏的输出
@type t() :: input()
抽象语法树 (AST)
函数
将给定字符串转换为 CamelCase 格式。
此函数旨在将语言标识符/令牌转换为驼峰式,这就是它属于 Macro
模块的原因。不要将其用作将字符串转换为驼峰式的通用机制,因为它不支持 Unicode 或 Elixir 标识符中无效的字符。
示例
iex> Macro.camelize("foo_bar")
"FooBar"
iex> Macro.camelize("foo/bar")
"Foo.Bar"
如果存在大写字符,则不会以任何方式修改它们,作为一种保留首字母缩略词的机制
iex> Macro.camelize("API.V1")
"API.V1"
iex> Macro.camelize("API_SPEC")
"API_SPEC"
@spec classify_atom(atom()) :: :alias | :identifier | :quoted | :unquoted
根据其可能的 AST 位置对 atom
进行分类。
它返回以下原子之一
:alias
- 该原子表示一个别名:identifier
- 该原子可以作为变量或本地函数调用(以及作为未引用的原子):unquoted
- 该原子可以在其未引用的形式中使用,包括运算符和包含@
的原子:quoted
- 所有其他原子,只能在它们的引用的形式中使用
大多数运算符将是 :unquoted
,例如 :+
,但由于歧义,一些例外会返回 :quoted
,例如 :"::"
。使用 operator?/2
检查给定原子是否是运算符。
示例
iex> Macro.classify_atom(:foo)
:identifier
iex> Macro.classify_atom(Foo)
:alias
iex> Macro.classify_atom(:foo@bar)
:unquoted
iex> Macro.classify_atom(:+)
:unquoted
iex> Macro.classify_atom(:Foo)
:unquoted
iex> Macro.classify_atom(:"with spaces")
:quoted
在 caller
中编译时应用 mod
、function
和 args
。
当您想在编译时以编程方式调用宏时,会使用它。
@spec dbg(t(), t(), Macro.Env.t()) :: t()
对 Kernel.dbg/2
的默认后端。
此函数为 Kernel.dbg/2
提供默认后端。有关更多信息,请参阅 Kernel.dbg/2
文档。
此函数
- 打印有关给定
env
的信息 - 打印有关
code
及其返回值的信息(使用opts
检查项) - 返回评估
code
所返回的值
您可以直接调用此函数来构建 Kernel.dbg/2
后端,这些后端会回退到此函数。
如果给定 env
的上下文为 :match
或 :guard
,此函数会引发错误。
将本地或远程调用分解为其远程部分(如果提供)、函数名称和参数列表。
在提供无效的调用语法时返回 :error
。
示例
iex> Macro.decompose_call(quote(do: foo))
{:foo, []}
iex> Macro.decompose_call(quote(do: foo()))
{:foo, []}
iex> Macro.decompose_call(quote(do: foo(1, 2, 3)))
{:foo, [1, 2, 3]}
iex> Macro.decompose_call(quote(do: Elixir.M.foo(1, 2, 3)))
{{:__aliases__, [], [:Elixir, :M]}, :foo, [1, 2, 3]}
iex> Macro.decompose_call(quote(do: 42))
:error
iex> Macro.decompose_call(quote(do: {:foo, [], []}))
:error
递归地转义一个值,以便可以将其插入语法树中。
示例
iex> Macro.escape(:foo)
:foo
iex> Macro.escape({:a, :b, :c})
{:{}, [], [:a, :b, :c]}
iex> Macro.escape({:unquote, [], [1]}, unquote: true)
1
选项
:unquote
- 当为 true 时,此函数会保留unquote/1
和unquote_splicing/1
语句,实际上在转义时会取消转义内容。此选项仅在转义可能包含引用的片段的 AST 时有用。默认为 false。:prune_metadata
- 当为 true 时,会从转义的 AST 节点中删除元数据。请注意,此选项会更改转义代码的语义,并且仅应在转义 AST 时使用。默认为 false。例如,
ExUnit
存储每个断言的 AST,因此当断言失败时,我们可以向用户显示代码片段。没有此选项,每次编译测试模块时,我们都会获得模块字节码的不同 MD5,因为 AST 包含元数据(例如计数器),这些元数据特定于编译环境。通过修剪元数据,我们确保模块是确定性的,并减少了ExUnit
需要保留的数据量。仅保留最少的元数据,例如:line
和:no_parens
。
与 quote/2
的比较
escape/2
函数有时会与 quote/2
混淆,因为上面的示例在两者中都表现相同。主要区别是当要转义的值存储在变量中时最能说明问题。
iex> Macro.escape({:a, :b, :c})
{:{}, [], [:a, :b, :c]}
iex> quote do: {:a, :b, :c}
{:{}, [], [:a, :b, :c]}
iex> value = {:a, :b, :c}
iex> Macro.escape(value)
{:{}, [], [:a, :b, :c]}
iex> quote do: value
{:value, [], __MODULE__}
iex> value = {:a, :b, :c}
iex> quote do: unquote(value)
{:a, :b, :c}
@spec expand(input(), Macro.Env.t()) :: output()
接收一个 AST 节点,并对其进行扩展,直到它不再能够被扩展。
请注意,此函数不会遍历 AST,只会展开根节点。
此函数在内部使用 expand_once/2
。查看它以获取更多信息和示例。
@spec expand_literals(input(), Macro.Env.t()) :: output()
使用给定的 env
扩展 ast
中的所有字面量。
此函数主要用于从 AST 节点中删除编译时依赖项。在这种情况下,给定的环境通常被操纵以表示一个函数
Macro.expand_literals(ast, %{env | function: {:my_code, 1}})
目前,AST 中唯一可扩展的文字节点是别名,因此此函数仅扩展别名(并且它在文字中的任何地方都这样做)。
但是,在删除模块之间的编译时依赖项时要小心。如果您删除它们,但您仍然在编译时调用该模块,Elixir 将无法在模块更改时正确地重新编译它们。
使用给定的 acc
和 fun
扩展 ast
中的所有字面量。
fun
将使用可扩展的 AST 节点和 acc
调用,并且必须返回一个带有 acc
的新节点。这是 expand_literals/2
的通用版本,支持自定义扩展函数。请查看 expand_literals/2
以获取用例和陷阱。
@spec expand_once(input(), Macro.Env.t()) :: output()
接收一个 AST 节点,并对其进行一次扩展。
以下内容被扩展
- 宏(本地或远程)
- 别名被扩展(如果可能)并返回原子
- 编译环境宏(
__CALLER__/0
、__DIR__/0
、__ENV__/0
和__MODULE__/0
) - 模块属性读取器(
@foo
)
如果表达式无法扩展,它会返回表达式本身。此函数不会遍历 AST,只会展开根节点。
expand_once/2
仅执行一次扩展。查看 expand/2
以执行扩展,直到节点不再可扩展。
示例
在下面的示例中,我们有一个宏,它生成一个具有名为 name_length
的函数的模块,该函数返回模块名称的长度。此函数的值将在编译时计算,而不是在运行时计算。
考虑以下实现
defmacro defmodule_with_length(name, do: block) do
length = length(Atom.to_charlist(name))
quote do
defmodule unquote(name) do
def name_length, do: unquote(length)
unquote(block)
end
end
end
当像这样调用时
defmodule_with_length My.Module do
def other_function, do: ...
end
编译将失败,因为引用后的 My.Module
不是原子,而是如下所示的语法树
{:__aliases__, [], [:My, :Module]}
也就是说,我们需要将上面的别名节点扩展为原子,以便我们可以检索其长度。扩展节点并不简单,因为我们还需要扩展调用者别名。例如
alias MyHelpers, as: My
defmodule_with_length My.Module do
def other_function, do: ...
end
最终的模块名称将是 MyHelpers.Module
而不是 My.Module
。使用 Macro.expand/2
,这些别名会被考虑在内。本地和远程宏也会被扩展。我们可以将上面的宏改写为使用此函数,如下所示
defmacro defmodule_with_length(name, do: block) do
expanded = Macro.expand(name, __CALLER__)
length = length(Atom.to_charlist(expanded))
quote do
defmodule unquote(name) do
def name_length, do: unquote(length)
unquote(block)
end
end
end
@spec generate_arguments(0, context :: atom()) :: []
@spec generate_arguments(pos_integer(), context) :: [{atom(), [], context}, ...] when context: atom()
使用 Macro.var/2
为给定数量的必需参数变量生成 AST 节点。
请注意,参数不唯一。如果您以后想访问相同的变量,您可以使用相同的输入调用此函数。使用 generate_unique_arguments/2
生成不能被覆盖的唯一参数。
示例
iex> Macro.generate_arguments(2, __MODULE__)
[{:arg1, [], __MODULE__}, {:arg2, [], __MODULE__}]
@spec generate_unique_arguments(0, context :: atom()) :: []
@spec generate_unique_arguments(pos_integer(), context) :: [ {atom(), [{:counter, integer()}], context}, ... ] when context: atom()
使用 Macro.unique_var/2
为给定数量的必需参数变量生成 AST 节点。
示例
iex> [var1, var2] = Macro.generate_unique_arguments(2, __MODULE__)
iex> {:arg1, [counter: c1], __MODULE__} = var1
iex> {:arg2, [counter: c2], __MODULE__} = var2
iex> is_integer(c1) and is_integer(c2)
true
根据不同的源格式检查 atom
。
该原子可以根据它在 AST 中出现的三种不同格式进行检查:作为文字(:literal
)、作为键(:key
)或作为远程调用的函数名称(:remote_call
)。
示例
作为文字
文字包括普通原子、引用的原子、运算符、别名以及特殊的 nil
、true
和 false
原子。
iex> Macro.inspect_atom(:literal, nil)
"nil"
iex> Macro.inspect_atom(:literal, :foo)
":foo"
iex> Macro.inspect_atom(:literal, :<>)
":<>"
iex> Macro.inspect_atom(:literal, :Foo)
":Foo"
iex> Macro.inspect_atom(:literal, Foo.Bar)
"Foo.Bar"
iex> Macro.inspect_atom(:literal, :"with spaces")
":\"with spaces\""
作为键
将原子作为关键字列表或映射的键进行检查。
iex> Macro.inspect_atom(:key, :foo)
"foo:"
iex> Macro.inspect_atom(:key, :<>)
"<>:"
iex> Macro.inspect_atom(:key, :Foo)
"Foo:"
iex> Macro.inspect_atom(:key, :"with spaces")
"\"with spaces\":"
作为远程调用
将原子作为远程调用的函数名称进行检查。
iex> Macro.inspect_atom(:remote_call, :foo)
"foo"
iex> Macro.inspect_atom(:remote_call, :<>)
"<>"
iex> Macro.inspect_atom(:remote_call, :Foo)
"\"Foo\""
iex> Macro.inspect_atom(:remote_call, :"with spaces")
"\"with spaces\""
如果给定的名称和元数是运算符,则返回 true
。
示例
iex> Macro.operator?(:not_an_operator, 3)
false
iex> Macro.operator?(:.., 0)
true
iex> Macro.operator?(:+, 1)
true
iex> Macro.operator?(:++, 2)
true
iex> Macro.operator?(:..//, 3)
true
@spec path(t(), (t() -> as_boolean(term()))) :: [t()] | nil
返回 ast
中节点的路径,对于该节点,fun
返回一个真值。
路径是一个列表,从 fun
返回真值的节点开始,然后是它所有父节点。
如果 fun
仅返回假值,则返回 nil
。
当您想在 AST 中找到特定节点及其上下文,然后断言该节点的某些内容时,计算路径可能是一种有效的操作。
示例
iex> Macro.path(quote(do: [1, 2, 3]), & &1 == 3)
[3, [1, 2, 3]]
iex> Macro.path(quote(do: [1, 2]), & &1 == 5)
nil
iex> Macro.path(quote(do: Foo.bar(3)), & &1 == 3)
[3, quote(do: Foo.bar(3))]
iex> Macro.path(quote(do: %{foo: [bar: :baz]}), & &1 == :baz)
[
:baz,
{:bar, :baz},
[bar: :baz],
{:foo, [bar: :baz]},
{:%{}, [], [foo: [bar: :baz]]}
]
将 expr
管道到给定 position
上的 call_args
中。
此函数可用于实现 |>
等功能。例如,|>
本身是作为
defmacro left |> right do
Macro.pipe(left, right, 0)
end
expr
是表达式的 AST。call_args
必须是调用的 AST,否则此函数将引发错误。例如,考虑管道运算符 |>/2
,它使用此函数来构建管道。
即使表达式被管道传输到 AST,也不一定意味着 AST 是有效的。例如,您可以将一个参数管道传输到 div/2
,实际上将其转换为对 div/3
的调用,而 div/3
是一个默认情况下不存在的函数。除非在本地定义了 div/3
函数,否则代码将引发错误。
此函数的行为类似于 prewalk/2
,但对引用的表达式执行深度优先的后序遍历。
此函数的行为类似于 prewalk/3
,但对引用的表达式执行深度优先的后序遍历,使用累加器。
@spec postwalker(t()) :: Enumerable.t()
返回一个可枚举对象,该对象以深度优先的后序遍历遍历 ast
。
示例
iex> ast = quote do: foo(1, "abc")
iex> Enum.map(Macro.postwalker(ast), & &1)
[1, "abc", {:foo, [], [1, "abc"]}]
对引用的表达式执行深度优先的先序遍历。
返回一个新的 AST,其中每个节点都是调用 fun
在 ast
的每个对应节点上的结果。
示例
iex> ast = quote do: 5 + 3 * 7
iex> {:+, _, [5, {:*, _, [3, 7]}]} = ast
iex> new_ast = Macro.prewalk(ast, fn
...> {:+, meta, children} -> {:*, meta, children}
...> {:*, meta, children} -> {:+, meta, children}
...> other -> other
...> end)
iex> {:*, _, [5, {:+, _, [3, 7]}]} = new_ast
iex> Code.eval_quoted(ast)
{26, []}
iex> Code.eval_quoted(new_ast)
{50, []}
对引用的表达式执行深度优先的先序遍历,使用累加器。
返回一个元组,其中第一个元素是一个新的 AST,其中每个节点都是调用 fun
在每个对应节点上的结果,而第二个元素是最终的累加器。
示例
iex> ast = quote do: 5 + 3 * 7
iex> {:+, _, [5, {:*, _, [3, 7]}]} = ast
iex> {new_ast, acc} = Macro.prewalk(ast, [], fn
...> {:+, meta, children}, acc -> {{:*, meta, children}, [:+ | acc]}
...> {:*, meta, children}, acc -> {{:+, meta, children}, [:* | acc]}
...> other, acc -> {other, acc}
...> end)
iex> {{:*, _, [5, {:+, _, [3, 7]}]}, [:*, :+]} = {new_ast, acc}
iex> Code.eval_quoted(ast)
{26, []}
iex> Code.eval_quoted(new_ast)
{50, []}
@spec prewalker(t()) :: Enumerable.t()
返回一个可枚举对象,该对象以深度优先的先序遍历遍历 ast
。
示例
iex> ast = quote do: foo(1, "abc")
iex> Enum.map(Macro.prewalker(ast), & &1)
[{:foo, [], [1, "abc"]}, 1, "abc"]
如果给定的引用表达式表示一个引用字面量,则返回 true
。
原子和数字始终是字面量。二进制、列表、元组、映射和结构体只有在其所有项也都是字面量时才是字面量。
示例
iex> Macro.quoted_literal?(quote(do: "foo"))
true
iex> Macro.quoted_literal?(quote(do: {"foo", 1}))
true
iex> Macro.quoted_literal?(quote(do: {"foo", 1, :baz}))
true
iex> Macro.quoted_literal?(quote(do: %{foo: "bar"}))
true
iex> Macro.quoted_literal?(quote(do: %URI{path: "/"}))
true
iex> Macro.quoted_literal?(quote(do: URI.parse("/")))
false
iex> Macro.quoted_literal?(quote(do: {foo, var}))
false
如果给定的名称和元数是特殊形式,则返回 true
。
@spec struct!(module, Macro.Env.t()) :: %{ :__struct__ => module, optional(atom()) => any() } when module: module()
在给定的 env
中扩展由 module
给出的结构。
当结构体需要在编译时扩展,并且正在扩展的结构体可能已被编译也可能尚未被编译时,这很有用。此函数还能够扩展在正在编译的模块下定义的结构体。
如果结构体不可用,它将引发 CompileError
。从 Elixir v1.12 开始,调用此函数还会在给定结构体上添加一个导出依赖项。
将给定的表达式 AST 转换为字符串。
这是一个用于将 AST 转换为字符串的便利函数,它会丢弃原始代码的所有格式,并在 98 个字符处换行。请参阅 Code.quoted_to_algebra/2
作为具有更多格式控制的更底层函数。
示例
iex> Macro.to_string(quote(do: foo.bar(1, 2, 3)))
"foo.bar(1, 2, 3)"
将给定的表达式 AST 转换为字符串。
给定的 fun
被调用以用于 AST 中的每个节点,并带有两个参数:正在打印的节点的 AST 和该相同节点的字符串表示形式。此函数的返回值用作该 AST 节点的最终字符串表示形式。
此函数会丢弃原始代码的所有格式。
示例
Macro.to_string(quote(do: 1 + 2), fn
1, _string -> "one"
2, _string -> "two"
_ast, string -> string
end)
#=> "one + two"
@spec traverse(t(), any(), (t(), any() -> {t(), any()}), (t(), any() -> {t(), any()})) :: {t(), any()}
使用累加器对引用的表达式执行深度优先的遍历。
返回一个元组,其中第一个元素是一个新的 AST,而第二个元素是最终的累加器。新的 AST 是在先序阶段对 ast
的每个节点调用 pre
,而在后序阶段调用 post
的结果。
示例
iex> ast = quote do: 5 + 3 * 7
iex> {:+, _, [5, {:*, _, [3, 7]}]} = ast
iex> {new_ast, acc} =
...> Macro.traverse(
...> ast,
...> [],
...> fn
...> {:+, meta, children}, acc -> {{:-, meta, children}, [:- | acc]}
...> {:*, meta, children}, acc -> {{:/, meta, children}, [:/ | acc]}
...> other, acc -> {other, acc}
...> end,
...> fn
...> {:-, meta, children}, acc -> {{:min, meta, children}, [:min | acc]}
...> {:/, meta, children}, acc -> {{:max, meta, children}, [:max | acc]}
...> other, acc -> {other, acc}
...> end
...> )
iex> {:min, _, [5, {:max, _, [3, 7]}]} = new_ast
iex> [:min, :max, :/, :-] = acc
iex> Code.eval_quoted(new_ast)
{5, []}
将给定的参数转换为使用下划线斜杠格式的字符串。
参数必须是原子或字符串。如果给定原子,则假定它是一个 Elixir 模块,因此将其转换为字符串,然后进行处理。
此函数旨在使用下划线斜杠格式来格式化语言标识符/标记,这就是它属于 Macro
模块的原因。不要将其用作对字符串进行下划线的通用机制,因为它不支持 Unicode 或 Elixir 标识符中无效的字符。
示例
iex> Macro.underscore("FooBar")
"foo_bar"
iex> Macro.underscore("Foo.Bar")
"foo/bar"
iex> Macro.underscore(Foo.Bar)
"foo/bar"
一般来说,underscore
可以被认为是 camelize
的反向操作,但是,在某些情况下,格式可能会丢失
iex> Macro.underscore("SAPExample")
"sap_example"
iex> Macro.camelize("sap_example")
"SapExample"
iex> Macro.camelize("hello_10")
"Hello10"
iex> Macro.camelize("foo/bar")
"Foo.Bar"
取消转义字符串中的字符。
这是 Elixir 单引号和双引号字符串中默认使用的转义行为。请查看 unescape_string/2
以了解如何自定义转义映射。
在此设置中,Elixir 将转义以下内容:\0
、\a
、\b
、\d
、\e
、\f
、\n
、\r
、\s
、\t
和 \v
。字节可以通过 \xNN
以十六进制形式给出,Unicode 代码点可以通过 \uNNNN
转义给出。
此函数通常用于 sigil 实现(如 ~r
、~s
等),这些实现接收原始的、未转义的字符串,并且可以在任何需要模仿 Elixir 解析字符串方式的地方使用。
示例
iex> Macro.unescape_string("example\\n")
"example\n"
在上面的示例中,我们传递了一个带转义 \n
的字符串,并返回一个未转义的版本。
@spec unescape_string(String.t(), (non_neg_integer() -> non_neg_integer() | false)) :: String.t()
根据给定的映射取消转义字符串中的字符。
如果您想使用与 Elixir 单引号和双引号字符串相同的映射,请查看 unescape_string/1
。
映射函数
映射函数接收一个整数,表示它想要转义的字符的代码点。还有特殊原子 :newline
、:unicode
和 :hex
,它们分别控制换行符、Unicode 和转义。
以下是 Elixir 实现的默认映射函数
def unescape_map(:newline), do: true
def unescape_map(:unicode), do: true
def unescape_map(:hex), do: true
def unescape_map(?0), do: ?0
def unescape_map(?a), do: ?\a
def unescape_map(?b), do: ?\b
def unescape_map(?d), do: ?\d
def unescape_map(?e), do: ?\e
def unescape_map(?f), do: ?\f
def unescape_map(?n), do: ?\n
def unescape_map(?r), do: ?\r
def unescape_map(?s), do: ?\s
def unescape_map(?t), do: ?\t
def unescape_map(?v), do: ?\v
def unescape_map(e), do: e
如果 unescape_map/1
函数返回 false
,则不会转义该字符,并且反斜杠保留在字符串中。
示例
使用上面定义的 unescape_map/1
函数很容易
Macro.unescape_string("example\\n", &unescape_map(&1))
@spec unique_var(var, context) :: {var, [{:counter, integer()}], context} when var: atom(), context: atom()
生成一个 AST 节点,该节点表示由原子 var
和 context
给出的唯一变量。
使用相同参数调用此函数将生成另一个变量,该变量具有其自身的唯一计数器。请参阅 var/2
以了解另一种选择。
示例
iex> {:foo, [counter: c], __MODULE__} = Macro.unique_var(:foo, __MODULE__)
iex> is_integer(c)
true
将管道表达式分解为列表。
管道(一系列 |>/2
的应用)的 AST 与一系列二元运算符或函数应用的 AST 类似:顶层表达式是最右边的 :|>
(这是最后一个被执行的),它的左手边和右手边是它的参数
quote do: 100 |> div(5) |> div(2)
#=> {:|>, _, [arg1, arg2]}
在上面的示例中,|>/2
管道是最右边的管道;arg1
是 100 |> div(5)
的 AST,而 arg2
是 div(2)
的 AST。
将此类管道的 AST 作为函数应用的列表通常很有用。此函数正是这样做的
Macro.unpipe(quote do: 100 |> div(5) |> div(2))
#=> [{100, 0}, {{:div, [], [5]}, 0}, {{:div, [], [2]}, 0}]
我们得到一个直接遵循管道的列表:首先是 100
,然后是 div(5)
(更准确地说是它的 AST),然后是 div(2)
。元组中的第二个元素 0
是管道中先前元素在当前函数应用中的位置:{{:div, [], [5]}, 0}
表示先前元素 (100
) 将被插入为 div/2
函数的第 0 个(第一个)参数,因此该函数的 AST 将变为 {:div, [], [100, 5]}
(div(100, 5)
)。
如果节点包含元数据,则将给定的函数应用于节点元数据。
当与 Macro.prewalk/2
一起使用以从表达式中删除行和卫生计数器等信息以进行存储或比较时,这通常很有用。
示例
iex> quoted = quote line: 10, do: sample()
{:sample, [line: 10], []}
iex> Macro.update_meta(quoted, &Keyword.delete(&1, :line))
{:sample, [], []}
验证给定的表达式是有效的引用表达式。
请查看类型 Macro.t/0
以了解有效引用的表达式的完整规范。
如果表达式有效,它将返回 :ok
。否则,它将返回一个形如 {:error, remainder}
的元组,其中 remainder
是引用的表达式中无效的部分。
示例
iex> Macro.validate({:two_element, :tuple})
:ok
iex> Macro.validate({:three, :element, :tuple})
{:error, {:three, :element, :tuple}}
iex> Macro.validate([1, 2, 3])
:ok
iex> Macro.validate([1, 2, 3, {4}])
{:error, {4}}
生成一个 AST 节点,该节点表示由原子 var
和 context
给出的变量。
请注意,此变量不是唯一的。如果您稍后想要访问同一个变量,您可以使用相同的参数再次调用 var/2
。使用 unique_var/2
生成一个不能被覆盖的唯一变量。
示例
为了构建一个变量,需要一个上下文。大多数情况下,为了保持卫生,上下文必须是 __MODULE__/0
iex> Macro.var(:foo, __MODULE__)
{:foo, [], __MODULE__}
但是,如果有必要访问用户变量,则可以给出 nil
iex> Macro.var(:foo, nil)
{:foo, [], nil}