10  预处理器

10  预处理器

文件可以如下包含

-include(File).
-include_lib(File).

File,一个字符串,用于指向文件。此文件的内容按原样包含在指令的位置。

包含文件通常用于由多个模块共享的记录和宏定义。建议使用 .hrl 文件名扩展名作为包含文件。

File 可以以路径组件 $VAR 开头,其中 VAR 是某个字符串。如果是这种情况,则将 os:getenv(VAR) 返回的环境变量 VAR 的值替换为 $VAR。如果 os:getenv(VAR) 返回 false,则 $VAR 保持原样。

如果文件名 File 是绝对的(可能在变量替换之后),则包含具有该名称的包含文件。否则,将在以下目录中搜索指定的文件,并按以下顺序进行搜索

  1. 当前工作目录
  2. 编译模块所在的目录
  3. include 选项指定的目录

有关详细信息,请参阅 ERTS 中的 erlc(1) 手册页和编译器中的 compile(3) 手册页。

示例

-include("my_records.hrl").
-include("incdir/my_records.hrl").
-include("/home/user/proj/my_records.hrl").
-include("$PROJ_ROOT/my_records.hrl").

include_lib 类似于 include,但不指向绝对文件。相反,假设第一个路径组件(可能在变量替换之后)是应用程序的名称。

示例

-include_lib("kernel/include/file.hrl").

代码服务器使用 code:lib_dir(kernel) 查找当前(最新)版本的内核的目录,然后搜索子目录 include 中的文件 file.hrl

宏定义如下

-define(Const, Replacement).
-define(Func(Var1,...,VarN), Replacement).

宏定义可以放置在模块的属性和函数声明中的任何位置,但定义必须出现在任何使用宏之前。

如果宏在多个模块中使用,建议将宏定义放在包含文件中。

宏的使用方式如下

?Const
?Func(Arg1,...,ArgN)

宏在编译期间扩展。一个简单的宏 ?Const 将被替换为 Replacement

示例

-define(TIMEOUT, 200).
...
call(Request) ->
    server:call(refserver, Request, ?TIMEOUT).

扩展为

call(Request) ->
    server:call(refserver, Request, 200).

?Func(Arg1,...,ArgN) 将被替换为 Replacement,其中宏定义中所有变量 Var 的出现都将被相应的参数 Arg 替换。

示例

-define(MACRO1(X, Y), {a, X, b, Y}).
...
bar(X) ->
    ?MACRO1(a, b),
    ?MACRO1(X, 123)

扩展为

bar(X) ->
    {a,a,b,b},
    {a,X,b,123}.

良好的编程实践(不是强制性的)是确保宏定义是有效的 Erlang 语法形式。

要查看宏扩展的结果,可以使用 'P' 选项编译模块。 compile:file(File, ['P'])。这将在 File.P 文件中生成预处理和解析转换后的解析代码的列表。

以下宏是预定义的

当前模块的名称。
当前模块的名称,以字符串形式表示。
当前模块的文件名。
当前行号。
机器名,'BEAM'
当前函数的名称。
当前函数的元数(参数数量)。
当前执行的 ERTS 应用程序所属的 OTP 版本,以整数形式表示。有关详细信息,请参阅 erlang:system_info(otp_release)
更改

?OTP_RELEASE 宏是在 Erlang/OTP 21 中引入的。

如果 特性 Feature 可用,则扩展为 true。该特性可能已启用或未启用。
更改

?FEATURE_AVAILABLE() 宏是在 Erlang/OTP 25 中引入的。

如果 特性 Feature 已启用,则扩展为 true
更改

?FEATURE_ENABLED() 宏是在 Erlang/OTP 25 中引入的。

可以重载宏,除了预定义宏。重载宏具有多个定义,每个定义都有不同的参数数量。

更改

对宏重载的支持是在 Erlang 5.7.5/OTP R13B04 中添加的。

如果宏 ?Func(Arg1,...,ArgN) 具有(可能为空)的参数列表,并且至少有一个带参数的 Func 定义,但没有 N 个参数的定义,则会生成错误消息。

假设这些定义

-define(F0(), c).
-define(F1(A), A).
-define(C, m:f).

以下操作无效

f0() ->
    ?F0. % No, an empty list of arguments expected.

f1(A) ->
    ?F1(A, A). % No, exactly one argument expected.

另一方面,

f() ->
    ?C().

扩展为

f() ->
    m:f().

提供以下宏指令

导致宏的行为如同从未定义一样。
仅当定义了 Macro 时才计算以下行。
仅当未定义 Macro 时才计算以下行。
仅在 ifdefifndef 指令之后允许使用。如果该条件为假,则将计算 else 之后的行。
指定 ifdefifndef 指令或 ifelif 指令的结束。
仅当 Condition 计算结果为真时才计算以下行。
仅在 if 或另一个 elif 指令之后允许使用。如果前面的 ifelif 指令计算结果不为真,并且 Condition 计算结果为真,则将计算 elif 之后的行。
注意

宏指令不能在函数内部使用。

示例

-module(m).
...

-ifdef(debug).
-define(LOG(X), io:format("{~p,~p}: ~p~n", [?MODULE,?LINE,X])).
-else.
-define(LOG(X), true).
-endif.

...

当需要跟踪输出时,在编译模块 m 时应定义 debug

% erlc -Ddebug m.erl

or

1> c(m, {d, debug}).
{ok,m}

?LOG(Arg) 然后扩展为对 io:format/2 的调用,并为用户提供一些简单的跟踪输出。

示例

-module(m)
...
-ifdef(OTP_RELEASE).
  %% OTP 21 or higher
  -if(?OTP_RELEASE >= 22).
    %% Code that will work in OTP 22 or higher
  -elif(?OTP_RELEASE >= 21).
    %% Code that will work in OTP 21 or higher
  -endif.
-else.
  %% OTP 20 or lower.
-endif.
...

该代码使用 OTP_RELEASE 宏根据版本有条件地选择代码。

指令 -feature(FeatureName, enable | disable) 可用于启用或禁用 特性 FeatureName。这是启用(禁用)特性的首选方法,尽管也可以使用编译器的选项来进行操作。

请注意,-feature(..) 指令只能出现在使用任何语法之前。实际上,这意味着它应该出现在任何 -export(..) 或记录定义之前。

指令 -error(Term) 会导致编译错误。

示例

-module(t).
-export([version/0]).

-ifdef(VERSION).
version() -> ?VERSION.
-else.
-error("Macro VERSION must be defined.").
version() -> "".
-endif.

错误消息将如下所示

% erlc t.erl
t.erl:7: -error("Macro VERSION must be defined.").

指令 -warning(Term) 会导致编译警告。

示例

-module(t).
-export([version/0]).

-ifndef(VERSION).
-warning("Macro VERSION not defined -- using default version.").
-define(VERSION, "0").
-endif.
version() -> ?VERSION.

警告消息将如下所示

% erlc t.erl
t.erl:5: Warning: -warning("Macro VERSION not defined -- using default version.").
更改

-error()-warning() 指令是在 Erlang/OTP 19 中添加的。

构造 ??Arg(其中 Arg 是宏参数)将扩展为包含参数标记的字符串。这类似于 C 中的 #arg 字符串化构造。

示例

-define(TESTCALL(Call), io:format("Call ~s: ~w~n", [??Call, Call])).

?TESTCALL(myfunction(1,2)),
?TESTCALL(you:function(2,1)).

结果为

io:format("Call ~s: ~w~n",["myfunction ( 1 , 2 )",myfunction(1,2)]),
io:format("Call ~s: ~w~n",["you : function ( 2 , 1 )",you:function(2,1)]).

即跟踪输出,包含调用的函数和结果值。