Local Variables

매크로 정의를 프로그래밍 언어에서 함수 정의와 같이 생각해볼 수 있습니다. 매크로 이름을 함수명으로, 매크로 body 를 함수 body 로 생각했을 때 local 변수는 어떻게 구현할 수 있을까요? 다음 예제를 보면 global 변수에 해당하는 foo, bar 의 초기값이 각각 100, 200 으로 설정되었지만 add 매크로를 호출하고 난후에는 각각 111, 222 로 변경된 것을 볼 수 있습니다.

$ m4 <<\@
define(`foo', 100)dnl
define(`bar', 200)dnl
define(`add',
    `define(`foo', $1)define(`bar', $2)eval( foo + bar )')dnl

foo
bar
add( 111, 222)
foo
bar
@

100
200
333
111         # foo, bar 값이 111, 222 로 변경됨
222

이것은 add 매크로의 body 가 실행될 때 define 에의해 새로운 값으로 정의되었기 때문인데요. 이때 define 대신에 pushdefpopdef built-in 매크로를 사용하면 local 변수를 구현할 수 있습니다.

$ m4 <<\@
define(`foo', 100)dnl
define(`bar', 200)dnl
define(`add',
    `pushdef(`foo', $1)pushdef(`bar', $2)eval( foo + bar )popdef(`foo', `bar')')dnl

foo
bar
add( 111, 222)                                                 
foo         
bar
@

100
200
333
100        # foo, bar 값이 add 매크로 호출 전과 동일하다.
200

pushdef

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

pushdef 는 두 가지 형태로 사용할 수 있습니다. pushdef 을 할때 value 를 함께 정의할 수도 있고 먼저 이름만 pushdef 한 다음에 ( 이때 value 는 empty 가 됨 ) 나중에 define 매크로를 이용해 value 를 정의할 수 있습니다.

$ m4 <<\@
pushdef(`foo', `Expansion one.')dnl
foo
pushdef(`foo')dnl
foo
define(`foo', `Update two')dnl
foo
@
Expansion one.
                       # 값이 empty
Update two

popdef

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

매크로 이름을 일종의 stack 으로 볼 수 있습니다. foo 라는 stack 에 pushdef 을 하면 현재 사용되고 있는 정의가 stack 에 저장되고 새로운 정의가 stack 의 top 에 위치하게 되어 사용됩니다. 현재 사용되고 있는 정의를 삭제하고 다시 이전 정의를 불러오려면 popdef 을 사용하면 됩니다. 매크로 정의가 존재하지 않는 상태에서의 pushdefdefine 과 같고 stack 에 원소가 하나만 남았을 때 popdef 하는 것은 undefine 과 같습니다.

$ m4 <<\@                                  $ m4 <<\@
foo                                        foo
define(`foo', `Expansion one.')dnl         pushdef(`foo', `Expansion one.')dnl
foo                                        foo
pushdef(`foo', `Expansion two.')dnl        pushdef(`foo', `Expansion two.')dnl
foo                                        foo
popdef(`foo')dnl                           popdef(`foo')dnl
foo                                        foo
popdef(`foo')dnl                           popdef(`foo')dnl     # undefine 과 같다.
foo                                        foo
@                                          @

foo                                        foo
Expansion one.                             Expansion one.
Expansion two.                             Expansion two.
Expansion one.                             Expansion one.
foo                                        foo

stack 의 top 인 현재 정의를 변경할 때는 define 매크로를 사용하면 됩니다.

$ m4 <<\@
pushdef(`foo', `Expansion one.')dnl
foo
pushdef(`foo', `Expansion two.')dnl
foo
define(`foo', `Update two.')dnl          # 현재 정의를 변경
foo
popdef(`foo')dnl
foo
@

Expansion one.
Expansion two.
Update two.
Expansion one.

undefine 매크로는 해당 stack 을 reset 시켜 undefined 상태로 만듭니다.

$ m4 <<\@
pushdef(`foo', `Expansion one.')dnl
foo
pushdef(`foo', `Expansion two.')dnl
foo
undefine(`foo')dnl
foo
@               

Expansion one.
Expansion two.
foo                    # foo 는 undefined 상태가 된다.

Quiz

shift built-in 매크로는 한 번에 하나씩만 인수를 shift 시킬 수 있는데요.
shift 횟수를 인수로 받을 수 있게 만들려면 어떻게 할까요?

$ m4 <<\@
define(`nshift', `pushdef(`num', eval($1))_$0(shift($@))popdef(`num')')
define(`_nshift', `ifelse( num, 0, `$@', `define(`num', decr(num))$0(shift($@))')')

nshift(2, `foo', `bar', `baz', `zoo' )
@

baz,zoo
-------------------------------------

1. nshift 는 pushdef(`num', eval($1))_$0(shift($@))popdef(`num') 로 정의된다.
   1.1 pushdef 는 num 지역변수를 만들고 첫번째 인수값을 value 로 설정
       여기서 eval 을 사용하면 인수값으로 산술식을 쓸수있는 장점이 있다.
   1.2 $0 는 매크로 자신의 이름과 같으므로 _$0 는 _nshift 매크로 가 된다.
   1.3 shift($@) 는 첫번째 인수를 제외한 나머지 인수를 전달
   1.4 _nshift 매크로 실행이 완료되면 popdef 으로 사용이 완료된 지역변수 제거.

2. _nshift 는 ifelse( num, 0, `$@', `define(`num', decr(num))$0(shift($@))') 로 정의된다.
   2.1 num == 0 이면 현재 $@ 값을 출력하고 종료
   2.2 num != 0 이면 define(`num', decr(num))$0(shift($@)) 가 실행된다.
   2.3 define 을 이용해 먼저 num 값을 -1 하고 
   2.4 $0 는 매크로 자신의 이름과 같으므로 다시 _nshift(shift($@)) 가 실행된다.
   2.5 이때 shift($@) 매크로를 이용해 인수를 shift 시켜 전달한다.