M4

M4 는 원래 Brian Kernighan 과 P.J. Plauger 가 만든 매크로 프로세서가 시초입니다. Dennis Ritchie 가 이것을 발전시켜 M3 를 만들고 이것이 Rational FORTRAN 의 전처리기인 Ratfor 의 엔진으로 사용됩니다. 전처리기라고 해서 CPP 같은 수준이 아니고 프로그래밍 언어입니다. 당시 FORTRAN 은 주로 GOTO 와 statement number 를 이용해 프로그래밍을 했는데 아래 왼쪽과 같이 코드를 작성하면 Ratfor 가 오른쪽과 같이 변환해 줍니다. ( C++ 도 초기에는 전처리기를 거쳐 C 코드로 변환이 됐죠 )

if (a > b) {                        IF (.NOT.(A.GT.B)) GOTO 1
  max = a                           MAX = A
} else {                            GOTO 2
  max = b                         1 CONTINUE        # 왼쪽의 1, 2 숫자가
}                                   MAX = B
                                  2 CONTINUE        # statement number

이후에 Brian Kernighan 과 Dennis Ritchie 가 UNIX operating system 을 위해 M3 를 확장해서 M4 를 만들고 Ratfor, C, Cobol 의 front-end 로 사용됩니다. 이전 매크로 프로세서와 달리 M4 는 M4 does not target any particular computer or human language 라서 general-purpose 매크로 프로세서입니다. 기본적으로 recursion 에 의해 동작하기 때문에 loop, recursion 을 쉽게 구현할 수 있습니다. ( while, for 같은 loop 문도 자기 자신이 계속 반복되는 것이므로 recursion 으로 쉽게 구현이 됩니다 ). 따라서 M4 에서는 infinite loop 와 stack overflow 가 기본입니다. 그리고 M4 는 일반 프로그래밍 언어와 같이 Turing-complete 합니다.

참조사이트: Generating code in M4

기본 사용법

M4 는 명령 라인에서 인수로 전달한 파일들을 확장자에 관계없이 앞에서부터 차례로 읽어들입니다. 이때 -D ( 매크로 define ) 옵션과 -U ( 매크로 undefine ) 옵션을 함께 사용할 수 있습니다.

$ cat base1.m4                $ cat base2.m4                $ cat hello.txt 

define(`foo', 111)            define(`bar', 222)            hello foo bar zoo
---------------------------------------------------------

$ m4 base1.m4 base2.m4 hello.txt                    

hello 111 222 zoo

$ m4 -D zoo=333 base1.m4 base2.m4 hello.txt                 # -D zoo=333

hello 111 222 333

$ m4 -D zoo=333 base1.m4 -U foo base2.m4 hello.txt          # -D zoo=333 -U foo

hello foo 222 333

$ m4 -D zoo=333 base1.m4 -D foo=1000 base2.m4 hello.txt     # -D zoo=333 -D foo=1000

hello 1000 222 333

define

define ( MacroName )
define ( MacroName, Replacement )
매크로의 확장 결과는 void 입니다. () 가 없으면 매크로로 인식되지 않습니다.

M4 도 매크로가 사용되는 방식은 기본적으로 CPP 와 같습니다. 가령 FOO 매크로를 사용하려면 먼저 이전에 FOO 매크로가 정의되어 있어야 합니다. 매크로 이름 구성은 알파벳, 숫자, _ 로 구성되고 맨 앞에 숫자가 올 수 없습니다. 확장이 되려면 매크로 이름 주위에 다른 문자가 와야 합니다. 다음 첫 번째의 경우 N 이 세 번 확장되지 않습니다.

매크로 이름 규칙은 매크로를 invoke 할 때 ( 사용할 때 ), ifelse 매크로 사용시 적용되는 것으로 실제 매크로를 define 할 때는 이름으로 특수문자나 공백도 사용이 가능합니다. 해당 이름은 ifdef, defn, indir 매크로에서 사용할 수 있습니다.

$ m4 <<\@                            $ m4 <<\@
define( N, 200)                      define( N, 200)

if (NNN > 100)                       if (N > 100)
@                                    @

if (NNN > 100)                       if (200 > 100)
------------------------------

$ m4 <<\@
define(`Version2',A - 1)
99Version2:Version2_   Version22
@

99A - 1:Version2_   Version22

CPP 와 달리 M4 는 따로 object-like 매크로와 function-like 매크로 구분이 없습니다. 모두 define built-in 매크로를 이용해 정의하고, 매크로를 정의할 때와 호출할 때 모두 이름과 괄호 사이에 공백이 있으면 안됩니다.

$ m4 <<\@                            $ m4 <<\@        
hello foo macro!                     define(foo)       # 매크로 값이 empty
@                                       
                                     hello foo macro!
hello foo macro!                     @

                                     hello  macro!
--------------------------------------------------------

$ m4 <<\@                            $ m4 <<\@
define(foo, m4)                      define(foo,$1 m4 $2)

hello foo macro!                     hello foo macro!
hello foo() macro!                   hello foo() macro!
hello foo(100) macro!                hello foo(100) macro!
@                                    hello foo(,200) macro!
                                     hello foo(100,200) macro!
hello m4 macro!                      hello foo(100,200,300) macro!   # 인수가 3 개
hello m4 macro!                      hello foo (100,200) macro!      # 괄호 앞에 공백
hello m4 macro!                      @

                                     hello  m4  macro!
                                     hello  m4  macro!
                                     hello 100 m4  macro!
                                     hello  m4 200 macro!
                                     hello 100 m4 200 macro!
                                     hello 100 m4 200 macro!
                                     hello  m4  (100,200) macro!
------------------------------------------------------------

$ m4 <<\@
define(`Version2', A – 1)
Version2(arg1, arg2) Version2 (junk) garbage(trash)Version2()
@

A – 1 A – 1 (junk) garbage(trash)A – 1

매크로 body 에서 define 을 사용할 수도 있습니다.

$ m4 <<\@                                    $ m4 <<\@
define(`foo',`define(`bar', 100)')           define(`foo',`define(`bar', 100)')
bar                                          foo     # foo 매크로가 확장된 후에는 
@                                            bar     # bar 매크로가 정의되므로
        # bar 매크로가 정의되어 있지              @
bar     # 않아 그대로 bar 가 된다.
                                             100     # 100 이 된다.

다음은 H2 매크로를 사용할 때마다 H2_COUNT 값이 incr ( increment ) 된 값으로 재정의 됩니다.

$ m4 <<\@
define(`H2_COUNT', 0)
define(`H2',
    `define(`H2_COUNT', incr(H2_COUNT))<h2>H2_COUNT. $1</h2>')

H2(First Section)
H2(Second Section)
H2(Conclusion)
@

<h2>1. First Section</h2>
<h2>2. Second Section</h2>
<h2>3. Conclusion</h2>

define 매크로가 실행됐을 때 반환값은 void 입니다.
따라서 위의 H2 매크로 정의에서 <h2> 태그 앞에 아무런 문자도 추가되지 않습니다.

value 앞의 공백은 제외된다.

매크로를 정의할 때 value 앞에 있는 공백은 출력에서 제외되지만 뒤에 있는 공백은 포함됩니다. 이것은 len built-in 매크로를 이용해 인수 값의 길이를 구할 때도 동일하게 적용됩니다.

$ m4 <<\@
define(foo,       m4       )

hello foo macro!
@

hello m4        macro!          # value 앞에 있는 공백은 출력에서 제외된다.
-----------------------------

$ m4 <<\@
define(foo,     $1     m4     $2     )

hello foo(100,200) macro!
@

hello 100     m4     200      macro!
------------------------------------

$ m4 <<\@
define(foo,$1 $2 $3 $4 $5 $6 $7 $8 $9 $10)    

hello foo(100,200) macro!                  
@

hello 100 200         macro!
------------------------------------

$ m4 <<\@
define(foo,$1$2$3$4$5$6$7$8$9$10)

hello foo(100,200) macro!
@

hello 100200 macro!
------------------------------------

$ m4 <<\@
define(foo,$1`'$2`'$3`'$4`'$5`'$6`'$7`'$8`'$9`'$10)

hello foo(100,200) macro!
@

hello 100200 macro!

매크로를 공백 없이 붙여 사용하려면

다음은 plus 매크로를 + 로 정의한 후에 ++ 를 만들려고 한 것인데요. plusplus 의 경우는 매크로 이름이 달라지므로 사용할 수 없습니다.

$ m4 <<\@
define(`plus', `+')
plusplus
plus()plus          # OK
plus`'plus          # OK
@

plusplus
++
++

다음은 한 단계 indirection 을 한 것인데요. oper()oper 의 경우 확장이 되면 plusoper 가 되므로 더 이상 확장이 일어나지 않게 됩니다. 매크로가 `' 로 분리되어 있을 경우는 각각에서 매크로 확장이 완료됩니다. oper() 를 사용했을 경우도 ++ 로 확장이 되려면 두 번째와 같이 oper 매크로 값에 `' 를 추가해 줍니다.

$ m4 <<\@                              $ m4 <<\@
define(`plus', `+')                    define(`plus', `+')
define(`oper', `plus')                 define(`oper', `plus`'')    # `' 를 추가
oper()oper                             oper()oper
oper`'oper                             oper`'oper
@                                      @

plusoper                               ++
++                                     ++

undefine

undefine ( MacroName . . . )
매크로의 확장 결과는 void 입니다. () 가 없으면 매크로로 인식되지 않습니다.

정의된 매크로를 삭제할 때는 undefine built-in 매크로를 사용합니다.

$ m4 <<\@
define(`foo', 100)
foo
undefine(`foo')
foo
@

100

foo

한 번만 사용하는 매크로는 다음과 같이 정의합니다.

$ m4 <<\@
define(`msg', `undefine(`msg')Secret message.')
msg
msg           # 두 번째 사용부터는 undefined 상태가 된다.
@

Secret message.
msg

defn

defn ( MacroName . . . )
() 가 없으면 매크로로 인식되지 않습니다.

defn ( definition ) 매크로는 MacroName 매크로의 정의에 quotes 을 붙여서 반환합니다. 인수가 2 개 이상 사용되면 각각 quotes 이 추가되고 공백 없이 결합되어 출력됩니다.

$ m4 <<\@
define(`message', `hello defn')     1. 매크로 message 는 hello defn 으로 정의된다.
defn(`message')                     2. `message' 인수는 prescan 에 의해 message 가 된다.
@                                   3. message 는 hello defn 으로 정의되므로 quote 을붙여
                                       출력하면 `hello defn' 가 된다.
hello defn                          4. 최종 결과에 main scan 이 일어나면 quotes 이 제거된다.

--------------------------------

$ m4 <<\@
define(`foo', bar)       1. 매크로 foo 는 bar 로 정의된다.
defn(foo)                2. 전달된 foo 인수가 prescan 에의해 확장되면 bar 가 된다.
@                        3. bar 에대한 정의는 존재하지 않으므로 결과는 empty 가 된다.

# empty

------------------------------

$ m4 <<\@
define(`name1', `foo')        1. 매크로 name1 은 foo 로 정의된다.
define(`name2', `bar')        2. 매크로 name2 는 bar 로 정의된다.
defn(`name1', `name2')        3. `name1' 은 prescan 에의해 name1 으로 확장되고 name1 은
@                                foo 로 정의되므로 quote 을 하여 출력하면 `foo' 가 된다.
                              4. 마찬가지로 `name2' 는 `bar' 가 되고 두개가 결합되어
foobar                           출력되면 `foo'`bar' 가 된다.
                              5. 마지막으로 main scan 에의해 quotes 이 제거되면 foobar 가된다.
-------------------------------

$ m4 <<\@
define(`foo', `This is `$0'')     1. 매크로 foo 는 This is `$0' 로 정의된다.
define(`bar', defn(`foo'))        2. defn(`foo') 인수가 prescan 에의해 확장되면
bar                                  `foo' --> foo --> This is `$0' --> `This is `$0''
@                                    가되어 define(`bar', `This is `$0'') 가 된다.
                                  3. 매크로 bar 는 This is `$0' 로 정의된다.
This is bar                       4. 매크로 bar 가 확장되면 This is bar 가 된다.

기본적으로 확장 결과에 quote 이 되어 출력되므로 더 이상 확장이 발생하지 않게 됩니다. 따라서 스트링을 값으로 갖는 매크로를 사용할 경우 defn 매크로를 사용해야 합니다. 다음과 같은 경우 defn 을 사용하지 않으면 매크로 값이 정상적으로 출력되지 않고 무한 loop 가 발생하거나 출력값이 변형됩니다.

$ m4 <<\@                                                 $ m4 <<\@
define( `cleanup', `This is the cleanup message.')        define( `foo', `$0')
defn(`cleanup')                                           defn(`foo')
# cleanup   # 무한 loop                                    # foo    # 무한 loop
@                                                         @

This is the cleanup message.                              $0
------------------------------------------------

$ m4 <<\@
define(`string', `The macro dnl is very useful')
string  
@

The macro        # dnl 매크로에 의해 뒷부분이 삭제된다.

$ m4 <<\@
define(`string', `The macro dnl is very useful')
defn(`string')
@

The macro dnl is very useful

defn 을 이용해 built-in 매크로의 정의도 사용할 수 있습니다.

$ m4 <<\@
define(`zap', defn(`undefine'))       1. 매크로 zap 의 정의는 undefine 매크로와 같아진다.
zap(`undefine')                       2. zap 을 이용해 기존의 undefine 매크로를 undefine. 
undefine(`zap')                       3. undefine built-in 매크로가 undefine 되어
@                                     

undefine(zap)                         4. 그대로 스트링으로 출력된다.

defnMacroName 으로 공백이나 특수문자도 사용이 가능합니다.

$ m4 <<\@
define(`{my var}', `a strange one')
                                      # 앞의 {my var} 는 매크로 이름 규칙에 맞지 않아 
{my var} is defn(`{my var}').         # 확장이 안되지만 defn(`{my var}') 는 확장된다.
@

{my var} is a strange one.
----------------------------------

$ m4 <<\@
define(`_set', `define(`$1[$2]', `$3')')dnl
define(`_get', `defn(`$1[$2]')')dnl
_set(`myarray', 1, `alpha')dnl             # define(`myarray[1]',`alpha') 가 된다.
_get(`myarray', 1)                         # defn(`myarray[1]') 가 된다.
_set(`myarray', `alpha', `omega')dnl       # define(`myarray[alpha]', `omega') 가 된다.
_get(`myarray', _get(`myarray',1))         # defn(`myarray[alpha]') 가 된다.
defn(`myarray[alpha]')
@
alpha
omega
omega

indir

indir ( MacroName . . . )
() 가 없으면 매크로로 인식되지 않습니다.

정의된 매크로를 invoke 하는 ( 사용하는 ) 방법에는 direct call 과 indir 매크로를 이용한 indirect call 이 있습니다.

$ m4 <<\@                               $ m4 <<\@
define(`msg', `{{ $1 }}')               define(`msg', `{{ $1 }}')

msg(`hello')   # direct call            indir(`msg', `hello')   # indirect call
@                                       @

{{ hello }}                             {{ hello }}
------------------------------------

$ m4 <<\@
indir(`define', `SIZE', 78)
SIZE
indir(`SIZE')
@

78
78

direct call 과 indirect call 은 다음과 같은 차이가 있습니다.

$ m4 <<\@                               
define(`foo', 100)                    1. foo 매크로는 100 으로 정의된다.         

foo( define(`foo', 200))              2. define 매크로의 확장 결과는 void 이므로      
foo                                      foo() 가 확장되면 결과가 100 이 된다.   
@                                        define 보다 먼저 foo 매크로가 실행중에 있는 상태이므로
                                         인수 부분에서의 foo 재정의는 적용되지 않는다. 
100                                   3. 인수 부분에서 foo 가 200 으로 재정의 됐으므로      
200                                      foo 의 확장 결과는 200 이 된다.            
-------------------------------

$ m4 <<\@
define(`foo', 100)                    1. foo 매크로는 100 으로 정의된다.

indir(`foo', define(`foo', 200))      2. define 매크로가 실행되어 foo 는 200 으로 재정의 된다.
foo                                      indir 매크로에 의해 foo 가 확장되면 200 이 된다.
@                                     3. 인수 부분에서 foo 가 200 으로 재정의 됐으므로
                                         foo 의 확장 결과는 200 이 된다.
200
200

indirMacroName 에 특수문자나 공백도 사용이 가능합니다.

$ m4 <<\@
define(`$$internal$macro', `Internal macro (name `$0')')

$$internal$macro                          # direct call
indir(`$$internal$macro')                 # indirect call
@
                                          # direct call 은 매크로 이름 규칙에
$$internal$macro                          # 맞지 않아 확장되지 않는다.  
Internal macro (name $$internal$macro)

indirdefn 과 달리 확장 결과에 자동으로 quotes 이 추가되지 않습니다.

$ m4 <<\@                                $ m4 <<\@                
define(`foo',100)                        define(`foo',100)
define(`name', `foo')                    define(`name', `foo')

defn(`name')                             indir(`name')
@                                        @
        1. `foo'                                    1.  foo
foo     2.  foo                          100        2.  100

builtin

builtin ( MacroName . . . )
() 가 없으면 매크로로 인식되지 않습니다.

M4 는 기본적으로 define, undefine, defn 매크로를 사용하여 built-in 매크로의 정의를 변경하거나 삭제하는 것도 가능합니다. 하지만 이와 같은 경우에도 builtin 매크로를 이용하면 기존의 디폴트 정의를 사용할 수 있습니다.

# 기존의 define 매크로를 create 로 변경하고 define 매크로는 undefine 을 이용해 삭제
$ m4 <<\@
define(`rename', `define(`$2',defn(`$1'))undefine(`$1')')
rename(`define',`create')
create(`vehicle',`truck')      # create 를 이용해 vehicle 을 truck 으로 정의
vehicle
define(`fuel',`diesel')
fuel
@

truck                          # vehicle 은 truck 으로 확장된다.
define(fuel,diesel)            # 기존의 define 매크로는 undefine 으로 삭제했으므로
fuel                           # 제대로 기능하지 않는다.
$  m4 <<\@ 
define(`TREE',`maple')dnl
undefine(`define',`undefine')dnl        # define, undefine 두 매크로 모두 삭제
undefine(`TREE')                        # undefine(TREE)
TREE                                    # maple
builtin(`undefine',`TREE')dnl           # builtin 매크로를 이용해 기존의 undefine 매크로 사용
TREE                                    # TREE
builtin(`define',`create',`builtin(`define',$@)')dnl   # builtin 매크로를 사용해 create 를
create(`TREE',`ash')dnl                                # 기존의 define 매크로로 정의
TREE                                    # ash
@

undefine(TREE)
maple
TREE
ash

m4 명령의 -P 옵션을 사용하면 built-in 매크로 이름에 m4_ prefix 를 붙여 사용하는데 builtin 매크로를 사용할 때는 붙일 필요가 없습니다.

dnl

M4 매크로 프로세서는 입력된 텍스트를 매크로 확장을 거쳐서 그대로 출력하기 때문에 define 같은 built-in 매크로를 사용할 때 입력한 newline 도 출력에 포함되게 됩니다. 따라서 불필요하게 공백 라인이 생기게 되는데요. 이때 dnl ( discard newline ) 매크로를 사용하면 입력된 위치부터 이후 newline 까지 포함해서 삭제됩니다. 따라서 comment 라인을 작성할 때도 사용됩니다.

dnl 라인에 존재하는 매크로는 실행되지 않습니다.

$ m4 <<\@                              $ m4 <<\@
define( foo, 100)                      dnl comment line 111
define( bar, 200)                      dnl comment line 222
foo                                    dnl comment line 333
bar                                    define( foo, 100)dnl
@                                      define( bar, 200)dnl
      <---- 2 개의 공백라인               foo
                                       bar
100                                    @
200                                    100
                                       200

changecom

changecom( [start], [end] )
changecom

m4 에서도 shell 에서처럼 # 문자를 이용해 comment 를 작성할 수 있습니다. comment 내에서는 매크로 확장이 발생하지 않습니다. 그러니까 dnl 처럼 라인이 삭제되는 것이 아니고 매크로 확장이 suppress 되는 것입니다. 디폴트는 # (start) ~ newline (end) 문자인데 changecom 매크로를 이용해 사용자가 임의로 변경해 사용할 수 있습니다. 2 문자 이상의 스트링도 가능하고 () 없이 사용하면 comment 기능이 disable 됩니다.

0. 디폴트는                           # ...... newline 
1. changecom( `!' )                 ! ...... newline
2. changecom( `/*', `*/' )          /* ..... */ 
3. changecom                        comment 기능을 disable
$ m4 <<\@                           $ m4 <<\@
define( foo, 100)dnl                # define( foo, 100)dnl
define( bar, 200)dnl                # define( bar, 200)dnl
foo                                 foo
bar                                 bar
@                                   @
100                                 # define( foo, 100)dnl
200                                 # define( bar, 200)dnl
                                    foo
                                    bar
----------------------------------------------------------

$ m4 <<\@                           $ m4 <<\@
define( foo, 222)                   define( foo, `#222')
define( bar, 333)                   define( bar, 333)
111 foo bar                         111 foo bar
@                                   @

111 222 333                         111 #222 bar    # bar 는 확장이 안된다.

-P , --prefix-builtins 옵션의 사용

Built-in 매크로와 이름 충돌을 피하기 위해 -P 옵션을 사용할 수 있습니다. -P 옵션을 사용하면 built-in 매크로 이름 사용 시 m4_ prefix 를 붙여야 합니다.

$ m4 <<\@                                 $ m4 -P <<\@              # -P 옵션 사용
define(`M1',`text1')M1                    define(`M1',`text1')M1
m4_define(`M1',`text1')M1                 m4_define(`M1',`text1')M1
@                                         @
text1                                     define(M1,text1)M1
m4_define(M1,text1)text1                  text1