expl3 / LaTeX3 계층

expl3(“expl-three”)는 현대 LaTeX를 내부에서 떠받치는 프로그래밍 언어입니다. 한때 “LaTeX3”라고 불렸던 작업에서 나왔고, 지금은 LaTeX 커널에 기본으로 로드됩니다. \newcommand로 쓸 수 있는 범위를 훨씬 넘어 변수, 자료구조, 정수 연산, 심지어 부동소수점까지 갖춘 본격적인 계층이며, xparse, siunitx, fontspec 같은 패키지가 그 위에 만들어져 있습니다. 이 페이지에서는 문법을 켜는 법, 독특한 명명 규칙, 핵심 모듈을 차례로 봅니다.

expl3란 무엇인가

TeX는 근본적으로 매크로 처리기입니다. \def\newcommand로 명령(매크로)을 정의합니다. 하지만 큰 패키지를 쓰려고 하면 TeX의 원시 primitive는 일관성이 부족하고, 전개 제어와 변수 처리가 장인 기술이 되기 쉽습니다. expl3 는 TeX와 e-TeX primitive에 새 이름을 붙이고, 함수와 변수를 체계적으로 이름 붙이며, 각 함수의 인수 타입을 명시하는 일관된 인터페이스를 제공합니다. LaTeX Project가 오랜 기간 정비한, 말하자면 LaTeX를 위한 표준 라이브러리이자 프로그래밍 언어입니다.

실용상 중요한 사실이 하나 있습니다. 새로운 LaTeX 커널에는 expl3가 기본으로 로드된다 는 점입니다. 공식 interface3 매뉴얼도 최신 LaTeX 2ε 커널에서는 이 자료가 format의 일부로 로드된다고 명시합니다. 따라서 더 이상 \usepackage{expl3}를 쓸 필요가 없습니다. 대부분의 사용자는 expl3를 직접 쓰기보다 xparse(\NewDocumentCommand)나 siunitx, fontspec, l3keys2e 같은 패키지를 통해 간접적으로 만납니다.

문법 전환 — \ExplSyntaxOn

expl3 코드는 \ExplSyntaxOn\ExplSyntaxOff로 둘러싼 구간에 씁니다. 이 구간에서는 문자를 읽는 방식, 즉 category code가 두 가지 바뀝니다. 첫째, 공백과 줄바꿈이 무시됩니다. 그래서 코드를 들여쓰고 토큰 사이에 여백을 넣어 읽기 쉽게 쓸 수 있습니다. 둘째, _(밑줄)와 :(콜론)이 “문자”로 취급됩니다. 보통 LaTeX에서는 명령 이름에 쓸 수 없지만, expl3에서는 이들을 포함한 길고 설명적인 명령 이름을 만들 수 있습니다.

공백이 무시된다는 것은, 출력에 진짜 공백을 넣으려면 다른 표기가 필요하다 는 뜻이기도 합니다. expl3 구간에서는 반각 공백을 ~(틸드)로 씁니다. 예를 들어 본문에 “Item: apple”을 내고 싶다면 Item:~#1처럼 씁니다. 그냥 공백은 사라지기 때문입니다. 이 한 가지를 익히면 나머지는 일반 프로그래밍 언어처럼 읽고 쓸 수 있습니다.

document.tex
% expl3 はカーネルに含まれるので \usepackage{expl3} は不要
\ExplSyntaxOn
  % ここでは空白・改行が無視され、 _ と : が命令名に使える
  \tl_new:N  \l_greeting_tl
  \tl_set:Nn \l_greeting_tl { Hello,~world! }
  \tl_use:N  \l_greeting_tl
\ExplSyntaxOff

명명 규칙과 인수 시그니처

expl3 함수 이름은 \⟨module⟩_⟨description⟩:⟨arg-signature⟩ 형식입니다. 첫 _까지가 모듈 이름(자료형이나 기능 분류), : 앞까지가 설명적인 이름, : 뒤가 인수 시그니처(arg-spec)입니다. 예를 들어 \seq_put_right:Nn에서는 모듈이 seq(sequence), 설명은 put_right(오른쪽에 추가), 시그니처는 Nn입니다. 시그니처의 각 문자는 그 위치의 인수를 넘기기 전에 어떻게 처리할지 나타냅니다.

주요 인수 지정자는 다음과 같습니다(공식 interface3 매뉴얼 기준). Nn이 가장 기본이며, 이것만으로도 꽤 많은 일을 할 수 있습니다. 전개를 동반하는 x, e, o, f나 변수 값을 꺼내는 V, v는 익숙해진 뒤 조금씩 배우면 됩니다.

지정자의미
N가공하지 않음. 단일 토큰(대개 제어철 하나).
n가공하지 않음. 중괄호로 묶은 토큰열.
c내용을 \csname으로 제어철로 변환 한 뒤 넘김.
V / v변수의 값 을 꺼내 넘김(V는 단일 토큰, v는 이름을 먼저 구성).
o인수를 한 번만 전개 한 뒤 넘김.
x / e완전 전개(x\edef 유사, 전개 불가; e\expanded 사용).
f왼쪽에서 오른쪽으로 첫 번째 전개 불가능 토큰까지 전개.
pTeX의 매개변수 텍스트(#1#2…). 함수 정의에서 사용.

변수도 비슷하게 이름 붙이지만, 앞에 스코프를 나타내는 한 글자 가 붙습니다. l_지역(현재 TeX 그룹 안에서만 변경), g_전역, c_상수(값을 바꾸지 않음)입니다. 변수 이름 끝에는 타입을 나타내는 식별자가 붙어 _tl(토큰 리스트), _int(정수), _seq(시퀀스), _prop(프로퍼티 리스트), _clist(콤마 리스트), _fp(부동소수점) 등이 됩니다. 예를 들어 \l_my_name_tl은 “지역 토큰 리스트 변수”, \g_counter_int는 “전역 정수 변수”로 바로 읽힙니다. 각 모듈은 \l_tmpa_tl, \l_tmpb_int 같은 scratch 변수(임시 변수)도 제공합니다.

핵심 모듈

expl3는 자료형별 모듈로 나뉘며, 모두 “만들기, 설정하기, 사용하기”라는 같은 리듬으로 조작할 수 있습니다. 먼저 함수 정의 입니다. \cs_new:Npn은 새 함수를 정의하고, 같은 이름의 함수가 이미 있으면 오류를 냅니다(cs는 control sequence). \cs_set:Npn도 정의하지만 현재 TeX 그룹 안에 한정되고, 재정의해도 오류가 나지 않습니다. 둘 다 :Npn을 쓰며, N은 정의할 함수 이름, p는 매개변수 텍스트(#1#2…), n은 본문(치환 텍스트)을 뜻합니다.

다음은 토큰 리스트(tl) 입니다. 문자열처럼 쓸 수 있는 가장 기본적인 변수입니다. \tl_new:N으로 선언하고, \tl_set:Nn으로 내용을 바꾸며(이전 내용은 사라짐), \tl_use:N으로 본문에 출력합니다. 정수(int) 도 같은 형태입니다. \int_new:N으로 선언, \int_set:Nn으로 대입하고, \int_eval:n은 안의 정수식을 계산해 값을 반환합니다. 예를 들어 \int_eval:n { 2 + 3 * 4 }는 14입니다.

시퀀스(seq) 는 양끝에서 넣고 뺄 수 있는 리스트로, 스택처럼도 쓸 수 있습니다. \seq_new:N으로 만들고, \seq_put_right:Nn으로 오른쪽 끝에 요소를 추가하며, \seq_map_inline:Nn으로 모든 요소를 순서대로 처리합니다. 이때 각 요소는 #1로 전달됩니다. 프로퍼티 리스트(prop) 는 이른바 딕셔너리, 즉 키와 값의 대응표입니다. \prop_new:N으로 만들고, \prop_put:Nnn으로 키와 값의 쌍을 저장합니다(Nnn은 “변수, 키, 값” 세 인수).

콤마 리스트(clist) 는 이름 그대로 쉼표로 구분된 값들의 나열을 다루는 모듈이며, 명령은 모두 \clist_로 시작합니다(\clist_new:N, \clist_set:Nn 등). 마지막으로 부동소수점(fp) 입니다. \fp_eval:n은 소수를 포함한 식을 평가하고, sin, sqrt, pi 같은 여러 과학 함수도 지원합니다. 예를 들어 \fp_eval:n { sqrt(2) }\fp_eval:n { 2 * pi }를 쓸 수 있습니다. 정수만 다루는 \int_eval:n과 구분하는 것이 핵심입니다.

작은 예 — 함수와 시퀀스

위 내용을 조합해 봅니다. 다음 예제는 \cs_new:Npn으로 “시퀀스에 항목 하나를 추가하는” 함수 \example_add:n을 정의하고, 몇 번 호출한 뒤 \seq_map_inline:Nn으로 모든 항목을 목록처럼 출력합니다. 명령 이름에 _:가 들어 있어도 \ExplSyntaxOn 구간이므로 문제없이 쓸 수 있다는 점에 주목하세요.

document.tex
\documentclass{article}
\begin{document}
\ExplSyntaxOn
  % シーケンスを 1 個用意する
  \seq_new:N \l_example_fruits_seq

  % 「右端に 1 項目追加する」関数を定義する
  \cs_new:Npn \example_add:n #1
    { \seq_put_right:Nn \l_example_fruits_seq {#1} }

  % 何度か呼ぶ
  \example_add:n { apple }
  \example_add:n { banana }
  \example_add:n { cherry }

  % 全項目を出力する(各項目が #1 に入る。 ~ は空白)
  \seq_map_inline:Nn \l_example_fruits_seq
    { Fruit:~#1 \par }
\ExplSyntaxOff
\end{document}

컴파일하면 “Fruit: apple”, “Fruit: banana”, “Fruit: cherry”가 세 줄로 나옵니다. \cs_new:Npn#1은 정의되는 함수의 인수이고, \seq_map_inline:Nn#1은 순회 중인 각 요소입니다. 둘 다 n 타입(중괄호 내용)으로 취급됩니다. Fruit:~#1~에 주의하세요. 그냥 공백으로 바꾸면 공백이 무시되어 “Fruit:apple”처럼 붙습니다.

실제로 쓰이는 방식

일반적인 문서 작성에서는 expl3를 직접 쓸 필요가 거의 없습니다. 하지만 패키지나 클래스를 만드는 단계가 되면 expl3는 이제 사실상의 표준입니다. 사용자용 명령은 \NewDocumentCommand(xparse)로 받고, 그 내부를 expl3로 구현하는 조합이 자주 쓰입니다. xparse의 인수 지정(m, O{...}, s 같은 문서 수준 인수)과 expl3의 인수 시그니처(N, n 같은 프로그래밍 수준 처리 지정)는 다른 것이므로 혼동하지 않아야 합니다. 전자는 xparse 페이지에서 자세히 다룹니다.

expl3를 배우면 \newcommand만으로는 어려웠던 처리를 보기 좋게 쓸 수 있습니다. 기억할 요점은 다음과 같습니다.

  • 코드는 반드시 \ExplSyntaxOn\ExplSyntaxOff로 감쌉니다. 구간 안에서는 공백이 무시되며, 출력할 공백은 ~를 씁니다.
  • 커널에 포함되어 있으므로 \usepackage{expl3}는 필요 없습니다. .sty 안에서는 패키지 선언 뒤에 바로 쓸 수 있습니다.
  • 함수는 \⟨module⟩_⟨description⟩:⟨signature⟩, 변수는 \⟨scope⟩_⟨name⟩_⟨type⟩입니다. 스코프는 l_ / g_ / c_입니다.
  • 확신 없는 명령 이름을 만들지 않습니다. 명명은 엄격하며, 공식 interface3 매뉴얼(texdoc interface3)이 1차 자료입니다.