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 를 첫번째 인수로 해서 다시 자기 자신을 호출합니다.