xparse(\NewDocumentCommand)

여러 선택 인수나 제대로 된 별표형 변종을 가진 명령을 만들고 싶어지면, \newcommand식으로 인수 개수만 세는 방식은 감당하기 어렵습니다. 이때 쓰는 현대적 인터페이스가 \NewDocumentCommand입니다. 각 인수의 *종류*를 문자들의 나열로 선언합니다. 원래는 xparse 패키지 기능이었지만 지금은 LaTeX 커널의 일부입니다. 이 페이지에서는 인수 지정 미니 언어와, 사용자가 실제로 무엇을 주었는지 확인하는 판정 명령을 다룹니다.

지금은 커널의 일부

예전에는 \usepackage{xparse}가 필요했지만, 2020년 10월 LaTeX 이후 이 구조는 LaTeX 커널에 들어갔습니다. \NewDocumentCommand 같은 명령은 처음부터 사용할 수 있고, xparse를 읽어들일 필요는 거의 없습니다. 커널 안에서 이 기능을 구현하는 파일은 ltcmd 라고 불리며, xparse 패키지 자체는 공식적으로 “obsolete”로 설명됩니다. 일부 비권장 인수형을 제외하면 새 코드는 xparse를 불러오지 않고 그대로 쓸 수 있습니다.

이 현대적 정의법의 생각은 의미(인터페이스)와 구현을 분리한다 는 것입니다. 사용자에게 보이는 인수 배열(필수, 선택, 별표)을 “인수 지정(argument specification, arg-spec)”으로 선언하면, 본문 코드는 언제나 #1, #2 같은 정규화된 형태로 인수를 받습니다. \newcommand의 기초는 “매크로 정의” 페이지에서 다루었으니, 아직 읽지 않았다면 먼저 보면 차이가 더 잘 보입니다.

명령과 환경 정의하기

기본 형태는 \NewDocumentCommand{\cmd}{⟨arg-spec⟩}{⟨code⟩} 입니다. 첫 번째 인수는 만들 명령 이름, 두 번째는 인수 지정, 세 번째는 본문입니다. \newcommand와 마찬가지로 목적이 다른 세 가지 친척이 있고, 차이는 이미 정의된 이름에 어떻게 동작하느냐입니다.

명령기존 이름에 대해
\NewDocumentCommand이미 정의되어 있으면 오류
\RenewDocumentCommand아직 정의되지 않았으면 오류(기존 것을 다시 만듦)
\ProvideDocumentCommand아직 없을 때만 정의
\DeclareDocumentCommand항상 덮어씀(신중히 사용)

환경에도 같은 구조가 있어 \NewDocumentEnvironment{⟨env⟩}{⟨arg-spec⟩}{⟨start code⟩}{⟨end code⟩} 로 정의합니다(\Renew…, \Provide…, \Declare…도 동일). 인수는 \begin{⟨env⟩} 뒤에 주어지고, 시작 코드와 종료 코드 양쪽에서 참조할 수 있습니다. 자세한 내용은 “사용자 환경” 페이지를 보세요. 또한 이렇게 만든 모든 명령은 자동으로 robust 해집니다(내부적으로 ε-TeX의 \protected 구조 사용). 따라서 제목이나 캡션에서 \protect를 걱정하지 않아도 됩니다.

\NewExpandableDocumentCommand 라는 전개 가능한 버전도 있습니다. \edef\write 안처럼 명령이 전개되는 문맥에서 쓸 수 있지만, 대신 허용되는 인수형에 제한이 있습니다. 뒤에서 설명할 s/t 같은 참거짓 판정이나 v 등은 사용할 수 없습니다. 보통 문서 작업에는 일반 버전이면 충분합니다.

인수 지정 언어

인수 지정은 한 글자씩 하나의 인수 “타입”을 나타내는 문자열 입니다. 필수인지, 선택인지, 별표인지, 특정 문자로 구분되는지 등을 글자로 나열합니다. 각 타입은 본문에 #1, #2 등으로 도착하지만, 핵심은 주어지지 않은 선택 인수에는 특수한 값이 들어간다 는 점입니다. 주요 타입은 다음 표와 같습니다.

타입쓰는 법의미본문에서 받는 형태
mm필수 {} 인수보통의 #1
oo선택 [...] 인수없으면 -NoValue-
OO{default}기본값 있는 선택 인수없으면 default
ss선택 별표 *\BooleanTrue / \BooleanFalse
tt⟨char⟩지정한 문자 토큰의 선택 여부\BooleanTrue / \BooleanFalse
rr⟨d1⟩⟨d2⟩d1d2로 구분되는 필수 인수없으면 오류 뒤 -NoValue-
dd⟨d1⟩⟨d2⟩d1d2로 구분되는 선택 인수없으면 -NoValue-
ee{⟨tokens⟩}장식자(^, _ 등)각각 없으면 -NoValue-
vvverbatim 인수그대로의 문자들

m 은 가장 기본적인 필수 인수입니다. {...}나 단일 토큰을 받고, 바깥 {}는 제거되어 본문으로 전달됩니다. o 는 표준 선택 인수 [...], O{default} 는 여기에 기본값을 준 버전입니다. O, D 같은 대문자 타입은 기본값을 지정할 수 있고, o, d 같은 소문자판은 대신 특수 표시 -NoValue-를 반환합니다.

s 는 별표 *의 유무를 보는 타입입니다. *가 있으면 \BooleanTrue, 없으면 \BooleanFalse가 들어갑니다. 임의의 한 문자로 일반화한 것이 t⟨char⟩ 이며, 예를 들어 t++의 유무를 참거짓으로 받습니다. r⟨d1⟩⟨d2⟩ 는 직접 고른 구분자(예: r()의 괄호)로 감싸는 “필수” 구분 인수이고, 여는 쪽이 없으면 오류를 내고 -NoValue-를 채웁니다. 구분자는 있지만 선택 사항인 것이 d⟨d1⟩⟨d2⟩ 입니다.

조금 특이한 것은 e{⟨tokens⟩} 로, ^_ 같은 장식자(embellishments) 를 모아 받습니다. e{^_}라고 하면 ^{...}_{...}를 임의의 순서로 주워 오고, 주어지지 않은 것은 -NoValue-가 됩니다. 나열하는 토큰은 모두 서로 달라야 합니다. v\verb처럼 verbatim 인수를 읽는 타입입니다. 구분형(r, d 등)의 구분자로는 TeX 그룹화 문자 {}를 쓸 수 없으므로, 보통 [], (), ""처럼 자연스럽게 짝이 되는 문자를 선택합니다.

인수 검사: 판정 명령

선택 인수나 별표를 다루려면, 본문 안에서 “주어졌는가”를 판정해야 합니다. 이를 위한 전용 명령은 두 계열입니다. 먼저 \IfNoValueTF{#1}{⟨if no value⟩}{⟨if value⟩} 는 인수가 -NoValue-인지, 즉 주어지지 않았는지 확인합니다. 값이 없으면 첫 번째 분기, 있으면 두 번째 분기를 실행합니다. 반대 방향의 \IfValueTF 도 있고, T만 또는 F만 있는 형태(\IfNoValueT / \IfNoValueF, \IfValueT / \IfValueF)도 쓸 수 있습니다.

st로 받은 참거짓 값에는 전용 \IfBooleanTF{#1}{⟨if starred⟩}{⟨if not⟩} 를 씁니다. #1\BooleanTrue이면 첫 번째 분기, \BooleanFalse이면 두 번째 분기를 실행합니다(\IfBooleanT / \IfBooleanF도 있음). 요령은 \IfNoValueTF 계열은 o/d 같은 선택 인수에, \IfBooleanTF 계열은 s/t의 참거짓 인수에 쓴다는 것입니다. 또한 -NoValue-는 문자열 -NoValue-와 일치하지 않도록 만들어져 있으므로, 문자열 비교가 아니라 반드시 \IfNoValueTF로 판정하세요.

실례: 별표와 기본값 있는 선택 인수

s O{} m이라는 인수 지정으로 별표형 변종과 기본값 있는 선택 인수를 모두 가진 명령을 만들어 봅니다. 여기서는 제목 명령을 예로 듭니다. #1이 별표(\IfBooleanTF로 판정), #2가 선택 짧은 제목(기본값은 빈 문자열), #3가 본 제목입니다.

latex
\NewDocumentCommand{\heading}{s O{} m}{%
  \IfBooleanTF{#1}%
    {\textbf{#3}}%                       星つき: 番号なしの見出し / starred: unnumbered
    {\section%
       \IfNoValueTF{#2}%
         {{#3}}%                          短縮見出しなし / no short title
         {[#2]{#3}}%                       短縮見出しあり / with short title
    }%
}

% 使用例 / usage
\heading{Introduction}             % 通常の番号つき節
\heading[Intro]{A Long Introduction} % 目次用に短縮見出しを指定
\heading*{Preface}                 % 星つき: 太字の無番号見出し

세부 사항 하나: oO{}를 섞어 생각하지 마세요. o를 쓰면 지정되지 않은 인수가 -NoValue-가 되므로 위처럼 \IfNoValueTF로 분기할 수 있습니다. 반면 O{}처럼 기본값을 주면 값이 항상 존재합니다(미지정 시 빈 값). 이때는 \IfNoValueTF가 아니라 \tl_if_blank:nTF(expl3)나 \ifblank(etoolbox)으로 “내용이 비었는가”를 검사해야 합니다. 위 예제가 의도대로 동작하려면 선택 인수를 o(기본값 없음)로 두어야 합니다.

\newcommand보다 나은 이유

\newcommand의 선택 인수는 맨 앞 하나뿐 이고, 별표형 변종도 기본 제공하지 않습니다. 전통적으로는 이를 보완하려고 \@ifstar\@ifnextchar 같은 저수준 장치를 직접 썼지만, 이들은 \makeatletter가 필요하고 공백 처리나 중첩에서 깨지기 쉬우며 읽기도 어렵습니다.

  • 여러 독립 선택 인수 를 가질 수 있습니다(o o m, O{a} O{b} m 등).
  • s\IfBooleanTF진짜 별표형 변종 을 다룹니다. \@ifstar 해킹이 필요 없습니다.
  • 기본값이 선언적 입니다. O{default}라고 쓰기만 하면 미지정 시 값이 정해집니다.
  • 처음부터 robust 합니다. 제목과 캡션에서도 \protect가 필요 없습니다.
  • 구분 인수(r, d)나 장식자(e)처럼 \newcommand로는 표현할 수 없는 입력 문법도 표현할 수 있습니다.

정리하면 아주 단순한 축약 매크로라면 \newcommand로 충분하지만, 인수 구조가 조금이라도 복잡해지면 \NewDocumentCommand가 첫 번째 후보 입니다. 더 나아가 본문 처리를 프로그램처럼 쓰고 싶다면 expl3(LaTeX3 프로그래밍 계층)와 조합하는 것이 자연스러운 발전입니다. 클래스와 패키지를 만드는 법은 “클래스와 패키지 작성” 페이지를 보세요.