Conditionals and Loops

ifdef

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

매크로가 정의되어 있는지 테스트할 때는 ifdef 매크로를 사용합니다. MacroName 이 정의되어 있으면 Argument1 가 확장되고 그렇지 않으면 Argument2 가 확장됩니다. Argument2 는 제외할 수 있습니다.

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

true
--------------------

$ m4 <<\@
define(`foo')
ifdef(`bar', `true')               # 현재 bar 는 정의되어 있지 않음 
@
         # empty
--------------------

$ m4 <<\@
define(`foo')
ifdef(`bar', `true', `false')      # Argument2 를 포함
@

false

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

$ m4 <<\@
define(`{my var}', `a strange one')

ifdef(`{my var}', `true', `false')
@

true

ifelse

ifelse ( Comment )
ifelse ( String-1, String-2, Equal )
ifelse ( String-1, String-2, Equal, Not-Equal )
ifelse ( String-1, String-2, Equal, Not-Equal . . . )
() 가 없으면 매크로로 인식되지 않습니다.

ifelse 는 매크로는 하나지만 인수 개수에 따라 comment , if , if ~ else , switch 문으로 사용이 가능한 다재다능한 매크로입니다.

인수 1 개는 comment 로 활용

$ m4 <<\@                               $ m4 <<\@
ifelse(`                                dnl
                                        dnl comment line 111
comment line 111                        dnl comment line 222
comment line 222                        dnl comment line 333
comment line 333                        dnl
                                        define( foo, 100)dnl
')dnl                                   define( bar, 200)dnl
define( foo, 100)dnl                    foo
define( bar, 200)dnl                    bar
foo                                     @
bar                                     100
@                                       200
100
200

인수 3 개는 if 문으로 사용

foo == foo 이면 true 그렇지 않으면 empty

$ m4 <<\@
ifelse(`foo', `foo', `true')
ifelse(`foo', `bar', `true')                                               
@

true
         #  false 는 empty

인수 4 개는 if ~ else 문으로 사용

foo == foo 이면 true 그렇지 않으면 false

$ m4 <<\@
ifelse(`foo', `foo', `true', `false')
ifelse(`foo', `bar', `true', `false')
@

true
false

그 이상은 switch 문으로 사용

  1. foo == foo 이면 yes-1

  2. 그렇지 않으면 bar == bar 이면 yes-2

  3. 그렇지 않으면 zoo == zoo 이면 yes-3

$ m4 <<\@      # zoo == zoo
ifelse(`foo', `bar', `yes-1', `bar', `baz', `yes-2', `zoo', `zoo', `yes-3')
@                           
yes-3

$ m4 <<\@      # bar == bar, zoo == zoo
ifelse(`foo', `bar', `yes-1', `bar', `bar', `yes-2', `zoo', `zoo', `yes-3')
@                           
yes-2

$ m4 <<\@      # foo == foo, bar == bar, zoo == zoo
ifelse(`foo', `foo', `yes-1', `bar', `bar', `yes-2', `zoo', `zoo', `yes-3')
@
yes-1

switch 문은 다음과 같은 것입니다.

$ m4 <<\@ 
ifelse(`foo', `bar', `yes-1', 
    ifelse(`bar', `baz', `yes-2', 
        ifelse( `zoo', `zoo', `yes-3')))        # zoo == zoo
@
yes-3

$ m4 <<\@ 
ifelse(`foo', `bar', `yes-1', 
    ifelse(`bar', `bar', `yes-2',               # bar == bar
        ifelse( `zoo', `zoo', `yes-3')))        # zoo == zoo
@
yes-2

$ m4 <<\@ 
ifelse(`foo', `foo', `yes-1',                   # foo == foo
    ifelse(`bar', `bar', `yes-2',               # bar == bar
        ifelse( `zoo', `zoo', `yes-3')))        # zoo == zoo
@
yes-1

ifelse 매크로 활용 예

foo == foo && bar == bar

$ m4 <<\@ 
ifelse(`foo', `foo', ifelse(`bar', `bar', `yes'))         
@
yes

foo == foo && bar != zoo

$ m4 <<\@ 
ifelse(`foo', `foo', ifelse(`bar', `zoo', `', `yes'))
@
yes

foo != bar && bar == bar

$ m4 <<\@ 
ifelse(`foo', `bar', `', ifelse(`bar', `bar', `yes'))
@
yes

foo != bar && bar != zoo

$ m4 <<\@ 
ifelse(`foo', `bar', `', ifelse(`bar', `zoo', `', `yes'))
@
yes

foo == foo || bar == bar

$ m4 <<\@ 
ifelse(`foo', `foo', `yes', `bar', `bar', `yes')
@
yes

foo == foo || bar != zoo

$ m4 <<\@ 
ifelse(`foo', `foo', `yes', ifelse(`bar', `zoo', `', `yes'))
@
yes

foo != bar || bar == bar

$ m4 <<\@ 
ifelse(`foo', `bar', ifelse(`bar', `bar', `yes'), `yes')
@
yes

foo != bar || bar != zoo

$ m4 <<\@ 
ifelse(`foo', `bar', ifelse(`bar', `zoo', `', `yes'), `yes')
@
yes

Loops

Loop 문의 경우도 자기 자신이 반복되는 구조이므로 recursion 을 이용해 쉽게 구현할 수 있습니다. 다음은 factorial(10)fibonacci(10) 을 구하는 예입니다.

# factorial 구하기

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

fac(10)                        1. 전달된 인수 값이 0 일 경우 1 을 반환하고 종료.
@                              2. 인수 값이 $1 >= 1 일 경우 값을 decr($1) 해서
                                 다시 자기 자신을 호출합니다.
3628800                        3. $0 은 매크로 자신의 이름과 같으므로 fac 가 된다.
---------------------------------------------------------------------------

# fibonacci 수 구하기

$ m4 <<\@                          
define(`fibo', `ifelse($1, 0, 0, eval($1 >= 1), 1, `_$0($1, 1, 0)')')
define(`_fibo', `ifelse($1, 1, $2,`$0(decr($1), eval($2 + $3), $2)')')

fibo(10)                       1. 전달된 인수 값이 0 일 경우 0 을 반환하고 종료.
@                              2. 인수 값이 $1 >= 1 일 경우 $2, $3 번째 인수를 설정해서
                                  _fibo 매크로를 호출. $0 는 매크로 자신의 이름과
55                                같으므로 _$0 은 _fibo 가 된다.

다음은 전달된 인수들 각각에 [ ] 괄호 처리를 해서 출력하는 것입니다.

$ m4 <<\@
define(`eprint', 
    `ifelse($#, 0, ,
            $#, 1, `ifelse( eval(len($1) > 0), 1, ``[$1]'')', 
                   ``[$1]',$0(shift($@))')')
eprint(11,22,33,44,55)
@

[11],[22],[33],[44],[55]
------------------------

1. ifelse 의 첫번째 $# == 0 테스트는 () 없이 eprint 매크로를 사용할 경우 empty 를 반환.
2. $# == 1 일 경우는 `[$1]' 를 반환하는데 이때 eprint() 와같은 경우도 $# 값이 1 이
   될수 있으므로 ifelse 를 이용해 $1 의 len 값이 0 보다 큰지 체크합니다.
3. $# != 1 일 경우는 원소 개수가 2 개 이상이라는 뜻이므로
   `[$1]',$0(shift($@)) ---> `[11]',eprint(shift(11,22,33,44,55)) 로 확장됩니다.
4. 전달된 인수가 shift 되어 다시 처음부터 eprint(22,33,44,55) 가 실행됩니다.

다음은 전달된 인수들을 역순으로 출력합니다.

$ m4 <<\@
define(`reverse', 
    `ifelse( eval($# > 1), 1, `reverse(shift($@)),`$1'', ``$1'')')

reverse(11,22,33,44,55)
@

55,44,33,22,11
---------------------------------

1. 전달된 인수 개수가 $# > 1 이면 
   reverse(shift($@)),`$1' ---> reverse(shift(11,22,33,44,55)),`11' 로 확장되고
   인수가 shift 되어 다시 reverse(22,33,44,55) 가 실행됩니다.
2. $# > 1 가 아니라는 것은 인수 개수가 1 또는 0 이라는 의미이고 `$1' 로 확장됩니다.

다음은 Local Variables 메뉴의 Quiz 에 소개되었던 nshift 매크로를 음수도 입력할 수 있게 reverse 매크로를 이용해 확장하는 것입니다.

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

nshift(2,11,22,33,44,55)           # 양수는 left shift
nshift(-2,11,22,33,44,55)          # 음수는 right shift
@

33,44,55
11,22,33
---------------------------

0. 기본적으로 Quiz 에 소개된 nshift 매크로와 동일한데 reverse 매크로가 추가되고
   nshift 매크로가 조금 변경됩니다. _nshift 매크로는 변경사항이 없습니다.
1. num >= 0 일 경우는 기존과 같이 _$0(shift($@)) 로 확장이 되지만
   그렇지 않고 음수일 경우는 먼저 num 값을 양수로 변경한 후에
   인수들을 reverse 해서 _nshift 에 전달하고 다시 결과값에 대해서 reverse 를 합니다.

while

다음 while 문은 num 값이 1 부터 시작해서 num <= 10 까지 10 번 반복하고 반복 때마다 num 값을 sum 변수에 더하는 것입니다.

$ m4 <<\@ 
define(`while', `ifelse($#, 0, ``$0'', eval($1 + 0), 1, `$2$0($@)')')
define(`num', 1)define(`sum', 0)
while(`num <= 10', `define(`sum', eval(sum + num))define(`num', eval(num + 1))')

num sum
@

11 55
-------------------------

1. ifelse 에서 $# 값이 0 일 경우는 while 매크로에 () 를 사용하지 않았을 경우인데
   이때는 그대로 while 스트링을 반환합니다 ``$0''
2. eval($1 + 0) ---> eval(num <= 10 + 0) 을 이용해 num <= 10 조건식을 테스트합니다.
   여기서 뒤에 + 0 을 붙인 이유는 while() 과같이 실행할 경우 $1 값이 empty 가 되는데
   이때 eval 매크로에서 warning 이 발생하기 때문입니다.
3. $2$0($@) 에서 $2 에의해 define(`sum', eval(sum + num))define(`num', eval(num + 1))
   가 실행되고 이전과 동일한 인수 값으로 다시 자기 자신을 호출합니다. $0($@)

forloop

forloop 매크로는 var, start, end, stmt 순으로 인수가 구성됩니다. start 부터 end 까지 차례로 값이 incr 되어 var 에 대입되고 stmt 가 출력될 때 해당 var 가 확장됩니다.

$ m4 <<\@
define(`forloop', 
    `ifelse(eval(`($2) <= ($3)'), `1',
        `pushdef(`$1')_$0(`$1', eval(`$2'), eval(`$3'), `$4')popdef(`$1')')')
define(`_forloop',
    `define(`$1', `$2')$4`'ifelse(`$2', `$3', `',
        `$0(`$1', incr(`$2'), `$3', `$4')')')

forloop(`num', `1', `8', `[num] ')
@

[1] [2] [3] [4] [5] [6] [7] [8]
-------------------------------

1. $2 <= $3 이면 먼저 num 을 pushdef 를 이용해 local 변수로 만들고 _forloop 을 호출합니다.
   이때 $2, $3 인수에 eval 을 사용하면 인수 값으로 산술식을 사용할 수 있습니다.
2. _forloop 에서는 먼저 define 을 이용해 num 값을 $2 값으로 설정하고
   $4 는 quotes 을 제거하여 확장시킵니다.
3. ifelse 에서는 $2 와 $3 의 값이 같은지 테스트하여 같을 경우 empty 를 반환하고
   다를 경우는 $2 값을 incr 하여 다시 자신을 호출합니다.
$ m4 <<\@
define(`forloop', 
    `ifelse(eval(`($2) <= ($3)'), `1',
        `pushdef(`$1')_$0(`$1', eval(`$2'), eval(`$3'), `$4')popdef(`$1')')')
define(`_forloop',
    `define(`$1', `$2')$4`'ifelse(`$2', `$3', `',
        `$0(`$1', incr(`$2'), `$3', `$4')')')

forloop(`i', `1', `4', `forloop(`j', `1', `8', ` (i, j)')
')
@

 (1, 1) (1, 2) (1, 3) (1, 4) (1, 5) (1, 6) (1, 7) (1, 8)
 (2, 1) (2, 2) (2, 3) (2, 4) (2, 5) (2, 6) (2, 7) (2, 8)
 (3, 1) (3, 2) (3, 3) (3, 4) (3, 5) (3, 6) (3, 7) (3, 8)
 (4, 1) (4, 2) (4, 3) (4, 4) (4, 5) (4, 6) (4, 7) (4, 8)

foreach

foreach 매크로는 var, stmt, list 로 인수가 구성됩니다. list 의 원소가 하나씩 순서대로 var 에 대입되고 stmt 가 출력될 때 해당 var 가 확장됩니다.

$ m4 <<\@
define(`foreach', `ifelse( eval($# > 2), 1,
    `pushdef(`$1',`$3')$2`'popdef(`$1')ifelse( eval($# > 3), 1, 
    `$0(`$1', `$2', shift(shift(shift($@))))')')')

foreach(`X', `Open the X. ', `door', `window')

foreach(`X', `foreach(`Y', `Y the X. ', `Open', `Close')', `door', `window')
@

Open the door. Open the window. 

Open the door. Close the door. Open the window. Close the window. 
-------------------------------------------------

1. ifelse 매크로 안에 ifelse 가 중첩되어 있습니다.
   $1 는 var 가 되고 $2 는 stmt, $3, $4, $5 ... 는 list 값이 됩니다. 
   $# > 2 이라는것은 list 값이 존재한다는 의미이므로 먼저 pushdef 을 이용해
   $1 에 list 의 첫번째 원소값 $3 을 대입하고 $2 는 quotes 을 제거하여 확장 시킵니다.
2. 그다음 이어서 ifelse 가 실행되는데 $# > 3 이라는것은 다음 list 값이 존재한다는 
   의미이므로 다시 자기 자신을 호출하는데 이때 $1, $2 값은 그대로 사용하고 
   $@ 값을 3 번 shift 하여 다음 처리할 원소가 $3 의 위치에 오게 합니다.
$ m4 <<\@
define(`foreach', `ifelse( eval($# > 2), 1,
    `pushdef(`$1',`$3')$2`'popdef(`$1')ifelse( eval($# > 3), 1, 
    `$0(`$1', `$2', shift(shift(shift($@))))')')')

define(`OPER', ``$2 the $1'')dnl
foreach(`XY', `OPER(XY). ', ``window',`Open'', ``door',`Close'')
@

Open the window. Close the door.
---------------------------------

1. 이번에는 list 값에 quotes 을 중첩 사용하여 2 개를 하나의 값으로 전달합니다.
   $3 값은 prescan 에의해 quotes 이 제거되어 XY 값은 `window',`Open' 가 되고
   OPER(`window',`Open') ---> Open the window 로 확장되게 됩니다.
$ m4 <<\@
define(`foreach',`ifelse( eval($# > 2), 1,
    `pushdef(`last', eval($# == 3))pushdef(`$1', `$3')$2`'popdef(
    `$1', `last')ifelse( eval($# > 3), 1,
    `$0(`$1',`$2', shift(shift(shift($@))))')')')

define(`everyone',``Tom',`Dick',`Harry'')
foreach(`X', `X`'ifelse(last, 0, ` and ')', everyone).
@

Tom and Dick and Harry.
-----------------------------------------

1. 이번 예제는 stmt 에 ifelse 매크로를 사용해서 단어 사이에 ` and ' 를 위치시키는 것입니다.
   그러기 위해서는 먼저 현재 처리중인 리스트 값이 마지막 원소인지 알아야 하기 때문에
   pushdef(`$1', `$3') 를 하기전에 먼저 pushdef(`last', eval($# == 3)) 를 하여
   last 변수값을 1 (마지막 원소임) or 0 (마지막 원소가 아님) 으로 설정합니다.
2. $2 에 quotes 을 제거하여 확장시킬 때는 결과가 X`'ifelse(last, 0, ` and ') 가 되는데 
   이때 X 는 list 원소가 되고 last 값에 따라 마지막 원소가 아닐경우 ` and ' 가 추가되게 됩니다.

다음부터는 foreach 매크로에서 quote 과 관련해 dquote, dquote_elt 사용자 정의 매크로 를 사용합니다. 인수의 위치가 바뀌어 list$2 위치에 오고 stmt$3 위치에 옵니다.

(foo, bar, zoo) 괄호형 리스트를 사용하는 foreach 매크로

$ m4 <<\@
define(`dquote', ``$@'')
define(`dquote_elt', `ifelse(`$#', `0', `', `$#', `1', ```$1''',
                             ```$1'',$0(shift($@))')')
dnl-------------------------------------------------------------
define(`foreach', `pushdef(`$1')_$0(`$1', (dquote(dquote_elt$2)), `$3')popdef(`$1')')
define(`_arg1', `$1')
define(`_foreach', `ifelse(`$2', `(`')', `',
    `define(`$1', _arg1$2)$3`'$0(`$1', (dquote(shift$2)), `$3')')')

foreach(`elem', `(foo, bar, zoo)', `Word was: elem
')
@

Word was: foo
Word was: bar
Word was: zoo
---------------------------------------------------

1. foreach 매크로는 두번째 인수로 리스트를 전달하는데 여기서는 () 괄호형을 사용합니다.
   따라서 코드중에 볼수있는 dquote_elt$2, _arg1$2, shift$2 는 확장이 되면 
   dquote_elt(foo,bar,zoo), _arg1(foo,bar,zoo), shift(foo,bar,zoo) 와 같은 
   매크로 호출이 됩니다.
2. 먼저 pushdef 를 이용해 elem 을 local 변수로 만들고 _foreach 매크로를 호출하는데
   여기서 두번째 인수는 먼저 (```foo'',``bar'',``zoo''') 로 확장된 후
   prescan 이 완료되면 (``foo'',``bar'',``zoo'') 형태가 되어 전달됩니다.
3. _foreach 매크로의 ifelse 는 $2 리스트의 원소가 empty 인지 체크하고 empty 가 아닐경우
   먼저 define 을 이용해 elem 값을 리스트의 첫번째 원소로 설정합니다.
   이때 첫번째 원소를 추출하는데 _arg1 매크로가 사용됩니다.
4. $3 은 quotes 을 제거하여 현재 설정된 elem 값을 이용해 확장시킵니다.
5. 두번째 인수를 shift 하고 dquote 하면 (```bar'',``zoo''') 가 되는데
   prescan 이 완료되면 (``bar'',``zoo'') 가 되고 다시 자신을 호출합니다.

`foo, bar, zoo' quoted 리스트를 사용하는 foreach 매크로

foreach 방법 1

$ m4 <<\@
define(`dquote', ``$@'')
dnl--------------------------------------------------
define(`foreachq', `pushdef(`$1')_$0($@)popdef(`$1')')
define(`_arg1q', ``$1'')
define(`_rest', `ifelse(`$#', `1', `', `dquote(shift($@))')')
define(`_foreachq', `ifelse(`$2', `', `',
    `define(`$1', _arg1q($2))$3`'$0(`$1', _rest($2), `$3')')')

foreachq(`elem', `foo, bar, zoo', `Word was: elem
')
@

Word was: foo
Word was: bar
Word was: zoo

foreach 방법 2

$ m4 -dael -t foreachq -t _foreachq  <<\@
define(`nshift', `pushdef(`num', eval($1))_$0(shift($@))popdef(`num')')
define(`_nshift', `ifelse( num, 0, `$@', `define(`num', decr(num))$0(shift($@))')')
dnl--------------------------------------------------------------------
define(`foreachq', `ifelse(`$2', `', `',
    `pushdef(`$1')_$0(`$1', `$3', `', $2)popdef(`$1')')')
define(`_foreachq', `ifelse(`$#', `3', `',
    `define(`$1', `$4')$2`'$0(`$1', `$2', nshift(3,$@))')')

foreachq(`elem', `foo, bar, zoo', `Word was: elem
')
@

Word was: foo
Word was: bar
Word was: zoo
--------------------------------------------------------

0. 이번 방법은 _arg1q, _rest 같은 매크로 사용을 제거하고 
   인수들의 위치를 변경하는 방법을 사용해서 코드를 간단히 합니다.
1. foreachq 매크로에서 _foreachq 로 인수를 전달할때 `Word was: elem' 세번째 인수가
   두번째 위치로 오고 두번째 인수인 `foo, bar, zoo' 리스트가 마지막 인수 자리에 옵니다.
   이렇게 하는 이유는 리스트의 quotes 이 제거되면 인수의 개수가 3 개로 변경되기 때문입니다.
2. _foreachq 매크로에서는 먼저 $4 인수값을 ( 처음에는 foo 가 되겠죠 )
   elem 매크로에 대입하고 $2 (`Word was: elem') 는 quotes 을 제거하여 확장시킵니다.
3. 그다음 다시 자신을 호출하는데 여기서 $1 (`elem') 과 $2 (`Word was: elem') 는 
   그대로 자리를 유지하고 nshift(3,$@) 한 결과를 세번째 인수 자리에 붙여서 호출합니다.
   그러면 다음과 같은 순서로 인수가 만들어져서 _foreachq 매크로가 호출되게 됩니다.
   (`elem', `Word was: elem', `foo', `bar', `zoo')
   (`elem', `Word was: elem', `bar', `zoo')
   (`elem', `Word was: elem', `zoo')

foreach 방법 3

$ m4 -dael -t foreachq -t _foreachq -t _foreachq_ <<\@
define(`forloop', 
    `ifelse(eval(`($2) <= ($3)'), `1',
        `pushdef(`$1')_$0(`$1', eval(`$2'), eval(`$3'), `$4')popdef(`$1')')')
define(`_forloop',
    `define(`$1', `$2')$4`'ifelse(`$2', `$3', `',
        `$0(`$1', incr(`$2'), `$3', `$4')')')
dnl------------------------------------------------------------
define(`foreachq',`ifelse(`$2', `', `', `_$0(`$1', `$3', $2)')')
define(`_foreachq',
    `pushdef(`$1', forloop(`$1', `3', `$#',
        `$0_(`1', `2', $1)')`popdef(`$1')')$1($@)')
define(`_foreachq_',
    ``define(`$$1', `$$3')$$2`''')

foreachq(`elem', `foo, bar, zoo', `Word was: elem
')
@

Word was: foo
Word was: bar
Word was: zoo
--------------------------------------------------------

1. 이번에는 이전에 작성해둔 forloop 매크로를 활용해서 구현합니다.
   foreachq 매크로에서 _foreachq 로 인수를 전달할때 방법 2 에서와 같이
   $2 (`foo, bar, zoo') 리스트를 마지막 인수에 위치 시킵니다.
   이때 quotes 을 제거하여 최종 전달되는 인수의 개수가 5 개가 되게합니다.
2. _foreach 매크로에서는 pushdef 을 이용해 elem 매크로를 설정하는데
   이때 forloop 를 이용해 3 ~ 5 까지 3 번 반복하여 _foreachq_ 매크로를 실행합니다.
3. _foreachq_ 매크로에서 $1, $2, $3 값은 모두 숫자이므로 $$1 --> $1, $$2 --> $2 가되고
   forloop 매크로 확장 결과와 뒤의 popdef 매크로를 합하면 최종 elem 매크로 정의는
  `define(`$1', `$3')$2`'' `define(`$1', `$4')$2`'' `define(`$1', `$5')$2`'' 
  `popdef(`$1')' 가 됩니다.
4. pushdef 에 의해 elem 매크로가 정의되고 난후에 $1($@) 가 확장되면
   elem(`elem',`Word was: elem',`foo',`bar',`zoo') 가되고 위의 elem 매크로 정의에 따라
   실행이 되면 define(`elem',`foo')Word was... define(`elem',`bar')Word was...
   define(`elem',`zoo')Word was... popdef(`elem') 까지 실행되게 됩니다.

Nesting limit 설정

앞서 소개되었던 factorial 매크로나 아래의 sigma 매크로를 살펴보면 eval($1 + sigma(decr($1))) 연산이 종료되지 않은 상태에서 계속해서 자기 자신이 호출됩니다. 이와 같은 형태는 stack overflow 대상입니다. m4 명령에서는 -L 옵션을 사용해서 사용자가 직접 nesting limit 을 설정할 수 있습니다. 이것은 무한정 가능한 것이 아니고 현재 m4 프로세스가 사용할 수 있는 stack size 내에서 설정할 수 있는 것입니다

$ m4 <<\@ 
define(`sigma', `ifelse( eval($1 <= 1), 1, $1, `eval($1 + $0(decr($1)))')')

sigma(1000)  
@

500500
-----------------------

$ m4 <<\@ 
define(`sigma', `ifelse( eval($1 <= 1), 1, $1, `eval($1 + $0(decr($1)))')')

sigma(100000)
@

m4: stack overflow

m4 명령의 -L 옵션을 사용해 nesting limit 을 설정하면 초과될 경우 프로세스가 종료됩니다.

$ m4 -L 100 <<\@      # -L 100
define(`sigma', `ifelse( eval($1 <= 1), 1, $1, `eval($1 + $0(decr($1)))')')

sigma(1000)
@

m4:stdin:3: recursion limit of 100 exceeded, use -L<N> to change it

다음과 같이 코드를 변경하면 nesting 이 발생하지 않게 됩니다.

$ m4 -L 2 <<\@        # -L 2
define(`sigma', `ifelse( eval($1 < 1), 1, $2, `$0(decr($1), eval($2 + $1))')')

sigma(10000)            # sigma 매크로가 처음 시작될 때는 $2 값이 empty 가 되는데   
@                       # 이때 `eval($2 + $1)` 를 `eval($1 + $2)` 로 작성하면
                        # 문법상 오류가 됩니다.
50005000

다음 코드는 nesting 이 발생하지 않는 loop 임을 알 수 있습니다.

$ m4 -L 2 <<\@
define(`S', `ifelse(`$1', `0', `', `a`'$0(decr($1))b')')
S(10)
@

aaaaaaaaaabbbbbbbbbb