Positional Parameters

전달되는 인수들은 shell 에서처럼 자동으로 positional parameters 에 할당됩니다. 사용되는 기호의 의미와 기능도 shell 과 동일합니다. 변수값은 quotes 에 관계없이 확장됩니다.

gnu m4 에서는 $1, $2, $3 ... $n 번호로 두 자리 이상도 사용할 수 있습니다.

$ m4 <<\@
define(foo, ``$0'')                 # $0 : 매크로 자신의 이름

hello foo(100,200,300) macro!    
@

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

$ m4 <<\@
define(foo, `$1' `$2' `$3')         # $1 : 첫번째 인수, $2 : 두번째 인수 ...

hello foo(100,200,300) macro!    
@

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

$ m4 <<\@
define(foo, `$#')                   # $# : 전체 인수 개수

hello foo(100,200,300) macro!
@

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

$ m4 <<\@
define(foo, ``$*'')                 # $* : 전달된 전체 인수들

hello foo(100,200,300) macro!
@

hello 100,200,300 macro!
----------------------------

$ m4 <<\@
define(foo, ``$@'')                 # $@ : 전달된 전체 인수들
                                    # $* 와의 차이점은 인수들이 자동으로 quote 이 된다.
hello foo(100,200,300) macro!
@

hello `100',`200',`300' macro!      # 인수들이 자동으로 quote 이 된다.

한 가지 $# 값과 관련해서 주의할 점은 count() 도 인수의 개수가 1 입니다. 왜냐하면 인수 값은 empty 가 될 수 있기 때문입니다. 따라서 $# 값이 0 이 되는 경우는 () 를 사용하지 않을 경우입니다.

$ m4 <<\@                                     $ m4 <<\@
define(`count', ``$0': $# args')              define(`foo', `bar($@)')
count                                         define(`bar', `$# args')
count()                                       
count(1)                                      foo  
count(1,)                                     @
@                                             
                                              1 args     # bar() 가 되므로
count: 0 args
count: 1 args
count: 1 args
count: 2 args

$# 는 결과가 숫자이기 때문에 기본적으로 quote 을 하지 않아도 되지만 $# 에서 # 문자는 m4 에서 comment 의 시작을 나타내므로 $# 를 사용하려면 직접이든 간접이든 quotes 안에 있어야 됩니다.

$ m4 <<\@                         $ m4 <<\@
define(foo, $2)                   define(foo, `$#')
foo(100,200,300)                  foo(100,200,300)
@                                 @

200                               3
-------------------------------------------------

$ m4 <<\@
define(foo, $#)           # 오류
foo(100,200,300)
@

m4:stdin:1: ERROR: end of file in argument list

인수의 개수

전달된 인수가 prescan 에 의해 quotes 이 제거된다고 해서 인수의 개수가 변경되는 것은 아닙니다. 다음 첫 번째 예제의 경우 foo 매크로에 전달되는 인수들에 prescan 이 일어나 quotes 이 제거되어 `11, 22, 33' 와 44 는 ---> 11, 22, 33 와 44 가 되지만 그렇다고 인수의 개수가 4 개가 되는 것은 아닙니다.

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

foo(`11, 22, 33', 44)       # 먼저 `11, 22, 33' 와 44 두개의 인수가 foo 매크로에 
@                           # 전달되고 이후 prescan 에의해 quotes 이 제거된다.

2
---------------------

$ m4 <<\@
define(`foo', ``$@'')       1. foo 는 `$@' 로 정의된다.
                            2. foo 매크로에 `11, 22, 33' 와 44 두개의 인수가 전달된다.
foo(`11, 22, 33', 44)       3. 전달된 인수에 prescan 이 일어나 첫번째 인수는 quotes 이 제거된다.
@                           4. `$@' 정의에 따라 확장되면 ``11, 22, 33',`44'' 가 된다.
                            5. 확장 결과에 main scan 이 발생하면 quotes 이 한단계 제거되므로
`11, 22, 33',`44'           6. `11, 22, 33',`44' 가 된다.

전달된 인수가 comma 에의해 분리되는 경우는 다음과 같은 경우입니다.

$ m4 <<\@
define(`foo', `$#')
define(`bar', `foo(`$2', $1)')         1. bar 매크로는 foo(`$2', $1) 로 정의된다.
                                       2. bar(`11, 22, 33', 44) 가 확장될땐 먼저
bar(`11, 22, 33', 44)                     `11, 22, 33' 와 44 두개의 인수로 전달되고
@                                         이후 prescan 에의해 quotes 이 제거되면
                                          foo(`44', 11, 22, 33) 로 확장된다.
4                                      3. foo 매크로가 실행되면 인수의 개수는 4 개가 된다.
----------------------------------

$ m4 <<\@
define(`foo', `$#')
define(`bar', `foo(`$2', `$1')')       1. bar 매크로는 foo(`$2', `$1') 로 정의된다.
                                       2. bar(`11, 22, 33', 44) 가 확장될땐 먼저 
bar(`11, 22, 33', 44)                     `11, 22, 33' 와 44 두개의 인수로 전달되고
@                                         이후 prescan 에의해 quotes 이 제거되면
                                          foo(`44', `11, 22, 33') 로 확장된다.
2                                      3. foo 매크로가 실행되면 인수의 개수는 2 개가 된다.

Positional parameters 를 nesting 해서 사용하려면

기본적으로 positional parameters 를 하나의 매크로 정의에서 nesting 해서 사용할 수는 없습니다. 다음은 foo(`xx') 확장 결과로 xx_yy_zz 를 만들려고 한것인데 zoo(`yy') 의 인수값이 적용되지 않아 xx_xx_zz 결과가 됩니다.

$ m4 <<\@
define(`foo',                           
        `define(`zoo', ``$1_zz'')define(`bar', ``$1_'zoo(`yy')')bar')

foo(`xx')
@

xx_xx_zz
---------------------------

1. foo 는 define(`zoo', ``$1_zz'')define(`bar', ``$1_'zoo(`yy')')bar 로 정의된다.
2. foo(`xx') 가 확장되면 위 정의에 의해
   zoo 는 `xx_zz' 로 정의되고 bar 는 `xx_'zoo(`yy') 로 정의되어
   마지막의 bar 는 `xx_'`xx_zz'  --->  xx_xx_zz 로 확장된다.

따라서 positional parameters 값이 변경되는 매크로를 다음과 같이 분리해서 작성해야 합니다.

$ m4 <<\@
define(`zoo', ``$1_zz'')       # zoo 매크로를 분리해 작성
define(`foo', `define(`bar', ``$1_'zoo(`yy')')bar')

foo(`xx')
@

xx_yy_zz
-----------------------------

1. zoo 는 `$1_zz' 로 정의된다.
2. foo 는 define(`bar', ``$1_'zoo(`yy')')bar 로 정의된다.
3. foo(`xx') 가 확장되면 위 정의에 의해 
   먼저 bar 가 `xx_'zoo(`yy') 로 정의되고
   마지막의 bar 는 `xx_'`yy_zz'  --->  xx_yy_zz 로 확장된다.

recursion 의 경우는 문제가 되지 않습니다.

$ m4 <<\@
define(`fac', `ifelse( $1, 1, 1, `eval($1 * fac(decr($1)))')')
fac(10)
@

3628800
---------------------------------

1. fac 매크로는 ifelse( $1, 1, 1, `eval($1 * fac(decr($1)))') 로 정의된다.
2. fac(10) 가 확장되면 위 정의에 따라 eval(10 * fac(decr(10))) 가 된다.
3. 확장 결과를 살펴보면 eval 매크로가 존재하므로 확장이 일어나게 되고
   먼저 decr(10) ---> 9 가 되어 fac(9) 가 되는데
   이것은 앞선 2. 번과 닮은 꼴이므로 정상적으로 반복 실행됩니다.

shift

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

shift built-in 매크로도 shell 과 동일합니다. 인수들을 좌측으로 한 칸씩 이동합니다.
결과적으로 제일 처음 인수가 삭제됩니다.

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

hello foo(100, shift(200, 300)) macro!
@

hello 100 and 300 macro!
----------------------------------

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

hello foo(100, 200, 300) macro!
@

hello 200,300 macro!

shift 에서는 인수 prescan 이 일어나지 않습니다.

다음 예제를 보면 shift 매크로가 실행될 때마다 quotes 이 한 단계씩 제거되지 않습니다.

1. 첫 번째 shift 가되면 ``222'', ``333'', ``444'' 가 되고
2. 두 번째 shift 가되면 ``333'', ``444'' 가 되고 
2. 세 번째 shift 가되면 ``444'' 가 되고 
3. ``444'' 결과에 대해서 main scan  --->  `444'

$ m4 <<\@
shift( shift( shift(``111'', ``222'', ``333'', ``444'')))
@

`444'
------------------------------

$ m4 <<\@
define(`abc', 100)
shift(1, 2, `abc', 4)       1. shift 를 하면 확장 결과는 그대로 2,`abc',4 가 되고
@                              quotes 에의해 abc 는 확장되지 않습니다.

2,abc,4                     2. 확장 결과에 main scan 이 발생하여 quotes 이 제거됨.

shift 매크로가 아닐 경우는 매 단계마다 prescan 에의해 quotes 이 한 단계씩 제거됩니다.

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

foo( foo( foo(`````111''''')))            2. foo(foo(foo(`````111''''')))
@                                         3. foo(foo(````111''''))
                                          4. foo(```111''')
`111'                                     5. ``111'' 결과에 대해 main scan --> `111'

다음의 경우 인수 prescan 이 일어나 `111, 222, 333' ---> 111, 222, 333 이 된 후에 shift 가 되어 결과가 222, 333 이 되는 것이 아닙니다. 전달되는 인수가 1 개 이므로 shift 가 되면 결과가 empty 가 되는 것입니다.

$ m4 <<\@
shift(`111, 222, 333')
@

empty

Quiz

매크로 인수로 seperator 와 리스트를 전달하면 각각의 원소를 seperator 를 이용해 join 하는 것입니다. join 할 때는 empty 인 원소를 포함할 경우와 안 할 경우를 생각해볼 수 있습니다.

다음은 empty 인 원소를 포함해서 join 합니다.

$ m4 -dtel <<\@ 
define(`joinall', ``$2'_$0(`$1', shift($@))')
define(`_joinall',
    `ifelse(`$#', `2', `', ``$1$3'$0(`$1', shift(shift($@)))')')

joinall(`:', `foo', `bar', `zoo')
joinall(`:', `foo', `', `zoo')
@

foo:bar:zoo
foo::zoo          # empty 인 원소도 포함된다.
-------------------------------------

1. joinall 매크로는 2 번째 원소 하나를 앞으로 빼고 _joinall 매크로를 호출합니다.
   이때 인수 구성을 _$0($@) 대신에 _$0(`$1', shift($@)) 로 분리하는 이유는
   최소 2 개의 인수가 되게 하기 위해서입니다.
2. $# 인수 개수가 2 개일 경우는 empty 가 반환되어 종료되고 그렇지 않으면
   seperator 와 3 번째 인수를 앞으로 빼고 다시 자기 자신을 호출합니다.
   이때 전달된 전체 원소를 2 번 shift 해서 다음 원소가 $3 자리에 오게 합니다.

다음은 empty 인 원소는 제외하고 join 합니다.

empty 인 원소를 제외하고 join 할 때는 좀 생각해야 될 부분이 있습니다. 먼저 다음과 같이 작성해 볼 수 있는데 이럴 경우 세 번째 입력과 같이 두 번째 인수 값이 empty 일 경우 결과에 : 가 포함되어 :bar 가 됩니다.

$ m4  <<\@
define(`join',
    `ifelse(`$#', 0, `', `$#', 1, `', `$#', 2, `$2', 
        ``$2'_$0(`$1', shift(shift($@)))')')
define(`_join',
    `ifelse(`$#$2', `2', `',
        `ifelse(`$2', `', `', ``$1$2'')$0(`$1', shift(shift($@)))')')

join(`:', `foo', `bar', `zoo')
join(`:', `foo', `', `zoo')
join(`:', `', `bar')               # 두 번쨰 인수 값이 empty
@

foo:bar:zoo
foo:zoo                 # empty 인 원소는 제외된다.
:bar              <---- 결과가 bar 가 돼야한다.

따라서 다음과 같이 작성해야 올바른 결과가 출력됩니다.

$ m4  <<\@
define(`join',
    `ifelse(`$#', `2', ``$2'',
        `ifelse(`$2', `', `', ``$2'_')$0(`$1', shift(shift($@)))')')
define(`_join',
    `ifelse(`$#$2', `2', `',
        `ifelse(`$2', `', `', ``$1$2'')$0(`$1', shift(shift($@)))')')

join(`:', `foo', `bar', `zoo')
join(`:', `foo', `', `zoo')
join(`:', `', `bar')
@

foo:bar:zoo
foo:zoo             
bar
----------------------------------------------------------

1. 먼저 join 매크로는 인수가 2 개 일경우 두번째 인수를 반환하고 종료합니다.
   여기서 눈여겨보아야 될 점은 다음번 ifelse 인데요. $2 값이 empty 이면 
   empty 가 반환되어 다시 자기 자신 $0 이 호출되지만 그렇지 않을 경우는
   반환값이 `$2'_ 가 되어 결과 적으로 `$2'_$0( ... ) ---> `$2'_join( ... ) 가되어
   _join 매크로가 호출됩니다.
2. _join 매크로는 인수 개수가 2 개이고 $2 값이 empty 이면 $#$2 == 2 가 되어 종료됩니다.
   다음 ifelse 는 $2 값이 empty 이면 empty 를 반환하고 그렇지 않으면 seperator 를 추가해
   $1$2 를 반환합니다. 이때 결과가 $0 과 연결되면 안되므로 quote 을 두번했죠 ``$1$2'' 
   그다음 전달된 전체 인수를 2 번 shift 해서 다음번 원소가 $2 위치에 오게하고 
   $1 를 첫번째 인수로 해서 다시 자기 자신을 호출합니다.