Quotes
M4 를 사용하는데 있어서 quotes 은 shell 에서와 같이 제일 중요한 부분입니다. M4 에서 quotes 은 다음과 같은 용도로 사용됩니다.
1 . 매크로 확장을 방지하기 위해
2 .
`'
을 이용해 공백을 제거하고 token 을 분리 or 결합하기 위해
$ m4 <<\@ $ m4 <<\@
define(`_join',100) define(`_join',100)
_join # quotes 이 없으므로 foo_join # foo_join 은 새로운
@ # 정상적으로 확장된다. @ # 이름을 구성한다.
100 foo_join
--------------------------------------------------------------
$ m4 <<\@ $ m4 <<\@
define(`_join',100) define(`_join',100)
`_join' # quotes 을 하면 foo`'_join # `' 를 이용해
@ # 확장이 안된다. @ # token 을 분리
_join foo100 # 공백없이 붙는다.
--------------------------------------------------------------
$ m4 <<\@
define(`_join',100)
`foo'_join # `foo' 와 분리되어
@ # _join 은 확장된다.
foo100 # 공백없이 붙는다.
M4 에서 기본적으로 사용되는 quotes 은 왼쪽에는 `
( backtick )
문자를 사용하고 오른쪽은 '
( apostrophe ) 문자를 사용합니다.
이렇게 다른 문자를 사용하는 이유는 quotes 을 nesting 해서 사용할 수가 있기 때문입니다.
M4 에서 매크로 확장은 CPP 의 function-like 매크로 에서처럼 전달된 인수 에서, 그리고 확장 결과에 대해서도 발생합니다. token 스트림을 읽어들이는 과정에서 앞서 정의된 매크로 이름이 발견되면 확장을 한 후에 다음 token 으로 넘어가는 것이 아니라 확장 결과를 다시 입력 스트림으로 넣어서 더 이상 확장이 발생하지 않을 때까지 읽어들이는 과정을 반복합니다.
다음 예제를 보면 bar
매크로를 define 한적이 없는데 bar
가 zoo
로
확장되는 것을 볼 수 있습니다.
이것은 두 번째 define 에서 foo 가 앞선 매크로 정의에 따라 먼저 bar 로
확장되었기 때문입니다.
$ m4 <<\@ 1. foo 는 현재 매크로 정의가 없으므로 그대로 foo 가 되고
define( foo, bar) bar 도 현재 매크로 정의가 없으므로 그대로 bar 가 되어
define( foo, zoo) foo 매크로는 bar 로 정의된다.
2. foo 매크로는 현재 bar 로 정의되어 있으므로 bar 로 확장되고
bar zoo 는 현재 매크로 정의가 없으므로 그대로 zoo 가 되어
@ bar 매크로는 zoo 로 정의된다.
3. 결과적으로 define( bar, zoo) 와 같게 되어
zoo bar 매크로는 zoo 로 확장됩니다.
token 스트림을 읽어들이는 과정에서 매크로 이름을 만나도 확장을 하지않고 다음 token 으로 넘어가는 경우가 있는데 바로 해당 token 이 quote 되었을 경우 입니다.
$ m4 <<\@
define( foo, bar) 1. foo 매크로가 bar 로 정의됨.
define( `foo', zoo) 2. quotes 에 의해 foo 는 확장이 일어나지 않고
다시 foo 매크로가 zoo 로 정의됨.
bar 3. bar 매크로는 이전에 정의된 적이 없으므로
@
bar 4. 그대로 bar 가 된다.
----------------------------
$ m4 <<\@
define( foo, bar)
define( `foo', zoo)
foo # foo 매크로는 zoo 로 확장된다.
@
zoo
token 을 읽어들이고 확장하는 과정을 evaluate 한다고 하는데 매 단계 evaluate 마다 quotes 이 한 단계씩 strip off 됩니다.
1. cleanup 은 현재 매크로 정의가 없으므로 그대로 cleanup 이 되고
`this is the cleanup message.' 는 확장없이 quotes 이 strip off 되어
cleanup 매크로는 this is the cleanup message. 로 정의된다.
2. cleanup 매크로가 확장되면 this is the cleanup message 가 되는데
확장 결과를 살펴보면 cleanup 매크로가 존재하므로 다시 확장이 되고
확장 결과를 살펴보면 cleanup 매크로가 존재하므로 다시 확장이 되고... 무한 반복된다.
$ m4 <<\@
define( cleanup, `This is the cleanup message.')
cleanup
@
this is the this is the this is the .... <---- infinite loop
---------------------------------------------------
1. `this is the `cleanup' message.' 는 확장없이 quotes 이 strip off 되어
cleanup 매크로는 this is the `cleanup' message. 로 정의된다.
2. cleanup 매크로가 확장되면 this is the `cleanup' message 가 되는데
cleanup 이름에 quotes 이 존재하므로 확장되지 않고 strip off 되어
this is the cleanup message. 가 된다.
$ m4 <<\@
define( cleanup, `This is the `cleanup' message.') # cleanup 에 quotes 추가
cleanup
@
This is the cleanup message.
$ m4 <<\@
define( foo, `$0') 1. foo 는 현재 매크로 정의가 없으므로
그대로 foo 가 되고 `$0' 는 quotes 이 제거되어
foo foo 매크로는 $0 로 정의된다.
@ 2. foo 는 $0 로 확장되는데 $0 는 매크로 자신의
Ctrl-c 종료 이름과 같으므로 foo 로 확장되고 foo 는 다시
^C <--- infinite loop $0 로 확장되고... 무한 반복된다.
----------------------------
$ m4 <<\@
define( foo, ``$0'') 1. ``$0'' 는 quotes 이 한 단계 제거되어
foo 매크로는 `$0' 로 정의된다.
foo 2. foo 매크로는 `foo' 로 확장되고
@ 3. `foo' 에서 확장이 중단되고
quotes 이 제거되어 foo 가 된다.
foo 4. 다음 token 을 읽어들임 ...
숫자나 특수문자는 매크로 이름이 될 수 없으므로 확장이 일어나지 않기 때문에 quote 을 하지 않아도 됩니다.
$ m4 <<\@
define(`foo', `bar') 1. `foo' 는 확장없이 quotes 이 제거되어 foo 가 되고
define(`bar', 100) `bar' 도 확장없이 quotes 이 제거되어 bar 가 되어
foo 매크로는 bar 로 정의된다.
foo 2. `bar' 는 확장없이 quotes 이 제거되어 bar 가 되고
@ bar 매크로는 100 으로 정의 된다.
3. foo 매크로는 bar 로 확장되고 다시
100 bar 매크로가 100 으로 확장된다.
--------------------------------
$ m4 <<\@
define(`foo', ``bar'') 1. foo 매크로는 `bar' 로 정의되고
define(`bar', 100) 2. bar 매크로는 100 으로 정의된다.
foo 3. foo 매크로는 `bar' 로 확장되고
@
4. `bar' 에서 확장이 중단되고
bar quotes 이 제거되어 bar 가 된다.
$ m4 <<\@
define(`foo', ``$*'') 1. foo 매크로는 `$*' 로 정의된다.
foo(100,200,300) 2. $* 는 전체 원소를 나타내므로 정의에 따라
@ `100,200,300' 로 확장된다.
100,200,300 3. quotes 이 제거되어 100,200,300 가 된다.
----------------------------
$ m4 <<\@
define(`foo', ``$@'') 1. foo 매크로는 `$@' 로 정의된다.
foo(100,200,300) 2. $@ 는 각각의 원소를 quote 한 것이므로
@ ``100',`200',`300'' 으로 확장된다.
`100',`200',`300' 3. quotes 이 한 단계 제거된다.
전달된 인수에서 prescan 에 의한 확장
$ m4 <<\@
define(`definenum', `define(`num', `99')') 1. definenum 매크로는
define(`num', `99') 로 정의된다.
num 2. num 매크로는 정의된 적이 없으므로
definenum num 그대로 num 이 된다.
@ 3. definenum 매크로가 확장되면 정의에 따라
define(`num', `99') 가 실행된다.
num 4. num 매크로가 정의되었으므로
99 99 로 확장된다.
-----------------------------------------
$ m4 <<\@
define(`definenum', define(`num', `99')) 1. define 매크로에 quotes 이 없으므로
prescan 에의해 확장이 되어
num num 매크로는 99 로 정의되고
definenum definenum 매크로는 void 로 정의된다.
@ 2. 이미 num 매크로가 정의된 상태이므로
99 로 확장되고 definenum 매크로는
99 void 로 확장된다.
$ m4 <<\@ 1. 전달된 인수가 prescan 에의해 quotes 이
define(`exch', ``$2', `$1'') 한 단계씩 제거되어 exch 매크로는
define(exch(`expansion text', `macro')) `$2', `$1' 로 정의 된다.
2. 인수들은 확장없이 quotes 이 제거되고
macro exch 매크로 정의에 따라
@ `macro', `expansion text' 로 확장된다.
3. define(`macro', `expansion text') 가되어
expansion text macro 매크로는 expansion text 로 정의된다.
$ m4 <<\@
define(`args', ``NAME', `Marie'') 1. args 매크로는 `NAME', `Marie' 로 정의된다.
define(args) 2. 전달된 args 가 prescan 에의해 확장되면
NAME define(`NAME', `Marie') 가 되고
NAME 은 Marie 로 정의된다.
1. define 매크로의 확장 결과는 void 이므로
args(define(`args',`Rachel')) args() 가 확장되면 NAME, Marie 가 된다.
args 2. 인수 부분에서 args 가 Rachel 로 재정의
@ 됐으므로 args 값은 Rachel 이 된다.
Marie
NAME, Marie
Rachel
함수가 중첩될 경우 quotes 의 처리
아래와 같은 매크로 호출이 있을 경우 zoo 매크로의 확장 결과가
`The brown fox jumped over'
이면 그 결과가 두 번째와 같이
그대로 bar 매크로의 인수 값으로 사용되는 것입니다.
마찬가지로 bar 매크로의 확장 결과가 ``The brown fox jumped over the lazy dog''
이면 그 결과가 세 번째와 같이 그대로 foo 매크로의 인수 값으로 사용됩니다.
인수 값으로 사용될 때는 prescan 에 의해 먼저 quotes 이 한 단계 제거되겠죠.
마지막으로 foo 매크로의 확장 결과가 ```The brown fox jumped over the lazy dog'''
이면 최종 결과에 대해서 main scan 이 발생하므로 실제 출력 결과는
``The brown fox jumped over the lazy dog''
가 됩니다.
foo( bar( zoo(`The brown fox'))) # 첫 번째
foo( bar(`The brown fox jumped over')) # 두 번째
foo(``The brown fox jumped over the lazy dog'') # 세 번째
확장 결과에 따른 quotes 처리 주의할 점
$ m4 <<\@
define(`foo', a'a) 1. foo 매크로는 a'a 로 정의된다.
define(`echo', `$@') 2. echo 매크로는 $@ 로 정의된다.
foo 3. foo 가 확장되면 a'a 가 된다.
echo(foo) 1. 전달된 foo 인수가 prescan 에의해 확장이 되면 a'a 가 되고
@ echo 매크로의 정의에 따라 $@ 는 `a'a' 로 확장된다.
확장 결과에 main scan 이 발생하면
a'a `a' 에서 quotes 이 제거되어 aa' 가 된다.
aa'
$ m4 <<\@
define(`l', `<[>') 1. 매크로 l 은 <[> 로 정의된다.
define(`r', `<]>') 2. 매크로 r 은 <]> 로 정의된다.
changequote(`[', `]') 3. defn 은 결과에 quotes 을 붙여 출력하므로
defn([l])defn([r])]) defn([l]) 는 [<[>] 로 확장되는데 여기서 처음 [ 문자는
defn([l],[r]) quote 의 시작이 되므로 마지막 ]) 까지 quote 처리가 되어
@ 결과적으로 <[>]defn([r])) 가 된다.
4. defn([l],[r]) 에서 [l] 은 [<[>] 로 확장되고
<[>]defn([r])) [r] 은 [<]>] 로 확장되면 결과가 [<[>][<]>] 가 되므로
<[>][<]> main scan 에의해 quotes 이 제거되면 <[>][<]> 가 된다.
출력에 newline 을 추가하는 방법
M4 에서는 기본적으로 escape character 를 사용할 수 없기 때문에 출력에 \n
를 사용할 수 없습니다.
$ m4 <<\@
11111111
format(`The brown fox jumped over\n the lazy dog\n')
22222222
@
11111111
The brown fox jumped over\n the lazy dog\n # newline 추가가 안된다.
22222222
따라서 newline 을 추가하려면 다음과 같이 quotes 을 활용해야 합니다.
$ m4 <<\@
11111111
format(`The brown fox jumped over the lazy dog
') <---- trailing newline 추가
22222222
@
11111111
The brown fox jumped over the lazy dog
<---- newline 이 추가됨
22222222
---------------------------------------
$ m4 <<\@
11111111
format(`The brown fox jumped over
the lazy dog
')
22222222
@
11111111
The brown fox jumped over
the lazy dog
22222222
changequote
changequote ( left, right )
changequote
매크로의 확장 결과는 void 입니다.
M4 는 shell 에서처럼 escape character 기능이 없습니다.
따라서 기본적으로 quotes 으로 사용되는 `
( backtick ) 문자를 출력에 사용할 수 없는데요.
하지만 changequote
built-in 매크로를 이용하면 quotes 문자 를 사용자가 임의로 변경해 사용할 수 있습니다.
quotes 문자는 2 문자 이상 사용할 수 있고 quotes 문자를 변경해 사용한 후에
다시 디폴트 문자로 돌아가려면 changequote
매크로를 ()
없이 사용하면 됩니다.
$ m4 <<\@
define(`escape', changequote(<!,!>)foo`bar<!!>changequote)
changequote(<!,!>)escape<!!>changequote
@
foo`bar # changequote 을 이용하면 ` 문자를 출력할 수 있다.
아래는 GNU Autoconf 패키지의 m4 파일에서 발췌한 내용인데요.
여기서 [
, ]
문자가 변경되어 사용되는 quotes 문자입니다.
스크립트 내용 중에는 sh
스크립트가 포함되는데 sh
에서는 backtick 문자가
명령 치환에 사용되고 single quotes 도 사용되므로 변경해서 사용하는 것입니다.
아래 내용 중 ac_cv_exeext=`expr "$ac_file" : ['[^.]*\(\..*\)']`
에서
바깥쪽 [
, ]
문자는 안쪽의 [
, ]
문자를 유지하기 위한 m4 quotes 입니다.
# _AC_COMPILER_EXEEXT_O
# ---------------------
# Check for the extension used when `-o foo'. Try to see if ac_cv_exeext,
# as computed by _AC_COMPILER_EXEEXT_DEFAULT is OK.
m4_define([_AC_COMPILER_EXEEXT_O],
[AC_MSG_CHECKING([for executable suffix])
AS_IF([AC_TRY_EVAL(ac_link)],
[# If both `conftest.exe' and `conftest' are `present' (well, observable)
# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will
# work properly (i.e., refer to `conftest.exe'), while it won't with
# `rm'.
for ac_file in `(ls conftest.exe; ls conftest; ls conftest.*) 2>/dev/null`; do
case $ac_file in
*.$ac_ext | *.o | *.obj | *.xcoff | *.tds | *.d | *.pdb ) ;;
*.* ) ac_cv_exeext=`expr "$ac_file" : ['[^.]*\(\..*\)']`
export ac_cv_exeext
break;;
* ) break;;
esac
done],
. . .
. . .
define
built-in 매크로 이름에 앞에m4_
가 붙은 것은 m4 명령의-P
옵션 사용입니다.
Quotes 사용을 일시적으로 중지하기
changequote(,)
형식을 사용하면 quotes 사용을 중지할 수 있습니다.
$ m4 <<\@ $ m4 <<\@
define(`foo', 100) changequote(,)
changequote(,) define(`foo', 100) # 매크로 이름에
# quotes 이 포함된다.
`foo' `foo'
foo foo
@ indir(`foo')
@
`100' # quotes 에서도 확장이된다. # 매크로 이름에 quotes 특수문자가 포함되어
100 `foo' # 확장되지 않는다 (indir 을 이용해야 한다)
foo
100
다음은 autoconf 패키지의 m4 파일 내용 중 일부인데 sh
스크립트 내용 중에 [0-5]
가 존재하여 일시적으로 quotes 사용을 중지하고 다시 설정하는 것을 볼 수 있습니다.
[if test -n "$gl_use_threads_default"; then
gl_use_threads="$gl_use_threads_default"
else
changequote(,)dnl # 일시적으로 quotes 사용을 중지
case "$host_os" in
osf*) gl_use_threads=no ;;
cygwin* | msys*)
case `uname -r` in
1.[0-5].*) gl_use_threads=no ;;
*) gl_use_threads=yes ;;
esac
;;
*) gl_use_threads=yes ;;
esac
changequote([,])dnl # 다시 quotes 을 [ , ] 로 설정
fi
])
if test "$gl_use_threads" = yes || test "$gl_use_threads" = posix; then
. . .
. . .
사용자 정의 quote 매크로
스크립트를 작성하다 보면 quotes 추가하는 것이 필요할 때가 있습니다.
이때는 다음과 같이 quote
, dquote
, dquote_elt
매크로를 정의해 사용할 수 있습니다.
$ m4 -del -t dquote -t dquote-elt -t ifelse <<\@
define(`quote', `ifelse(`$#', `0', `', ``$*'')')
define(`dquote', ``$@'')
define(`dquote_elt', `ifelse(`$#', `0', `', `$#', `1', ```$1''',
```$1'',$0(shift($@))')')
quote(`foo', `bar', `zoo')
dquote(`foo', `bar', `zoo')
dquote_elt(`foo', `bar', `zoo')
dquote(dquote_elt(`foo', `bar', `zoo'))
@
m4trace:5: -1- ifelse -> `foo,bar,zoo' # quote()
foo,bar,zoo
m4trace:6: -1- dquote -> ``foo',`bar',`zoo'' # dquote()
`foo',`bar',`zoo'
m4trace:7: -1- ifelse -> ``foo'',dquote_elt(shift(`foo',`bar',`zoo'))
`foo',m4trace:7: -1- ifelse -> ``bar'',dquote_elt(shift(`bar',`zoo'))
`bar',m4trace:7: -1- ifelse -> ``zoo'' # dquote_elt()
`zoo'
m4trace:8: -2- ifelse -> ``foo'',dquote_elt(shift(`foo',`bar',`zoo'))
m4trace:8: -2- ifelse -> ``bar'',dquote_elt(shift(`bar',`zoo'))
m4trace:8: -2- ifelse -> ``zoo''
m4trace:8: -1- dquote -> ```foo'',``bar'',``zoo''' # dquote(dquote_elt())
``foo'',``bar'',``zoo''
---------------------------------------------------------------------------
quote(`foo', `bar', `zoo') ---> `foo,bar,zoo' 로 확장
dquote(`foo', `bar', `zoo') ---> ``foo',`bar',`zoo'' 로 확장
dquote_elt(`foo', `bar', `zoo') ---> ``foo'',``bar'',``zoo'' 로 확장
dquote(dquote_elt(`foo', `bar', `zoo')) ---> ```foo'',``bar'',``zoo''' 로 확장
Quiz
다음 스크립트는 정상적으로 동작하는 것 같지만 문제가 있습니다. 어디가 잘못되었을까요?
$ m4 <<\@
define(`UL', `<ul>LI($1)</ul>')
define(`LI', `<li>$1</li>')
UL(`foo')
@
<ul><li>foo</li></ul>
만약에 UL
매크로의 인수 값으로 현재 정의되어 있는 매크로 이름을 전달하면
다음과 같이 정상적으로 동작하지 않게 됩니다.
$ m4 <<\@
define(`UL', `<ul>LI($1)</ul>')
define(`LI', `<li>$1</li>')
UL(`UL') // 현재 정의되어 있는 매크로 이름을 인수로 전달
@
<ul><li><ul><li></li></ul></li></ul>
------------------------------------
1. define `UL' 은 확장없이 quotes 이 strip off 되어 UL 이 되고
`<ul>LI($1)</ul>' 도 확장없이 quotes 이 strip off 되어 <ul>LI($1)</ul> 이 되어
UL 매크로는 <ul>LI($1)</ul> 로 정의된다.
2. define `LI' 은 확장없이 quotes 이 strip off 되어 LI 이 되고
`<li>$1</li>' 도 확장없이 quotes 이 strip off 되어 <li>$1</li> 이 되어
LI 매크로는 <li>$1</li> 로 정의된다.
3. UL(`UL') 이 확장될 때 `UL' 은 quotes 이 strip off 되어 UL 이 되어
<ul>LI(UL)</ul> 로 확장된다.
4. 확장된 결과를 살펴보면 LI 매크로와 UL 매크로가 존재하므로
먼저 인수부분의 UL 매크로가 확장되어 <ul>LI(<ul>LI()</ul>)</ul> 가 된다.
5. 다시 확장된 결과를 살펴보면 LI() 매크로가 존재하므로
<ul>LI(<ul><li></li></ul>)</ul> 로 확장된다.
6. 다시 확장된 결과를 살펴보면 LI 매크로가 존재하므로
<ul><li><ul><li></li></ul></li></ul> 로 확장됩니다.
따라서 현재 정의되어 있는 매크로 이름이 인수로 전달될 경우에도 정상적으로 동작하기 위해서는
다음과 같이 $1
에도 quote 을 해줘야 합니다.
이것은 매크로 확장은 quotes 이나 terminal token 을 만날때 까지 recursive 하게
확장되기 때문입니다.
$ m4 <<\@
define(`UL', `<ul>LI(`$1')</ul>') // $1 에도 quotes 을 추가
define(`LI', `<li>`$1'</li>')
UL(`UL')
@
<ul><li>UL</li></ul>
--------------------------------
1. define UL 매크로는 <ul>LI(`$1')</ul> 로 정의된다.
2. define LI 매크로는 <li>`$1'</li> 로 정의된다.
3. UL(`UL') 이 확장될 때 `UL' 은 quotes 이 strip off 되어 UL 이 되고
<ul>LI(`$1')</ul> 정의에 따라 <ul>LI(`UL')</ul> 로 확장된다.
4. 확장된 결과를 살펴보면 LI 매크로가 존재하므로 LI(`UL') 가 확장되어
<li>`$1'</li> 정의에 따라 <ul><li>`UL'</li></ul> 로 확장된다.
5. `UL' 은 확장없이 quotes 이 strip off 되고 최종적으로
<ul><li>UL</li></ul> 가 됩니다.
이번에는 ifelse
built-in 매크로를 이용해 반복 기능을 구현하는 것입니다.UL(`UL', `LI', `IT')
와 같이 여러 개의 값을 인수로 사용할 수 있습니다.
$ m4 <<\@
define(`UL', `<ul>IT($@)</ul>')
define(`LI', `ifelse(`$1', `', `', `<li>`$1'</li>')')
define(`IT', `ifelse($#, 1, `LI(`$1')', `LI(`$1')IT(shift($@))')')
UL(`UL', `LI', `IT')
@
<ul><li>UL</li><li>LI</li><li>IT</li></ul>
------------------------------------------
1. define UL 매크로는 <ul>IT($@)</ul> 로 정의된다.
2. define LI 매크로는 ifelse(`$1', `', `', `<li>`$1'</li>') 로 정의된다.
3. define IT 매크로는 ifelse($#, 1, `LI(`$1')', `LI(`$1')IT(shift($@))') 로 정의된다.
4. UL(`UL', `LI', `IT') 가 확장될 때 `UL', `LI', `IT' 는 quotes 이 strip off 되지만
$@ 에 의해 다시 quotes 이 추가되어 <ul>IT(`UL', `LI', `IT')</ul> 로 확장됩니다.
5. 확장된 결과를 살펴보면 IT 매크로가 존재하므로 다음과 같이 확장된다.
<ul>ifelse(`3', 1, `LI(`UL')', `LI(`UL')IT(shift(`UL', `LI', `IT'))')</ul>
6. 확장된 결과를 살펴보면 ifelse 매크로가 존재하므로 다음과 같이 확장된다.
`3' --> 3 으로 1 --> 1 으로 `LI(`UL')' --> LI(`UL') ... 로 확장
<ul>LI(`UL')IT(shift(`UL', `LI', `IT'))</ul>
7. 확장된 결과를 살펴보면 LI 매크로와 IT 매크로가 존재하므로
LI 매크로는 ifelse(`UL', `', `', `<li>`UL'</li>') ---> <li>`UL'</li> 로 확장되고
IT 매크로는 ifelse(`2', 1, `LI(`LI')', `LI(`LI')IT(shift(`LI', `IT'))')
---> LI(`LI')IT(shift(`LI', `IT')) 로 확장되어 최종 결과가
<ul><li>`UL'</li>LI(`LI')IT(shift(`LI', `IT'))</ul> 가 된다.
8. 확장된 결과를 살펴보면 LI 매크로와 IT 매크로가 존재하는데
이것은 6. 번 확장 결과와 닮은 꼴이므로 반복이 되면 결과적으로
<ul><li>UL</li><li>LI</li><li>IT</li></ul> 가 됩니다.