10 预处理器
10.1 文件包含
文件可以如下包含
-include(File). -include_lib(File).
File,一个字符串,用于指向文件。此文件的内容按原样包含在指令的位置。
包含文件通常用于由多个模块共享的记录和宏定义。建议使用 .hrl 文件名扩展名作为包含文件。
File 可以以路径组件 $VAR 开头,其中 VAR 是某个字符串。如果是这种情况,则将 os:getenv(VAR) 返回的环境变量 VAR 的值替换为 $VAR。如果 os:getenv(VAR) 返回 false,则 $VAR 保持原样。
如果文件名 File 是绝对的(可能在变量替换之后),则包含具有该名称的包含文件。否则,将在以下目录中搜索指定的文件,并按以下顺序进行搜索
- 当前工作目录
- 编译模块所在的目录
- 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。
10.2 定义和使用宏
宏定义如下
-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 文件中生成预处理和解析转换后的解析代码的列表。
10.3 预定义宏
以下宏是预定义的
- ?MODULE
- 当前模块的名称。
- ?MODULE_STRING.
- 当前模块的名称,以字符串形式表示。
- ?FILE.
- 当前模块的文件名。
- ?LINE.
- 当前行号。
- ?MACHINE.
- 机器名,'BEAM'。
- ?FUNCTION_NAME
- 当前函数的名称。
- ?FUNCTION_ARITY
- 当前函数的元数(参数数量)。
- ?OTP_RELEASE
- 当前执行的 ERTS 应用程序所属的 OTP 版本,以整数形式表示。有关详细信息,请参阅 erlang:system_info(otp_release)。更改
?OTP_RELEASE 宏是在 Erlang/OTP 21 中引入的。
- ?FEATURE_AVAILABLE(Feature)
- 如果 特性 Feature 可用,则扩展为 true。该特性可能已启用或未启用。更改
?FEATURE_AVAILABLE() 宏是在 Erlang/OTP 25 中引入的。
- ?FEATURE_ENABLED(Feature)
- 如果 特性 Feature 已启用,则扩展为 true。更改
?FEATURE_ENABLED() 宏是在 Erlang/OTP 25 中引入的。
10.4 宏重载
可以重载宏,除了预定义宏。重载宏具有多个定义,每个定义都有不同的参数数量。
对宏重载的支持是在 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().
10.5 宏中的流程控制
提供以下宏指令
- -undef(Macro).
- 导致宏的行为如同从未定义一样。
- -ifdef(Macro).
- 仅当定义了 Macro 时才计算以下行。
- -ifndef(Macro).
- 仅当未定义 Macro 时才计算以下行。
- -else.
- 仅在 ifdef 或 ifndef 指令之后允许使用。如果该条件为假,则将计算 else 之后的行。
- -endif.
- 指定 ifdef、ifndef 指令或 if 或 elif 指令的结束。
- -if(Condition).
- 仅当 Condition 计算结果为真时才计算以下行。
- -elif(Condition).
- 仅在 if 或另一个 elif 指令之后允许使用。如果前面的 if 或 elif 指令计算结果不为真,并且 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 宏根据版本有条件地选择代码。
10.6 The -feature() 指令
指令 -feature(FeatureName, enable | disable) 可用于启用或禁用 特性 FeatureName。这是启用(禁用)特性的首选方法,尽管也可以使用编译器的选项来进行操作。
请注意,-feature(..) 指令只能出现在使用任何语法之前。实际上,这意味着它应该出现在任何 -export(..) 或记录定义之前。
10.7 -error() 和 -warning() 指令
指令 -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 中添加的。
10.8 将宏参数字符串化
构造 ??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)]).
即跟踪输出,包含调用的函数和结果值。