merl
merl
模块
merl
模块摘要
Erlang的元编程。
描述
在Erlang中进行元编程。Merl是该erl_syntax
模块更友好的用户界面,可以轻松地从头开始构建新的AST并匹配和分解现有的AST。有关Merl本身范围以外的详细信息,请参阅文档erl_syntax
。
快速启动
要启用Merl的全部功能,您的模块需要包含Merl头文件:
-include_lib("syntax_tools/include/merl.hrl").
然后,您可以使用?Q(Text)
代码中的宏创建AST或匹配现有的AST。例如:
Tuple = ?Q("{foo, 42}"),
?Q("{foo, _@Number}") = Tuple,
Call = ?Q("foo:bar(_@Number)")
调用merl:print(Call)
将打印下面的代码:
foo:bar(42)
?Q
宏打开引用的代码片段到的AST,并提升元变量,如_@Tuple
和_@Number
对你的二郎山代码的水平,这样你就可以使用相应的Erlang变量Tuple
和Number
直接。这是使用Merl最直接的方式,在许多情况下,它只是您需要的。
您甚至可以使用?Q
宏作为模式。例如:
case AST of
?Q("{foo, _@Foo}") -> handle(Foo
?Q("{bar, _@Bar}") when erl_syntax:is_integer(Bar) -> handle(Bar
_ -> handle_default()
end
这些案例开关只允许?Q(...)
或者_
作为子句模式,并且警卫可以包含任何表达式,而不仅仅是Erlang警戒表达式。
如果在包含头文件MERL_NO_TRANSFORM
之前定义宏merl.hrl
,Merl使用的分析转换将被禁用,在这种情况下,匹配表达式?Q(...) = ...
,使用?Q(...)
模式的情况开关以及自动元变量等_@Tuple
不能在代码中使用,但Merl宏和函数仍然有效。要进行元变量替换,您需要使用?Q(Text, Map)
宏,例如:
Tuple = ?Q("{foo, _@bar, _@baz}", [{bar, Bar}, {baz,Baz}])
提供给?Q(Text)
宏的文本可以是单个字符串,也可以是字符串列表。当需要将多个表达式分割成多行时,后者很有用,例如:
?Q(["case _@Expr of",
" {foo, X} -> f(X",
" {bar, X} -> g(X)",
" _ -> h(X)"
"end"])
如果文本中某处存在语法错误(如上面第二个子句中缺少的分号),则Merl会生成指向源代码中确切行的错误消息。(只要记住逗号分隔列表中的字符串,否则Erlang将连接字符串片段,就好像它们是单个字符串一样。)
元语法
有多种方法可以在引用的代码中编写元变量:
- 原子开始
@
,例如'@foo'
或'@Foo'
- 变量以
_@
,例如_@bar
或_@Bar
- 字符串以
"'@
,例如"'@File"
- 以909开头的整数,例如
9091
或909123
以下前缀,一个或多个_
或0
可被使用的字符来表示变量的一个或多个级别的“翘起”,之后,一个@
或9
字符表示的glob metavariable(匹配序列中的零个或更多个元件),而不是一个正常的metavariable。例如:
'@_foo'
被提升一个等级,并且_@__foo
被提升了两个层次
_@@bar
是一个GLOB变量,并且_@_@bar
是一个提升的GLOB变量。
90901
是一个提升变量,90991
是一个GLOB变量,并且9090091
是一个GLOB变量提升了两个级别
(请注意,在名的最后一个字符是永远不会被认为是提升或水珠标记,因此,_@__
和90900
只举起一个级别,而不是两个还要注意的是水珠,只有无关紧要匹配;这样做换人的时候,非水珠变量可以用来注入一系列元素,反之亦然。)
如果前缀和任何提升和全局标记之后的名称是_
或0
,则该变量在匹配中被视为匿名全部捕获模式。例如_@_
,_@@_
,_@__
,甚至_@__@_
。
最后,如果没有任何前缀或升降/水珠标记名称以大写字母开始,如_@Foo
或_@_@Foo
,它将成为在二郎水平的变量,可以用来方便地解构和建构语法树:
case Input of
?Q("{foo, _@Number}") -> ?Q("foo:bar(_@Number)"
...
我们称这些为“自动变量”。如果另外名称以变量的值结尾@
,那么_@Foo@
当用来构造更大的树时,作为Erlang项的变量的值将自动转换为相应的抽象语法树。例如,在:
Bar = {bar, 42},
Foo = ?Q("{foo, _@Bar@}")
(其中Bar仅仅是一些术语,而不是语法树),结果Foo
将是一个语法树表示{foo, {bar, 42}}
。这避免了为了注入数据而需要临时变量,如在
TmpBar = erl_syntax:abstract(Bar),
Foo = ?Q("{foo, _@TmpBar}")
如果上下文需要整数而不是变量,原子或字符串,则不能使用大写约定来标记自动变量。相反,如果整数(没有909
-prefix和lift/glob标记)以a结尾9
,整数将成为前缀为Erlang的变量Q
,并且如果它结束,99
它也将被自动抽象。例如,以下内容将增加导出函数f的arity:
case Form of
?Q("-export([f/90919]).") ->
Q2 = erl_syntax:concrete(Q1) + 1,
?Q("-export([f/909299])."
...
何时使用各种形式的元数据
Merl只能解析文本片段,如果它遵循Erlang的基本语法规则。在大多数地方,一个普通的Erlang变量可以用作元变量,例如:
?Q("f(_@Arg)") = Expr
但是,如果您想要匹配类似于函数名称的内容,则必须使用原子作为元数据:
?Q("'@Name'() -> _@@_." = Function
(注意匿名glob变量_@@_
忽略函数体)。
在某些情况下,只允许字符串或整数。例如,该指令-file(Name, Line)
要求它Name
是一个字符串文字和Line
一个整数文字:
?Q("-file(\"'@File\", 9090).") = ?Q("-file(\"foo.erl\", 42).")).
这会将字符串文字提取"foo.erl"
到变量中Foo
。请注意使用匿名变量9090
忽略行号。为了匹配并绑定一个必须是整数字面值的元变量,我们可以使用以9结尾整数的约定,将它变成Erlang级别上的Q前缀变量(请参阅上一节)。
Globs
无论何时您想要在一个序列(零个或多个)中匹配多个元素而不是一组固定元素,您需要使用glob。例如:
?Q("{_@@Elements}") = ?Q{a, b, c})
将结合的元素,以表示原子个体语法树的列表a
,b
和c
。这也可以与序列中的静态前缀和后缀元素一起使用。例如:
?Q("{a, b, _@@Elements}") = ?Q{a, b, c, d})
将元素绑定到c
和d
子树,和
?Q("{_@@Elements, c, d}") = ?Q{a, b, c, d})
将绑定元素的列表a
和b
子树。您甚至可以在前缀或后缀中使用普通变量:
?Q("{_@First, _@@Rest}") = ?Q{a, b, c})
或
?Q("{_@@_, _@Last}") = ?Q{a, b, c})
(忽略除最后一个元素以外的所有元素)。然而,你不能将两个球体作为同一序列的一部分。
提升元变量
在某些情况下,Erlang语法规则使得不可能直接在需要的地方放置元变量。例如,你不能写:
?Q("-export([_@@Name]).")
匹配出口列表中的所有名称/元数对,或者在声明中插入导出列表,因为Erlang分析器只允许导出列表中的表单上的元素A/I
(其中A
是原子和I
整数)。像上述的可变是不允许的,但也不是单个原子或整数,因此'@@Name'
或909919
将不能工作。
在这种情况下你需要做的是将你的元变量写在语法上有效的位置,并使用提升标记来表示它应该真正应用的位置,如下所示:
?Q("-export(['@_@Name'/0]).")
这会导致变量在语法树中被提升(解析后)到下一个更高级别,取代整个子树。在这种情况下,'@_@Name'/0
将被替换为'@@Name'
,并且该/0
部分仅用作虚拟符号,并且将被丢弃。
您甚至可能需要多次应用提升。要将整个导出列表匹配为单个语法树,可以编写:
?Q("-export(['@__Name'/0]).")
使用两个下划线,但这次没有全局标记。这将使整个['@__Name'/0]
部分被替换'@Name'
。
有时候,代码片段的树结构不是很明显,并且当打印为源代码时,部分结构可能是不可见的。例如,一个简单的函数定义如下所示:
zero() -> 0.
由名称(原子zero)和包含单个子句的子句列表组成() -> 0。该子句由一个参数列表(空),一个警卫(空)和一个包含单个表达式的主体(总是一个表达式列表)组成0。这意味着要匹配任何函数的名称和子句列表,您需要使用一个模式?Q("'@Name'() -> _@_@Body."),使用一个哑元子句,该子句的主体是一个全局提升的。
要可视化语法树的结构,可以使用merl:show(T)
打印摘要的函数。例如,输入
merl:show(merl:quote("inc(X, Y) when Y > 0 -> X + Y."))
在Erlang shell中将打印以下内容(+
标记将相同级别的子树组分开):
function: inc(X, Y) when ... -> X + Y.
atom: inc
+
clause: (X, Y) when ... -> X + Y
variable: X
variable: Y
+
disjunction: Y > 0
conjunction: Y > 0
infix_expr: Y > 0
variable: Y
+
operator: >
+
integer: 0
+
infix_expr: X + Y
variable: X
+
operator: +
+
variable: Y
这显示了另一个重要的非显而易见的情况:即使它Y > 0像一个元组元组一样简单,总是由一个或多个测试连接的单个析取组成,这很像一个元组元组。从而:
- "when _@Guard ->" 只会和一名卫兵完全匹配一次测试
- "when _@@Guard ->"将与一个或多个以逗号分隔的测试(但不包含分号)的警卫匹配,并绑定Guard到测试列表
- "when _@_Guard ->"将像以前的模式一样匹配,但绑定Guard到连接子树
- "when _@_@Guard ->"将匹配任意的非空保护,绑定Guard到连接子树列表
- "when _@__Guard ->"将像以前的模式匹配,但绑定Guard到整个分离子树
- 最后,"when _@__@Guard ->"将匹配任何子句,绑定Guard到[]如果警卫是空的,[Disjunction]否则
因此,以下模式匹配所有可能的子句:
"(_@Args) when _@__@Guard -> _@Body"
数据类型
default_action() = () -> any()env() = [{Key::id(),pattern_or_patterns()}]guard_test() = (env()) -> boolean()guarded_action() =switch_action()| {guard_test(),switch_action()}guarded_actions() =guarded_action()| [guarded_action()]id() = atom() | integer()location() =erl_anno:location()**pattern() = [tree()](about:blank#type-tree) | [template()](about:blank#type-template)pattern_or_patterns() = [pattern()](about:blank#type-pattern) | [[pattern()](about:blank#type-pattern)]switch_action() = ([env()](about:blank#type-env)) -> any()switch_clause() = {[pattern_or_patterns()](about:blank#type-pattern_or_patterns), [guarded_actions()](about:blank#type-guarded_actions)} | {[pattern_or_patterns()](about:blank#type-pattern_or_patterns), [guard_test()](about:blank#type-guard_test), [switch_action()](about:blank#type-switch_action)} | [default_action()](about:blank#type-default_action)template() = [tree()](about:blank#type-tree) | {[id()](about:blank#type-id)} | {*, [id()](about:blank#type-id)} | {template, atom(), term(), [[[template()](about:blank#type-template)]]}template_or_templates() = [template()](about:blank#type-template) | [[template()](about:blank#type-template)]text() = string() | binary() | string() | binary()tree() = [erl_syntax:syntaxTree()](erl_syntax#type-syntaxTree)tree_or_trees() = [tree()](about:blank#type-tree) | [[tree()](about:blank#type-tree)]**
出口
alpha(Trees::pattern_or_patterns(), Env::[{id(),id()}]) ->template_or_templates()
Alpha转换模式(重命名变量)。类似于tsubst / 1,但只重命名变量(包括globs)。
另见:
tsubst/2
。
compile(Code) -> term()
相当于compile(Code, [])
。
compile(Code, Options) -> term()
将表示模块的语法树或语法树列表编译为二进制BEAM对象。
另见:
compile/1
,,,compile_and_load/2
...
compile_and_load(Code) -> term()
相当于compile_and_load(Code, [])
...
compile_and_load(Code, Options) -> term()
编译表示模块的语法树或语法树列表,并将结果模块加载到内存中。
另见:
compile/2
,,,compile_and_load/1
...
match(Patterns::pattern_or_patterns(), Trees::tree_or_trees()) -> {ok,env()} | error
将模式与语法树(%28)匹配,或将模式(模式)与语法树(%29)匹配,返回将变量名映射到子树的环境;环境总是按键排序。请注意,不允许在模式中多次出现元数据,但不允许检查。
另见:
switch/2
,,,template/1
...
meta_template(Templates::template_or_templates()) ->tree_or_trees()
将模板转换为表示模板的语法树。模板中的元变量被转换为普通的Erlang变量,如果它们的名称%28在元可调前缀字符%29之后以大写字符开头。例如,_@Foo
在模板中成为变量Foo
在元模板中。此外,变量以@
自动包装在调用Merl:Term/1中,因此。_@Foo@ in the template becomes `merl:term(Foo)
在元模板中。
print(Ts) -> term()
将语法树或模板打印到标准输出。这是一个用于开发和调试的实用函数。
qquote(Text::text(), Env::env()) ->tree_or_trees()
解析文本并替换元变量。
qquote(StartPos::location(), Text::text(), Env::env()) ->tree_or_trees()
解析文本并替换元变量。将初始扫描仪起始位置作为第一个参数。
宏?Q(Text, Env)
扩展到merl:qquote(?LINE, Text, Env)
...
另见:
quote/2
...
quote(Text::text()) ->tree_or_trees()
解析文本。
quote(StartPos::location(), Text::text()) ->tree_or_trees()
解析文本。将初始扫描仪起始位置作为第一个参数。
宏?Q(Text)
扩展到merl:quote(?LINE, Text, Env)
...
另见:
quote/1
...
show(Ts) -> term()
将语法树或模板的结构打印到标准输出。这是一个用于开发和调试的实用函数。
subst(Trees::pattern_or_patterns(), Env::env()) ->tree_or_trees()
在模式或模式列表中替换元变量,从而生成语法树或树列表。对于普通元和GLOB元数据,替换值可以是单个元素或元素列表。例如,如果表示1, 2, 3
取代var
任一种[foo, _@var, bar]
或[foo, _@var, bar]
,结果表示[foo, 1, 2, 3, bar]
...
switch(Trees::tree_or_trees(), Cs::[switch_clause()]) -> any()
匹配一个或多个带有模式和可选保护的子句。
注意,默认操作后的子句将被忽略。
另见:
match/2
...
template(Trees::pattern_or_patterns()) ->template_or_templates()
将语法树或树列表转换为一个或多个模板。模板可以实例化或匹配,然后使用tree/1
如果输入已经是模板,则不会进一步修改。
另见:
match/2
,,,subst/2
,,,tree/1
...
template_vars(Template::template_or_templates()) -> [id()]
返回模板中元数据的有序列表。
term(Term::term()) ->tree()
为常量项创建语法树。
tree(Templates::template_or_templates()) ->tree_or_trees()
将模板还原为普通语法树。任何剩余的元数据都会被转换为@
-前缀原子或909
-前缀整数。
另见:
template/1
...
tsubst(Trees::pattern_or_patterns(), Env::env()) ->template_or_templates()
类似subst/2,但不会将结果从模板转换回树。如果您想要执行多个单独的替换,则非常有用。
另见:
subst/2
,,,tree/1
...
var(Name::atom()) ->tree()
创建一个变量。
richard@gmail.com
© 2010–2017 Ericsson AB
根据ApacheLicense,版本2.0获得许可。