Macro
宏
使用宏的方便性。
自定义符号
要创建自定义签名,请使用带有sigil_{identifier}
两个参数的名称定义一个函数。第一个参数是字符串,第二个参数是包含任何修饰符的charlist。如果sigil是小写字母(例如sigil_x
),那么字符串参数将允许插值。如果sigil是大写(例如sigil_X
),那么字符串将不会被内插。
有效的修饰符只包括小写字母和大写字母。其他字符将导致语法错误。
包含自定义sigil的模块必须在使用sigil语法之前导入。
实例
defmodule MySigils do
defmacro sigil_x(term, [?r]) do
quote do
unquote(term) |> String.reverse()
end
end
defmacro sigil_x(term, _modifiers) do
term
end
defmacro sigil_X(term, [?r]) do
quote do
unquote(term) |> String.reverse()
end
end
defmacro sigil_X(term, _modifiers) do
term
end
end
import MySigils
~x(with #{"inter" <> "polation"})
#=>"with interpolation"
~x(with #{"inter" <> "polation"})r
#=>"noitalopretni htiw"
~X(without #{"interpolation"})
#=>"without \#{"interpolation"}"
~X(without #{"interpolation"})r
#=>"}\"noitalopretni\"{# tuohtiw"
摘要
类型
expr()
表示AST中的表达式。
literal()
表示AST中的文字
t()
抽象语法树(AST)
函数
camelize(string)
将给定的字符串转换为CamelCase格式
decompose_call(ast)
将本地或远程呼叫分解到其远程部分(提供时),函数名称和参数列表
escape(expr, opts \ [])
递归转义一个值,以便将其插入语法树中。
expand(tree, env)
接收AST节点并展开它,直到它无法再展开为止。
expand_once(ast, env)
接收AST节点并将其展开一次
generate_arguments(amount, context)
使用给定数量的所需参数变量生成AST节点 Macro.var/2
pipe(expr, call_args, position)
管道expr
进入call_args
在给定的position
postwalk(ast, fun)
对引用的表达式执行深度优先后序遍历。
postwalk(ast, acc, fun)
使用累加器对引用的表达式执行深度优先后序遍历。
prewalk(ast, fun)
对引用的表达式执行深度优先的预顺序遍历。
prewalk(ast, acc, fun)
使用累加器对引用的表达式执行深度优先的预顺序遍历。
to_string(tree, fun \ fn _ast, string -> string end)
将给定表达式转换为二进制
traverse(ast, acc, pre, post)
使用累加器执行引号表达式的深度优先遍历。
traverse(ast, acc, pre, post)
将给定的原子或二进制转换为下划线格式。
unescape_string(chars)
解开给定的字符
unescape_string(chars, map)
根据给出的地图解开给定的字符
unescape_tokens(tokens)
根据默认映射解除给定的令牌。
unescape_tokens(tokens, map)
根据给定的地图取消给定的令牌。
unpipe(expr)
将管道表达式分解为列表。
update_meta(quoted, fun)
如果节点元数据包含给定函数,则将其应用于节点元数据。
validate(expr)
验证给定表达式是有效的引号表达式。
var(var, context)
生成一个AST节点,代表原子var
和给定的变量context
类型
expr()
expr() :: {expr | atom, keyword, atom | [t]}
表示AST中的表达式。
literal()
literal ::
atom |
number |
binary |
(... -> any) |
{t, t} |
[t]
表示AST中的文字
t()
t() :: expr | literal
抽象语法树(AST)
功能
camelize(string)
camelize(String.t) :: String.t
将给定的字符串转换为CamelCase格式。
这个函数被设计用于camelize语言标识符/标记,这就是它属于Macro
模块的原因。不要将它用作骆驼化字符串的通用机制,因为它不支持在Elixir标识符中无效的Unicode或字符。
实例
iex> Macro.camelize "foo_bar"
"FooBar"
如果存在大写字符,则无论如何都不会将它们修改为保留缩略语的机制:
iex> Macro.camelize "API.V1"
"API.V1"
iex> Macro.camelize "API_SPEC"
"API_SPEC"
decompose_call(ast)
decompose_call(Macro.t) ::
{atom, [Macro.t]} |
{Macro.t, atom, [Macro.t]} |
:error
将本地或远程呼叫分解到其远程部分(提供时),函数名称和参数列表。
回报: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
escape(expr, opts \ [])
escape(term, keyword) :: Macro.t
递归地转义一个值,以便将其插入到语法树中。
一个人可能会传递unquote: true
给escape/2
叶子的unquote/1
报表未经处理,实际上不会在逃生中引用内容。
实例
iex> Macro.escape(:foo)
:foo
iex> Macro.escape{:a, :b, :c})
{:{}, [], [:a, :b, :c]}
iex> Macro.escape{:unquote, [], [1]}, unquote: true)
1
expand(tree, env)
接收AST节点并展开它,直到它无法再展开为止。
此函数使用expand_once/2
在引擎盖下面。查看它以获得更多信息和示例。
expand_once(ast, env)
接收AST节点并展开它一次。
扩大了以下内容:
- Macros (local or remote)
- 扩展别名(如果可能的话)并返回原子
- 编译环境宏(
__ENV__/0
,__MODULE__/0
和__DIR__/0
)
- 模块属性reader(
@foo
)
如果不能展开表达式,则返回表达式本身。注意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
generate_arguments(amount, context)
使用,为给定数量的所需参数变量生成AST节点Macro.var/2
。
实例
iex> Macro.generate_arguments(2, __MODULE__)
[{:var1, [], __MODULE__}, {:var2, [], __MODULE__}]
pipe(expr, call_args, position)
pipe(Macro.t, Macro.t, integer) :: Macro.t | no_return
管道expr
进入call_args
在给定的position
。
postwalk(ast, fun)
postwalk(t, (t -> t)) :: t
执行深度优先,后序遍历引号表达式.
postwalk(ast, acc, fun)
postwalk(t, any, (t, any -> {t, any})) :: {t, any}
使用累加器对引用的表达式执行深度优先后序遍历.
prewalk(ast, fun)
prewalk(t, (t -> t)) :: t
对引用的表达式执行深度优先的预顺序遍历.
prewalk(ast, acc, fun)
prewalk(t, any, (t, any -> {t, any})) :: {t, any}
使用累加器对引用的表达式执行深度优先的预顺序遍历.
to_string(tree, fun \ fn _ast, string -> string end)
to_string(Macro.t, (Macro.t, String.t -> String.t)) :: String.t
将给定表达式转换为二进制。
给出fun
为AST中的每个节点调用两个参数:正在打印的节点的AST和该节点的字符串表示形式。此函数的返回值用作该AST节点的最终字符串表示形式。
实例
iex> Macro.to_string(quote(do: foo.bar(1, 2, 3)))
"foo.bar(1, 2, 3)"
iex> Macro.to_string(quote(do: 1 + 2), fn
...> 1, _string -> "one"
...> 2, _string -> "two"
...> _ast, string -> string
...> end)
"one + two"
traverse(ast, acc, pre, post)
traverse(t, any, (t, any -> {t, any}), (t, any -> {t, any})) :: {t, any}
使用累加器对引用的表达式执行深度优先遍历。
underscore(atom)
将给定的原子或二进制转换为下划线格式。
如果给定一个原子,则假定它是一个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"
unescape_string(chars)
unescape_string(String.t) :: String.t
解开给定的字符。
这是默认情况下在Elixir单引号和双引号字符串中使用的无法使用的行为。检查unescape_string/2
有关如何自定义转义映射的信息。
在这种设置中,药剂将难逃以下几点:\0
,\a
,\b
,\d
,\e
,\f
,\n
,\r
,\s
,\t
和\v
。字节可以通过十六进制\xNN
和Unicode Codepoints作为\uNNNN
转义给出。
此功能上印记的实现(如通常使用的~r
,~s
及其他),其接收原料,不转义字符串。
实例
iex> Macro.unescape_string("example\\n")
"example\n"
在上面的示例中,我们传递一个字符串\n
转义,并返回一个版本与它未转义。
unescape_string(chars, map)
unescape_string(String.t, (non_neg_integer -> non_neg_integer | false)) :: String.t
根据给出的地图解开给定的字符。
检查unescape_string/1
是否要使用与Elixir单引号和双引号字符串相同的映射。
地图
该地图必须是一个函数。该函数接收一个整数,表示它想要使用的角色的代码点。以下是由Elixir实施的默认映射功能:
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(?x), do: true
def unescape_map(?u), do: true
def unescape_map(e), do: e
如果unescape_map/1
函数返回false
,则不转义字符,反斜杠保留在字符串中。
如果map函数返回true
,则十六进制和Unicode码点将被转义?x
。Unicode代码点;如果该映射函数返回true
的?u
。
实例
使用unescape_map/1
上面定义的函数很简单:
Macro.unescape_string "example\\n", &unescape_map(&1)
unescape_tokens(tokens)
unescape_tokens([Macro.t]) :: [Macro.t]
根据默认映射解除给定的令牌。
查帐unescape_string/1
和unescape_string/2
有关逃逸的更多信息。
只有二进制标记才会转义,所有其他标记都会被忽略。这个函数在实现自己的签名时很有用。检查Kernel.sigil_s/2
示例的实现。
unescape_tokens(tokens, map)
unescape_tokens([Macro.t], (non_neg_integer -> non_neg_integer | false)) :: [Macro.t]
根据给定的映射解除给定的令牌。
检查unescape_tokens/1
并unescape_string/2
获取更多信息。
unpipe(expr)
unpipe(Macro.t) :: [Macro.t]
将管道表达式分解为列表。
流水线的AST(一系列应用程序|>)类似于一系列二元运算符或函数应用程序的AST:顶级表达式是最右边的:|>(这是最后一个要执行的),它的左边和右边是它的论点:
quote do: 100 |> div(5) |> div(2)
#=> {:|>, _, [arg1, arg2]}
在上面的例子中,|>管道是最右边的管道;arg1是AST的100 |> div(5),并且arg2是AST的div(2)。
拥有这样一个管道的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
)将被插入作为第0
(第一)参数给div/2
功能,从而使AST为该函数将变成{:div, [], [100, 5]}
(div(100, 5)
)。
update_meta(quoted, fun)
update_meta(t, (keyword -> keyword)) :: t
如果节点元数据包含给定函数,则将其应用于该节点元数据。
当与Macro.prewalk/2
从表达式中删除诸如行和卫生计数器之类的信息,以便存储或比较。
实例
iex> quoted = quote line: 10, do: sample()
{:sample, [line: 10], []}
iex> Macro.update_meta(quoted, &Keyword.delete(&1, :line))
{:sample, [], []}
validate(expr)
validate(term) :: :ok | {:error, term}
验证给定表达式是有效的引号表达式。
检查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}}
var(var, context)
var(var, context) :: {var, [], context} when var: atom, context: atom
生成一个AST节点,代表原子var
和给定的变量context
。
实例
为了构建一个变量,需要一个上下文。大多数时候,为了保持卫生,上下文必须是__MODULE__/0
:
iex> Macro.var(:foo, __MODULE__)
{:foo, [], __MODULE__}
但是,如果需要访问用户变量,则可以给出0:
iex> Macro.var(:foo, nil)
{:foo, [], nil}