非常教程

Erlang 20参考手册

syntax_tools

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变量TupleNumber直接。这是使用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开头的整数,例如9091909123

以下前缀,一个或多个_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})

将结合的元素,以表示原子个体语法树的列表abc。这也可以与序列中的静态前缀和后缀元素一起使用。例如:

?Q("{a, b, _@@Elements}") = ?Q({a, b, c, d})

将元素绑定到cd子树,和

?Q("{_@@Elements, c, d}") = ?Q({a, b, c, d})

将绑定元素的列表ab子树。您甚至可以在前缀或后缀中使用普通变量:

?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获得许可。

Erlang 20

Erlang 是一种通用的面向并发的编程语言,可应付大规模开发活动的程序设计语言和运行环境。

主页 https://www.erlang.org/
源码 https://github.com/erlang/otp
版本 20
发布版本 20.1