카테고리 코드와 \makeatletter

TeX가 소스를 읽을 때 왜 \는 명령의 시작이 되고, {는 그룹을 여는 것일까요? 답은 카테고리 코드(catcode) 입니다. 입력 문자마다 붙은 숫자가 토크나이저에게 그 문자의 역할을 알려 줍니다. 같은 구조가 LaTeX 내부 명령에 나타나는 @의 정체와, 프리앰블에서 내부 명령을 쓸 때 \makeatletter\makeatother로 감싸야 하는 이유도 설명합니다. 이 페이지에서는 그 의미와 사용처를 다룹니다.

카테고리 코드란 무엇인가

TeX의 처리는 입력을 문자열에서 토큰 열로 바꾸는 토큰화 에서 시작합니다. 이때 TeX는 읽은 문자 하나하나의 카테고리 코드(catcode), 0부터 15까지의 번호를 보고 그 문자가 조판에서 어떤 역할을 하는지 결정합니다. 예를 들어 \는 catcode 0(escape, 명령 시작), {는 catcode 1(그룹 시작), $는 catcode 3(수식 모드 전환)입니다. 핵심은 역할이 문자 자체가 아니라 catcode로 결정된다 는 점입니다. $가 수식으로 전환하는 것은 “달러 기호라서”가 아니라 “catcode 3이라서”입니다.

다음 표는 기본 카테고리 코드의 전체 모습입니다. 일상적으로 의식하는 것은 명령을 시작하는 0, 그룹의 1/2, 수식의 3, 표의 열 구분자 4, 문단을 만드는 줄끝 5, 매크로 인수 표시 6, 위첨자와 아래첨자의 7/8, 본문을 이루는 10/11/12, 그리고 주석의 14 정도입니다.

catcode역할기본 문자
0escape(명령 시작)백슬래시 \
1그룹 시작왼쪽 중괄호 {
2그룹 끝오른쪽 중괄호 }
3수식 모드 전환달러 기호 $
4정렬 탭앰퍼샌드 &
5줄끝리턴 / 줄바꿈
6매개변수(매크로 인수)해시 #
7위첨자캐럿 ^
8아래첨자밑줄 _
9무시되는 문자널 문자
10공백공백과 탭
11문자(명령 이름 구성)a–z, A–Z
12기타숫자, 문장부호, 나머지 전부
13활성 문자(그 자체가 명령처럼 동작)틸드 ~
14주석퍼센트 %
15유효하지 않은 문자삭제 문자

catcode 11(문자)과 명령 이름

카테고리 중에서도 11(문자) 은 명령 구조와 직접 연결되는 특별한 존재입니다. TeX의 제어어(control word), 즉 \section 같은 이름 있는 명령은 catcode 0 문자(보통 \) 뒤에 이어지는 catcode 11 문자들의 연속 으로 인식됩니다. 즉 원칙적으로 명령 이름을 만들 수 있는 것은 catcode 11 문자뿐입니다. \section의 이름이 section에서 끝나는 이유는 다음 문자(공백이나 {)가 catcode 11이 아니기 때문입니다. 제어어 뒤의 공백이 사라지는 것, \LaTeXlogo가 다른 명령으로 읽히는 것 같은 사용자 수준의 결과는 “문법 규칙” 페이지에서 다루었습니다. 여기서는 그 바탕의 catcode를 봅니다.

숫자와 문장부호가 catcode 12(기타) 라는 점도 여기서 중요합니다. a2b 중 명령 이름에 들어갈 수 있는 것은 문자뿐이므로, \a2\a라는 명령 뒤에 문자 2가 온 것으로 해석됩니다. 반대로 어떤 문자의 catcode를 11로 바꾸면 그 문자를 명령 이름의 일부로 사용할 수 있게 됩니다. 이 한 수가 다음에 볼 @ 이야기의 열쇠입니다.

\catcode로 카테고리 바꾸기

문자와 카테고리의 대응은 고정되어 있지 않고, primitive \catcode 로 바꿀 수 있습니다. 문법은 ` \catcode⟨char⟩=⟨number⟩ ` 입니다. 백틱 ` 뒤에 대상 문자를 쓰고, = 오른쪽에 새 catcode를 지정합니다. 예를 들어 \catcode@=11 ` 은 “이후 @`를 문자(catcode 11)로 취급하라”는 지시입니다.

latex
% catcode を直接いじる(上級・壊れやすい)/ Touching catcodes directly (advanced, fragile)
\catcode`@=11   % @ を英字に / make @ a letter
\catcode`@=12   % @ を「その他」に戻す / put @ back to “other”

이는 강력하지만 fragile 한 조작이며, 그대로 쓰는 것은 고급 사용자용입니다. 카테고리를 바꾸면 이후의 해석 전체가 바뀌므로, 되돌리는 것을 잊으면 뜻밖의 곳이 깨집니다. 한편 이 구조는 익숙한 기능의 기반이기도 합니다. \verbverbatim 환경이 소스를 그대로 출력할 수 있는 것은 내부에서 특수 문자(\, {, $ 등)의 catcode를 일시적으로 12(기타)로 낮추어, 명령이나 그룹으로 해석되지 않게 하기 때문입니다. 다음 절의 \makeatletter는 이 \catcode 조작에 안전한 이름을 붙인 표준 wrapper 라고 생각하면 이해하기 쉽습니다.

\makeatletter\makeatother

LaTeX 커널과 패키지의 내부 명령 중에는 이름에 @가 들어가는 것이 많습니다. 절 제목을 조립하는 \@startsection, 다음 토큰을 들여다보고 분기하는 \@ifnextchar, 길이 0pt를 나타내는 \p@ 등이 있습니다. 이들이 .sty/.cls 파일 안에서 문제없이 정의되고 사용되는 것은, 그 안에서는 @catcode 11(문자) 이 되어 명령 이름의 일부로 다루어지기 때문입니다. 실제로 \usepackage\documentclass가 패키지나 클래스를 읽는 동안에는 @의 catcode를 11로 바꾸어 줍니다.

하지만 보통 문서 본문에서는 @catcode 12(기타) 입니다. 그래서 본문이나 프리앰블에 그대로 \p@라고 쓰면, TeX는 그것을 \p라는 명령 뒤에 문자 @가 온 것으로 읽고, 의도한 내부 명령에는 닿지 않습니다. 내부 명령을 사용하거나 재정의하는 구간만 @를 문자로 돌리는 선언이 \makeatletter 입니다. 짝이 되는 \makeatother@의 catcode를 기본값 12로 되돌립니다. 이름 그대로 “@를 letter로 만들기 / other로 만들기”이며, 내부는 정확히 ` \catcode@=11 ` \catcode@=12 `` 입니다.

왜 이런 약속이 있을까요? 내부 명령을 일반 사용자의 손이 닿지 않는 곳에 격리하기 위해서 입니다. @가 기본적으로 catcode 12이면, 단지 문서를 쓰는 사람이 \@startsection 같은 이름을 실수로 자기 명령으로 덮어쓰는 일이 생기지 않습니다. @가 들어간 이름은 “내부용”이라는 표식이고, 그것을 만지려면 \makeatletter로 명시적으로 문을 열어야 합니다.

예제와 함정

전형적인 용도는 클래스나 패키지가 정의한 내부 명령을 프리앰블에서 조금 손보는 것입니다. 다음 예에서는 제목 영역을 조판하는 내부 매크로 \@maketitle\makeatletter\makeatother로 감싸 다시 정의합니다. 감싼 안에서만 @가 문자이므로, \@maketitle이라는 이름이 하나의 명령으로 올바르게 인식됩니다.

document.tex
\documentclass{article}

\makeatletter
% 内部命令 \@maketitle を再定義する / redefine the internal \@maketitle
\renewcommand{\@maketitle}{%
  \begin{center}
    {\LARGE\bfseries \@title}\par
    \vspace{1ex}{\large \@author}\par
  \end{center}%
}
\makeatother

\title{カテゴリコード入門}
\author{名前}
\begin{document}
\maketitle
\end{document}

가장 큰 함정은 \makeatother를 쓰지 않는 것 입니다. 닫는 것을 잊으면 @가 문자 상태로 뒤까지 이어지고, 이메일 주소처럼 본문 중 @가 들어간 부분이나 @를 특별히 다루는 일부 패키지의 동작이 깨집니다. \makeatletter를 썼다면 반드시 짝인 \makeatother로 닫습니다. 이 둘은 항상 쌍으로 쓴다고 기억하세요.

또 다른 오해는 .sty/.cls 파일 안에서 \makeatletter를 쓰는 것 입니다. 앞서 말했듯 패키지와 클래스를 읽는 중에는 이미 @가 catcode 11이므로, 그 안에서는 필요 없고, 오히려 불필요한 \makeatother가 다른 처리를 어지럽힐 수 있습니다. \makeatletter\makeatother는 어디까지나 .tex 문서, 보통 프리앰블에서 내부 명령을 만질 때 쓰는 것입니다. 내부 명령을 굳이 만지지 않아도 된다면 \renewcommand로 공개 명령을 바꾸거나, 목적 기능을 제공하는 정식 패키지를 쓰는 쪽이 더 안전합니다. 본격적인 매크로와 패키지 작성은 별도 페이지에서 다룹니다.