Debugging

M4 는 매크로 프로세서이므로 디버깅은 주로 매크로 trace 를 통해 이루어집니다. 디버깅을 위한 설정은 m4 명령 라인에서 -d, -t 옵션을 이용해 할 수도 있고, 코드의 특정 영역을 trace 하기 위해 traceon, traceoff 매크로를 사용할 수도 있습니다. 기본적으로 디버깅 정보는 stderr 로 출력되는데 debugfile 매크로를 이용하면 지정한 파일로 저장할 수도 있습니다.

dumpdef

dumpdef
dumpdef ( Names. . . )
매크로의 확장 결과는 void 입니다.

매크로 Names 의 definitions 을 출력합니다. () 없이 사용하면 현재 위치에서 정의되어 있는 모든 매크로를 알파벳 순서로 출력합니다. ( 기본적으로 stderr 로 출력됩니다 ).

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

dumpdef(`nshift', `define')
@

define: <define>        <--- built-in 매크로의 출력 형식
nshift: pushdef(`num', eval($1))_$0(shift($@))popdef(`num')

() 없이 사용하면 현재 위치에서 정의되어 있는 모든 매크로를 출력합니다.

$ m4  <<\@
define(`foo', 100)
define(`bar', 200)
dumpdef                              # () 없이 사용
define(`zoo', 300)                   # zoo 는 이후에 정의되므로 출력에서 제외됨
@

__file__:       <__file__>
__gnu__:
__line__:       <__line__>
__program__:    <__program__>
__unix__:
bar:    200                          # bar: 200
builtin:        <builtin>
changecom:      <changecom>
changequote:    <changequote>
debugfile:      <debugfile>
debugmode:      <debugmode>
decr:   <decr>
define: <define>
defn:   <defn>
divert: <divert>
divnum: <divnum>
dnl:    <dnl>
dumpdef:        <dumpdef>
errprint:       <errprint>
esyscmd:        <esyscmd>
eval:   <eval>
foo:    100                          # foo: 100
format: <format>
ifdef:  <ifdef>
ifelse: <ifelse>
include:        <include>
incr:   <incr>
index:  <index>
indir:  <indir>
len:    <len>
m4exit: <m4exit>
m4wrap: <m4wrap>
maketemp:       <maketemp>
mkstemp:        <mkstemp>
patsubst:       <patsubst>
popdef: <popdef>
pushdef:        <pushdef>
regexp: <regexp>
shift:  <shift>
sinclude:       <sinclude>
substr: <substr>
syscmd: <syscmd>
sysval: <sysval>
traceoff:       <traceoff>
traceon:        <traceon>
translit:       <translit>
undefine:       <undefine>
undivert:       <undivert>

traceon, traceoff

traceon
traceon ( Names. . . )
traceoff
traceoff ( Names. . . )
매크로의 확장 결과는 void 입니다.

traceon,traceoff 매크로는 특정 위치부터 시작해서 특정 매크로만 trace 하고자 할 때 사용합니다.

특정 매크로만 선택적으로 trace 하고자 할 때는 m4 명령의 -t 옵션을 사용할 수도 있습니다. traceon(`foo', `bar') 는 선택적으로 foo, bar 매크로만 trace 를 합니다. ( 이때 foo, bar 매크로가 현재 정의되어 있을 필요는 없습니다 ). traceoff(`foo', `bar') 는 선택적으로 foo, bar 매크로만 trace 를 중단합니다. () 없이 사용하는 traceon 은 현재 위치에서 정의되어 있는 모든 매크로를 trace 합니다. 이것은 debugmode(t) 를 사용하는 것과 같은 것입니다. 그리고 () 없이 사용하는 traceoff 는 모든 매크로 trace 를 중단합니다. 이것은 () 없이 debugmode 를 사용하는 것과 같은 것입니다.

trace 정보는 기본적으로 stderr 로 출력됩니다.

$ m4 <<\@                                  $ m4 <<\@    
define(`foo', `Hello World.')              define(`foo', `Hello World.')
define(`echo', `$@')                       define(`echo', `$@')
traceon     # 모든 매크로를 trace             traceon(`echo')      # echo 매크로만 trace
foo                                        foo                
echo(`gnus', `and gnats')                  echo(`gnus', `and gnats')
@                                          @

m4trace: -1- foo                           Hello World.
Hello World.                               m4trace: -1- echo
m4trace: -1- echo                          gnus,and gnats
gnus,and gnats
$ m4  <<\@
define(`nshift', `pushdef(`num', eval($1))_$0(shift($@))popdef(`num')')
define(`_nshift', `ifelse( num, 0, `$@', `define(`num', decr(num))$0(shift($@))')')

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,$@))')')

                                                   # 현재 위치에서 부터
debugmode(`ael')traceon(`foreachq',`_foreachq')    # foreachq, _foreachq 매크로만 trace  
foreachq(`elem', `111, 222, 333', `Word was: elem
')
traceoff
foreachq(`elem', `foo, bar, zoo', `Word was: elem
')
@

m4trace:12: -1- foreachq(elem, 111, 222, 333, Word was: elem
) -> ifelse(`111, 222, 333', `', `',
    `pushdef(`elem')_foreachq(`elem', `Word was: elem
', `', 111, 222, 333)popdef(`elem')')
m4trace:12: -1- _foreachq(elem, Word was: elem
, , 111, 222, 333) -> ifelse(`6', `3', `',
    `define(`elem', `111')Word was: elem
`'_foreachq(`elem', `Word was: elem
', nshift(3,`elem',`Word was: elem
',`',`111',`222',`333'))')
Word was: 111
m4trace:12: -1- _foreachq(elem, Word was: elem       # 매 단계 _foreachq 매크로의
, 111, 222, 333) -> ifelse(`5', `3', `',
    `define(`elem', `222')Word was: elem
`'_foreachq(`elem', `Word was: elem
', nshift(3,`elem',`Word was: elem
',`111',`222',`333'))')
Word was: 222
m4trace:12: -1- _foreachq(elem, Word was: elem       # 인수들을 조회할 수 있다.
, 222, 333) -> ifelse(`4', `3', `',                   
    `define(`elem', `333')Word was: elem
`'_foreachq(`elem', `Word was: elem
', nshift(3,`elem',`Word was: elem
',`222',`333'))')
Word was: 333
m4trace:12: -1- _foreachq(elem, Word was: elem
, 333) -> ifelse(`3', `3', `',
    `define(`elem', `')Word was: elem
`'_foreachq(`elem', `Word was: elem
', nshift(3,`elem',`Word was: elem
',`333'))')


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

debugmode

debugmode
debugmode ( )
debugmode ( Flags )
매크로의 확장 결과는 void 입니다.

debugmode 매크로를 이용해 디버깅 정보를 얼마나 자세히 표시할지 설정할 수가 있습니다. 이것은 m4 명령의 -d 옵션을 이용해 설정하는 것과 동일한 것입니다. 매크로 trace 정보에는 m4trace: prefix 가 붙고, 그 외 매크로 호출과 관련 없는 정보에는 m4debug: prefix 가 붙어 출력됩니다.

flag 값을 설정할 때는 앞에 - or + 를 붙일 수가 있는데 - 를 붙이면 기존 설정값에서 해당 flag 가 제거되고 + 를 붙이면 추가됩니다. debugmode 매크로를 () 없이 사용하면 설정된 flag 값들이 clear 됩니다. ( 이것은 -d 옵션을 사용하지 않는 것과 같은 것입니다 ). 인수 값이 없는 debugmode() 형식은 flag 값을 디폴트로 설정합니다. ( 이것은 -d 옵션을 flag 없이 사용하는 것과 같은 것으로 디폴트 값은 aeq 가 됩니다 ).

debug 정보는 기본적으로 stderr 로 출력됩니다.

Flags Description
a show actual arguments
c show before collect, after collect and after call
e show expansion
f say current input file name
i show changes in input files
l say current input line number
p show results of path searches
q quote values as necessary, with a or e flag
t trace for all macro calls, not only traceon'ed
x add a unique macro call id, useful with c flag
V shorthand for all of the above flags
q flag 를 사용하는 이유 )

매크로가 확장될 때는 전달된 인수에서도 prescan 에의해 확장이 일어나고 결과에 대해서도 main scan 에의해 확장이 일어납니다. 인수에 포함된 quotes 은 확장을 방지하는 역할을 하고 한 단계씩 quotes 이 제거됩니다. quotes 이 제거되면 trace 시에 실제 인수의 개수가 몇 개인지 알 수 없는 경우가 생길 수 있습니다. 이때 q flag 을 사용하면 인수와 확장 결과에 quotes 을 추가해서 표시해 줍니다.

$ m4 -dtael <<\@ 
define(`args', `num: $#')
args(`foo', `bar, baz', `zoo')                       # 실제 인수의 개수는 3 개인데
@
m4trace:1: -1- define(args, num: $#)
                                                     # 인수부분 prescan 에 의해
m4trace:3: -1- args(foo, bar, baz, zoo) -> num: 3    # quotes 이 제거되어 4 개로 보인다. 
num: 3
-------------------------------------------------

$ m4 -dtaelq <<\@                  # "q" 디버그 flag 추가
define(`args', `num: $#')
args(`foo', `bar, baz', `zoo')
@
m4trace:1: -1- define(`args', `num: $#')

m4trace:3: -1- args(`foo', `bar, baz', `zoo') -> `num: 3'    # 인수와 확장 결과에 quotes
num: 3                                                       # 을 추가해 표시해 준다.

다음은 quotes 사용과 관련해 발생한 오류를 디버깅을 통해서 해결하는 것입니다. 다음 예제를 보면 aa 매크로 정의에 사용된 값과 ifelse 에서 비교에 사용된 값이 같기 때문에 결과로 111 을 기대하였으나 222 가 출력되고 있습니다.

$ m4 <<\@    
define(`aa',`(`')')
ifelse(aa,`(`')', 111, 222)          
@                          

222

먼저 간단히 m4 명령의 -d 옵션을 이용해 디버깅 정보를 출력해 볼 수 있는데 이때 사용된 V flag 값은 모든 디버깅 flag 을 enable 하는 것과 같습니다. 따라서 필요하지 않는 정보도 많이 출력되고 q flag ( quote value ) 가 포함되기 때문에 출력되는 value 들에 추가로 quote 이 되어서 실제 quotes 이 어떻게 처리되는지 보기에 어려움이 있습니다.

$ m4 -dV <<\@
define(`aa',`(`')')
ifelse(aa,`(`')', 111, 222)          
@                          

m4debug: input read from stdin
m4trace:stdin:1: -1- id 1: define ...
m4trace:stdin:1: -1- id 1: define(`aa', `(`')') -> ???
m4trace:stdin:1: -1- id 1: define(...)

m4trace:stdin:2: -1- id 2: ifelse ...
m4trace:stdin:2: -2- id 3: aa ...
m4trace:stdin:2: -2- id 3: aa -> ???
m4trace:stdin:2: -2- id 3: aa -> `(`')'
m4trace:stdin:2: -1- id 2: ifelse(`()', `(`')', `111', `222') -> ???
m4trace:stdin:2: -1- id 2: ifelse(...) -> `222'
222
m4debug:stdin:3: input exhausted

따라서 이번에는 t ( 모든 매크로 trace ), a ( 인수값 표시 ), e ( 확장결과 표시 ) l ( 라인넘버 표시 ) flags 을 사용해 trace 를 하면 필요한 정보만 볼 수 있고 실제 m4 에서 quotes 이 어떻게 처리되는지 볼 수 있습니다. 결과를 살펴보면 ifelse 의 비교에 사용된 두 값이 틀리게 나오는 것을 알 수 있습니다.

$ m4 -dtael <<\@                                  
define(`aa',`(`')')
ifelse(aa,`(`')', 111, 222)          
@                                    
m4trace:1: -1- define(aa, (`'))                       # m4trace:1: 에서 1 은 라인넘버

m4trace:2: -2- aa -> (`')                             #  ->  로 표시되는 값이 확장결과
m4trace:2: -1- ifelse((), (`'), 111, 222) -> 222      # () 와 (`') 는 값이 틀리다.
222          
-------------------------------

$ m4 <<\@                                  
debugmode(`tael')                    # -d 옵션 대신에 debugmode 매크로 사용
define(`aa',`(`')')                  # 동일한 결과가 출력된다.
ifelse(aa,`(`')', 111, 222)          
@

m4trace:2: -1- define(aa, (`'))

m4trace:3: -2- aa -> (`')
m4trace:3: -1- ifelse((), (`'), 111, 222) -> 222
222

따라서 ifelse 매크로 확장 결과로 111 이 나오게 하려면 다음과 같이 aa 매크로의 정의를 변경해 주어야 합니다.

$ m4 -dtael <<\@
define(`aa',``(`')'')                 # 매크로 aa 의 값을 ``(`')'' 로 변경
ifelse(aa,`(`')', 111, 222)          
@
m4trace:1: -1- define(aa, `(`')')

m4trace:2: -2- aa -> `(`')'
m4trace:2: -1- ifelse((`'), (`'), 111, 222) -> 111     #  111 이 출력된다.
111         
------------------------------------------

1. define(`aa',``(`')'') 에서 prescan 에의해 quotes 이 한 단계 제거되면
   매크로 aa 는 `(`')' 로 정의된다.
2. ifelse(aa, `(`')', 111, 222) 는 aa 매크로 정의에 따라
   ifelse(`(`')', `(`')', 111, 222) 가 되고
   prescan 에 의해 quotes 이 한 단계 제거되면
   최종 결과는 ifelse((`'), (`'), 111, 222) 가 되어 111 로 확장된다.

debugfile

debugfile
debugfile ( )
debugfile ( File )
매크로의 확장 결과는 void 입니다.

debug, trace, dumpdef 정보는 기본적으로 stderr 로 출력이 되는데 m4 명령의 --debugfile 옵션이나 debugfile 매크로를 이용하면 지정한 파일로 출력을 저장할 수 있습니다. 저장은 append 형식으로 되는데 지정한 파일을 open 할 수 없을 경우에는 기존 값이 유지됩니다. debugfile 매크로를 () 없이 사용하면 다시 출력이 stderr 로 되고, 인수 값이 없는 debugfile() 형식을 사용하면 출력이 discard 됩니다.

$ m4 <<\@    
define(`foo', `Hello World.')
define(`echo', `$@')
traceon(`foo', `echo')
foo                
echo(`gnus', `and gnats')
@                        

m4trace: -1- foo                    # 디폴트는 stderr 로 출력 
Hello World.
m4trace: -1- echo
gnus,and gnats
-----------------------------

$ m4 <<\@    
define(`foo', `Hello World.')
define(`echo', `$@')
traceon(`foo', `echo')
debugfile(`trace1')                 # debugfile 을 이용해 trace1 파일로 출력
foo
echo(`gnus', `and gnats')
@

Hello World.
gnus,and gnats

$ cat trace1 
m4trace: -1- foo
m4trace: -1- echo