전처리기

전처리기 (Preprocessor)

에를랭의 전처리기(preprocessor)는 파일 포함(include), 매크로, 조건부 컴파일을 지원해요. 이번 글에서는 전처리기가 제공하는 여러 지시어(directive)를 하나씩 살펴볼게요.

출처: Erlang 공식 문서 - Preprocessor

파일 포함 (File Inclusion)

파일은 다음과 같이 포함할 수 있어요:

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

File은 문자열로, 포함할 파일을 가리킵니다. 이 파일의 내용은 지시어가 있는 위치에 그대로 포함돼요.

include 파일은 주로 여러 모듈이 공유하는 레코드·매크로 정의를 담는 데 씁니다. include 파일의 확장자는 .hrl을 권장해요.

File은 경로 구성 요소 $VAR로 시작할 수 있는데, 여기서 VAR는 임의의 문자열이에요. 그 경우 os:getenv(VAR)가 반환하는 환경 변수 VAR의 값이 $VAR를 대체합니다. os:getenv(VAR)false를 반환하면 $VAR는 그대로 남아요.

파일명 File이 (변수 치환 후에도) 절대 경로라면 그 이름의 include 파일이 포함돼요. 그렇지 않으면 지정된 파일을 다음 디렉토리에서 이 순서대로 찾습니다:

  1. 현재 작업 디렉토리
  2. 모듈이 컴파일되고 있는 디렉토리
  3. include 옵션으로 지정한 디렉토리

자세한 내용은 ERTS의 erlc와 Compiler의 m: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을 찾아요.

매크로 정의와 사용 (Defining and Using Macros)

매크로는 다음과 같이 정의합니다:

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

매크로 정의는 모듈의 속성과 함수 선언 어디에든 둘 수 있지만, 그 매크로를 사용하는 코드보다 먼저 와야 해요.

매크로를 여러 모듈에서 쓴다면 매크로 정의를 include 파일에 두는 걸 권장합니다.

매크로는 다음과 같이 사용해요:

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

매크로는 컴파일 중에 확장됩니다. 단순 매크로 ?ConstReplacement로 치환돼요.

예시:

-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}.

매크로 정의가 유효한 에를랭 문법 형태가 되도록 하는 것은 좋은 프로그래밍 관행이지만, 필수는 아니에요.

매크로 확장 결과를 보려면 'P' 옵션으로 모듈을 컴파일하면 됩니다. compile:file(File, ['P'])는 전처리와 파스 변환을 거친 후의 파싱된 코드 목록을 File.P 파일에 만들어요.

미리 정의된 매크로 (Predefined Macros)

다음 매크로들은 미리 정의되어 있어요:

  • ?MODULE - 현재 모듈의 이름. 원자(atom)로 반환됩니다.

  • ?MODULE_STRING - 현재 모듈의 이름. 문자열로 반환됩니다.

  • ?FILE - 현재 모듈의 파일 이름. 문자열로 반환됩니다.

  • ?LINE - 현재 줄 번호. 정수로 반환됩니다.

  • ?MACHINE - 머신 이름, 'BEAM'.

  • ?FUNCTION_NAME - 현재 함수의 이름. 원자로 반환됩니다.

  • ?FUNCTION_ARITY - 현재 함수의 인자 개수(arity). 정수로 반환됩니다.

  • ?OTP_RELEASE - 컴파일러를 실행 중인 런타임 시스템의 OTP 릴리스. 정수로 반환됩니다. 예를 들어 Erlang/OTP 27로 컴파일하면 이 매크로는 27을 반환해요.

    Note {: .info }

    런타임에 릴리스를 알아내려면 erlang:system_info(otp_release)를 호출하세요. 참고로 반환값은 문자열입니다. 예를 들어 릴리스가 Erlang/OTP 27이면 문자열 "27"이 반환돼요.

    Change {: .info }

    ?OTP_RELEASE 매크로는 Erlang/OTP 21에서 도입됐어요.

  • ?FEATURE_AVAILABLE(Feature) - feature Feature를 사용할 수 있으면 true로 확장됩니다. 해당 feature가 활성화되어 있을 수도 있고 아닐 수도 있어요.

    Change {: .info }

    ?FEATURE_AVAILABLE() 매크로는 Erlang/OTP 25에서 도입됐어요.

  • ?FEATURE_ENABLED(Feature) - feature Feature가 활성화되어 있으면 true로 확장됩니다.

    Change {: .info }

    ?FEATURE_ENABLED() 매크로는 Erlang/OTP 25에서 도입됐어요.

매크로 오버로딩 (Macro Overloading)

미리 정의된 매크로를 제외하고는 매크로를 오버로딩할 수 있어요. 오버로딩된 매크로는 인자 개수가 서로 다른 정의를 하나 이상 가집니다.

Change {: .info }

매크로 오버로딩 지원은 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().

매크로 정의 제거 (Removing a macro definition)

매크로 정의는 다음과 같이 제거할 수 있어요:

-undef(Macro).

조건부 컴파일 (Conditional Compilation)

다음 매크로 지시어들이 조건부 컴파일을 지원합니다:

  • -ifdef(Macro). - Macro가 정의되어 있을 때만 다음 줄들을 평가합니다.

  • -ifndef(Macro). - Macro가 정의되어 있지 않을 때만 다음 줄들을 평가합니다.

  • -else. - ifdef, ifndef, if, elif 지시어 뒤에서만 허용돼요. 앞선 지시어가 false로 평가된 경우 else 다음 줄들이 평가됩니다.

  • -if(Condition). - Condition이 true로 평가될 때만 다음 줄들을 평가합니다.

  • -elif(Condition). - if 또는 다른 elif 지시어 뒤에서만 허용됩니다. 앞선 ifelif 지시어가 true로 평가되지 않고, Condition이 true로 평가되면 elif 다음 줄들이 대신 평가돼요.

  • -endif. - 일련의 제어 흐름 지시어의 끝을 지정합니다.

Note {: .info }

매크로 지시어는 함수 안에서는 사용할 수 없어요.

문법적으로 ifelifCondition가드 표현식이어야 합니다. 다른 구성(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.

...

추적(trace) 출력이 필요하면 모듈 m을 컴파일할 때 debug를 정의해 주어야 해요:

% erlc -Ddebug m.erl

or

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

그러면 ?LOG(Arg)io:format/2 호출로 확장되어 사용자에게 간단한 추적 출력을 제공합니다.

예시:

-module(m)
...
-if(?OTP_RELEASE >= 26).
%% Code that will work in OTP 26 or higher
-elif(?OTP_RELEASE >= 25).
%% Code that will work in OTP 25 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) 지시어는 feature FeatureName을 활성화하거나 비활성화하는 데 써요. 컴파일러에 옵션으로 하는 방법도 있지만, 이 방법이 feature를 활성화(비활성화)하는 선호 방식입니다.

참고로 -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.").

Change {: .info }

-error()-warning() 지시어는 Erlang/OTP 19에서 추가됐어요.

매크로 인자 문자열화 (Stringifying Macro Arguments)

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

즉 호출된 함수와 결과 값 모두를 보여주는 추적 출력이 만들어지는 거예요.

더 알아보기 (Learn more)