查看源代码 预处理器

文件包含

可以使用以下方式包含文件

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

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

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

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

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

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

有关详细信息,请参阅 ERTS 中的 erlc 和 Compiler 中的 compile

示例

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

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

示例

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

代码服务器使用 code:lib_dir(kernel) 来查找 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 中生成一个经过预处理和解析转换后的解析代码列表。

预定义宏

以下宏是预定义的

  • ?MODULE - 当前模块的名称,作为原子。

  • ?MODULE_STRING - 当前模块的名称,作为字符串。

  • ?FILE - 当前模块的文件名,作为字符串。

  • ?LINE - 当前行号,作为整数。

  • ?MACHINE - 机器名称,'BEAM'

  • ?FUNCTION_NAME - 当前函数的名称,作为原子。

  • ?FUNCTION_ARITY - 当前函数的元数(参数数量),作为整数。

  • ?OTP_RELEASE - 运行编译器的运行时系统的 OTP 版本,作为整数。例如,当使用 Erlang/OTP 27 进行编译时,宏返回 27

    注意

    要在运行时查找版本,请调用 erlang:system_info(otp_release)。请注意,它以字符串形式返回版本。例如,当版本为 Erlang/OTP 27 时,将返回字符串 "27"

    变更

    ?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 中引入的。

宏重载

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

变更

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

如果存在至少一个带有参数的 Func 定义,但没有一个带有 N 个参数的定义,则带有(可能为空的)参数列表的宏 ?Func(Arg1,...,ArgN) 将导致错误消息。

假设有以下定义

-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().

删除宏定义

可以使用以下方式删除宏的定义

-undef(Macro).

条件编译

以下宏指令支持条件编译

  • -ifdef(Macro). - 仅当定义了 Macro 时才计算以下行。

  • -ifndef(Macro). - 仅当未定义 Macro 时才计算以下行。

  • -else. - 仅允许在 ifdefifndefifelif 指令之后。如果前面的指令的计算结果为 false,则计算 else 后面的行。

  • -if(Condition). - 仅当 Condition 的计算结果为 true 时才计算以下行。

  • -elif(Condition). - 仅允许在 if 或另一个 elif 指令之后。如果前面的 ifelif 指令的计算结果不为 true,并且 Condition 的计算结果为 true,则改为计算 elif 后面的行。

  • -endif. - 指定一系列控制流指令的结束。

注意

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

在语法上,ifelif 中的 Condition 必须是 保护表达式。其他构造(例如 case 表达式)将导致编译错误。

与标准的保护表达式相反,ifelif 中的表达式还支持调用伪函数 defined(Name),它测试 Name 参数是否是先前定义的宏的名称。如果宏已定义,则 defined(Name) 的计算结果为 true,否则为 false。尝试调用其他函数将导致编译错误。

示例

-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)
...
-if(?OTP_RELEASE >= 25).
%% Code that will work in OTP 25 or higher
-elif(?OTP_RELEASE >= 26).
%% Code that will work in OTP 26 or higher
-else.
%% Code that will work in OTP 24 or lower.
-endif.
...

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

示例

-module(m)
...
-if(?OTP_RELEASE >= 26 andalso defined(debug)).
%% Debugging code that requires OTP 26 or later.
-else.
%% Non-debug code that works in any release.
-endif.
...

此代码使用 OTP_RELEASE 宏和 defined(debug) 仅针对 OTP 26 或更高版本编译调试代码。

-feature() 指令

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

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

-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 中添加的。

字符串化宏参数

构造 ??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)]).

也就是说,一个带有调用的函数和结果值的跟踪输出。