3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Hello World あたたたた 20日目 expl3編

Last updated at Posted at 2025-12-19

この記事は Hello World あたたたた Advent Calendar 2025 の20日目の記事、かつTeX & LaTeX Advent Calendar 2025の20日目の記事です。

今日はexpl3で「Hello World あたたたた」を実装して解説していきます。

そもそも「Hello World あたたたた」が何かは 1日目の記事 をご覧ください。

……えっ、このアドベントカレンダーは「変な言語を使ってみる」という趣旨ではない? いえいえ、expl3は決して変な言語ではありません

LaTeXについては既に知っているものとします。

コーディング例

Overleafで実際に動く様子を見ることができます。

expl3は「LaTeXの機能(命令や環境)を実装するための言語」であるため、ここでも「Hello World あたたたた」の出力を行うLaTeX命令という形で実装します。命令の名前は\HelloWorldAtatatataです。

hwata.sty
% expl3を利用するLaTeXパッケージの宣言
\ProvidesExplPackage {hwata} {2025-12-20} {0.2.0} {Hello World Atatatata}

% 出力した文字をためていく変数
\str_new:N \l_hwata_hako_str
% プログラムを続けるかどうかのフラグ
\bool_new:N \l_hwata_running_bool
% ランダム値(0 または 1)
\int_new:N \l_hwata_x_int
% 選ばれた文字(「あ」または「た」)
\str_new:N \l_hwata_char_str

% 命令 \HelloWorldAtatatata の定義
\NewDocumentCommand \HelloWorldAtatatata {}
  {
    \str_clear:N \l_hwata_hako_str
    \bool_set_true:N \l_hwata_running_bool

    \bool_while_do:Nn \l_hwata_running_bool
      {
        % 0 または 1 をランダムに生成
        \int_set:Nn \l_hwata_x_int { \int_rand:nn { 0 } { 1 } }

        % 条件に応じて「あ」または「た」を選ぶ
        \int_compare:nNnTF { \l_hwata_x_int } = { 0 }
          { \str_set:Nn \l_hwata_char_str {} }
          {
            \int_compare:nNnT { \l_hwata_x_int } = { 1 }
              { \str_set:Nn \l_hwata_char_str {} }
          }

        % 版面に出力する
        \l_hwata_char_str

        % 出力した文字を hako に追加
        \str_put_right:Ne \l_hwata_hako_str { \l_hwata_char_str }

        % 最後の 5 文字が「あたたたた」なら終了
        \str_compare:eNeT { \str_range:Nnn \l_hwata_hako_str { -5 } { -1 } } = { あたたたた }
          {
            \par
            お前はもう死んでいる \par
            \bool_set_false:N \l_hwata_running_bool
          }
      }
  }

このhwata.styファイルは「\HelloWorldAtatatata命令を提供するhwataパッケージ」として機能するので、このパッケージを実際に利用するLaTeX文書(ここではLuaLaTeXを使います1)を用意します。

example.tex
% LuaLaTeX文書
\documentclass[a4paper]{ltjsarticle}
% 'hwata'パッケージを読み込む
\usepackage{hwata}
\begin{document}
\HelloWorldAtatatata % 命令を実行
\end{document}

LuaLaTeXを実行すると、以下の出力(組版結果)が得られます。

image-1.png
出力結果(一例)

コードと文法の解説

この節の説明から判るように、「Hello World あたたたた」のロジックそのまま素直に実装しています。expl3が(見た目は少し異様ですが)“変な言語”ではないことがお判りでしょう。

コードの冒頭

LaTeXの普通の(TeX言語で実装する)パッケージの冒頭には\ProvidesPackageという命令を書くのですが、パッケージをexpl3で実装したい場合は代わりに\ProvidesExplPackage命令を書きます。

% 引数は順に, パッケージ名, 更新日付, パージョン, 説明文
\ProvidesExplPackage {hwata} {2025-12-20} {0.2.0} {Hello World Atatatata}

expl3はLaTeXやTeX言語とは異なる字句解析規則をもちますが、この命令を書くことで以降のソースコードがexpl3の規則で字句解析されます。

変数

  • expl3には一般的な意味(可視スコープ)での「ローカル変数」がなく、変数は全てグローバルに定義されます。かなり不便なのですが、「TeX言語の上で動作させる」という制約のためこの仕様が採用されています。
    • このためcharに相当する変数(\l_hwata_char_str)もトップレベルで定義されています。
  • 変数名は\‹種別›_‹モジュール›_‹説明›_‹データ型›という命名規則に従います。
    • 例えば\l_hwata_hako_strは「ローカル(l)でhwataモジュールに属して文字列(str)であるhako」を意味します。
    • さっき「変数は全てグローバル」といいましたが、expl3の変数にも“普通と異なる意味”でローカルとグローバルの区別2があります。
    • 「モジュール」は単に名前空間のことです。今回はhwataパッケージが独自に定義する変数をhwataモジュールに含めました。

関数

  • expl3では全ての動作を関数呼出として扱います。
    • 多くの言語で特別な構文を用いる「変数の宣言」「変数への代入」「実行制御」なども関数呼出として記述します。
  • 関数名は\‹モジュール›_‹説明›:‹引数指定›という命名規則に従います。
    • 例えば「\bool_set_true:N」は「boolモジュールで引数指定がNであるset_true」を意味します。boolはexpl3のカーネルが提供するモジュールです。
  • 引数指定は「その関数がどのような引数をとるか」についての情報を表します。引数指定だけが異なる関数は共存できるため一種のオーバロードの役割も果たしています3。1つの引数に対して1つの文字が対応し、各文字は以下のような意味を持ちます。
    • Nまたはn: 通常の(特別な意味のない)引数4
    • T: 条件関数における真分岐の処理。
    • F: 条件関数における偽分岐の処理。TFは条件関数のオーバロードを行うために特別扱いになっています。詳細は「条件分岐」の節を参照してください。
    • e: 関数適用前に展開される引数。例えば\str_put_right:Neは第2引数を展開したものに置き換えてから\str_put_right:Nnを実行します。「展開」とは何かについては省略しますが、例えば文字列変数を「展開」するとその変数の現在の値の文字列5になります。

LaTeX命令の定義

\NewDocumentCommandは新しいLaTeX命令(マクロ)を定義するLaTeX命令6です。

% 引数無しの命令を定義する
\NewDocumentCommand \HelloWorldAtatatata {}
  {
    処理
  }
  • expl3のコードでLaTeX命令(公開されるもの)を定義する場合にも\NewDocumentCommand7を用います。
  • ただし定義本体はexpl3のコードで記述します。

ちなみに、LaTeXの命令ではなくexpl3の関数として定義するには\cs_new:Nn関数を用います。例えば、hwataモジュールの関数\hwata_main:を定義するには次のように書きます。

\cs_new:Nn \hwata_main:
  {
    処理
  }

変数の宣言

変数を使用する前に必ず宣言する必要があります。

\str_new:N \l_hwata_hako_str       % 文字列型
\bool_new:N \l_hwata_running_bool  % 真偽値型
\int_new:N \l_hwata_x_int          % 整数型
  • 変数の宣言は\‹データ型›_new:Nという関数8で行います。
  • 変数の初期値は型ごとに固定(strは空文字列、intは0、等)されています。

変数への代入

変数への代入も関数呼出で行います。

\str_set:Nn \l_hwata_char_str {} % 文字列"あ"を代入
\int_set:Nn \l_hwata_x_int { \int_rand:nn { 0 } { 1 } } % 第2引数の式の値を代入
\str_clear:N \l_hwata_hako_str % 空文字列を代入
\bool_set_true:N \l_hwata_running_bool % 真値を代入
  • 変数への代入は\‹データ型›_set:Nnという関数で行います。
  • \bool_set_true:N\str_clear:Nのように専用の命令が用意されている場合もあります。

繰り返し

他の言語のwhile文に相当するのは\bool_while_do:Nn関数です。

% \l_hwata_running_bool が真である間ループ
\bool_while_do:Nn \l_hwata_running_bool
  {
    ループ処理
  }

ちなみに、do~while文に相当する\bool_do_while:Nnもあります。

条件判定

expl3での条件判定は少し特徴的です。普通の言語では「真偽値の値を受け取って条件分岐する」という機能をもつ単一のif文が存在して、それと「何らかの判定をして真偽値を返すような演算子や関数」を組み合わせて用います。しかしexpl3では「何らかの判定を行う関数」がif文の機能を兼ねる方式を基本とします。普通の言語と同様の方式もサポートされています9が、こちらは必要な場合(例えば、複数の条件のANDやORをとる)にのみ用いられます。

% "\l_hwata_x_int が0に等しいか"の条件で分岐する
\int_compare:nNnTF { \l_hwata_x_int } = { 0 }
  { 真の場合の処理 }
  { 偽の場合の処理 }
% 同様に"2つの文字列が等しいか"の条件で分岐する
\str_compare:eNeT { (文字列を返す式) } = { あたたたた }
  { 真の場合の処理 }
  • \int_compare:nNnTFは「2つの整数値を比較してその結果に応じて真か偽かの何れかの分岐を実行する」という内容の関数です。
    • 関数の直後に{ \l_hwata_x_int } = { 0 }という条件式がありますが、これは3つの引数として扱います。(引数指定のnNnに対応)
    • 残りの引数指定がTFなので、その後に「真の場合の処理」と「偽の場合の処理」の引数があります。
  • 同様に\str_compare:eNeTは文字列を比較して分岐します。
    • 引数指定の後半がTなので分岐のうち「真の場合の処理」のみが存在します。
  • どの条件関数もTFTFの3つの変種が用意されていて、真と偽の各々の場合に処理があるかどうかに応じて使い分けます。

ちなみに、else-if文に相当する構造はなく、単純に条件関数をネストさせます。ただしKotlinのwhen10に相当する\bool_case:~関数も用意されています。

文字の版面への出力

% \par は改段落
お前はもう死んでいる \par

% 変数の内容を出力する
\l_hwata_char_str

文字列を直接実行すると、その文字列11が版面に出力されます。これは組版言語であるTeXの特徴を継承した仕様といえます。他にも「結果が文字列型になるような式」(例えば文字列変数の\l_hwata_char_str)を実行した場合も文字列が版面に出力されます。

改段落12\par)や強制改行(\\)についてはLaTeXの命令を利用します。

ちなみに、版面ではなく端末に出力したい場合は通常通りに関数(\iow_term:n)を利用します。

文字列の連結

\str_put_right:Nnで文字列を連結できます。これは+=に相当する関数です。

% \l_hwata_hako_str の値を, 右側に \l_hwata_char_str の内容を
% 連結した文字列に置き換える
\str_put_right:Ne \l_hwata_hako_str { \l_hwata_char_str }

ちなみに、hako = hako + charに相当する文は次のように書けます。

\str_set:Ne \l_hwata_hako_str { \l_hwata_hako_str \l_hwata_char_str }

複数の文字列を単に並べるとそれは連結したものと見なされます。上の文の場合、e指定の引数に文字列変数を2つ書いたので各々がその内容に置き換えられて、それが並んでいるため連結されます。

文字列から最後の5文字を取り出す

\str_range:Nnn関数で文字列の一部分を取り出せます。

% \l_hwata_hako_str の最後の5文字
\str_range:Nnn \l_hwata_hako_str { -5 } { -1 }

第2・第3引数はそれぞれ開始位置と終了位置です(先頭は1)。負数は後ろから数えた位置を表します(末尾は−1)。

乱数生成

\int_rand:nn関数で整数の乱数が得られます。

% 0以上1以下の整数乱数
\int_rand:nn { 0 } { 1 }

第1・第2引数はそれぞれ下限と上限です。

expl3の概要と歴史

expl3の特徴

  • TeX処理系上で動作するインタプリタ言語
  • expl3のインタプリタ自体はTeX言語で実装されている
  • 生のTeX言語に比べると簡単
  • 異なるTeXエンジン間の機能の違いをある程度吸収できる
  • LaTeXカーネルの新しい機能や一部のLaTeXパッケージの実装で使われている

ちなみに、“expl”は /ˈɛkspəl/(日本語の慣習に従った読み方は「エクスプル」)と読みます。

expl3の歴史

  • 開発者:The LaTeX Project(現在使われているLaTeX2eの開発者でもある)13
  • 誕生:1997年14
  • 名前の由来:元々はExperimental LaTeX3の略で「LaTeX3の機能を現在のLaTeX(2e)で使うための実験的なパッケージ」という位置づけであった。現在ではもうexperimentalではないので「LaTeX3 Programming Language」の略となっているらしい(参照

個人的なコメント

TeX言語の世界では時々「TeX言語で○○言語(BASICとかBrainf**kとか)の処理系を実装してみた」というネタが流行ったりしますが、expl3は「LaTeX3カーネルの実装を容易にする」という実用的な目的で開発されたものです。1990年頃にThe LaTeX Projectは当時のLaTeX 2.09に代わる新しい次世代のLaTeXカーネルである「LaTeX3」を開発しようとしていて、その実装を(異様な文法の)TeX言語で行うのは困難だと判断して「LaTeX3を実装するための言語」としてexpl3を開発したのでした15

expl3は実用目的の言語なので、TeX言語を生で使う場合に対するオーバヘッドが過大になるのは望ましくありません。言語としての利便性と速度のトレードオフを考慮する必要があり、実際にexpl3の機能のセットにその苦心の跡を感じられます。

expl3はLaTeX上の開発で使う言語なので一般のユーザ(文書作成者)が使うものではありません。TeX言語と違ってあまり「LaTeXの話の中に露出する」こともほとんどないので、LaTeXユーザの大多数はその存在を知らないでしょう。もちろん、fontspecやunicode-math等のパッケージはexpl3実装であり、また現在のLaTeXカーネルの一部はexpl3で実装されているので、今時のLaTeXユーザはみんなexpl3の恩恵を受けています。

ちなみに、自作のパッケージでCTANに登録しているものの中16にはexpl3で(主要部分を)実装したものはまだありません(ざんねん:upside_down:

  1. hwataパッケージがサポートするエンジンはLuaLaTeX・XeLaTeX・(u)pLaTeXです。日本語文字を扱うのでpdfLaTeXでは使えません。

  2. expl3でいうローカル変数はPerl 4のローカル変数(localで宣言する変数)と似たような挙動をします。今回のコードではグルーピング(実行時のローカルスコープ)を利用しないので変数はローカルでもグローバルでも動作は変わりません。

  3. 例えば、int_randには「2引数の\int_rand:nn」と「1引数の\int_rand:n」の2種類の変種があります。

  4. Nは「単一要素からなる引数」、nは「複数要素からなる引数({~}で囲って書く)」を表し、これらの違いはオーバロードのために用いられます。例えば、\str_range:Nnnは第1引数に文字列変数をとりますが、この関数には\str_range:nnnという変種があり、こちらは第1引数に文字列即値をとります。

  5. 文字列用のn引数に変数名を書くと、その変数名の文字列と解釈されてしまうので、文字列変数を扱うときは多くの場合N引数かe引数が用いられます。

  6. この\NewDocumentCommandはexpl3の文法ではなくLaTeXの文法に属します。

  7. \NewDocumentCommand以外の命令も使用可能で、例えば旧来の\newcommandも可能です。ただしexpl3が関わる場合は普通は新方式の定義用命令が利用されます。

  8. 関数の命名規則を鑑みると、この‹データ型›の部分は実際にはモジュール名です。つまり、expl3カーネルには各データ型(strint、等)に対してそれと同名のモジュールが存在します。この後の説明でも\bool_while_do:Nn\int_compare:nNnTF等の「データ型と同名のモジュール」の関数が登場します。

  9. 「普通の言語の方式」のサポートには少し制限があって、例えば「判定を行う関数」のなかには「兼ねる方式」のみサポートするものもあります。expl3での条件判定が特殊であるのは、そもそもTeX言語の条件判定が特殊であることに起因しています。

  10. またはGoのswitchやRubyのcaseで引数をもたない版。

  11. ただしexpl3の字句解析規則では空白文字は無視されるので、ASCII空白文字を書いても欧文空白は入りません。代わりに~を書くと(LaTeXの非分割空白ではなく通常の)欧文空白が挿入されます。

  12. LaTeXでは「空行を置く」ことでも改段落ができますが、expl3では字句解析規則が異なるためこの方法は使えず、\parを明示的に書く必要があります。

  13. 1997年の論文の著者はDavid Carlisle、Chris Rowley、Frank Mittelbachであり、この3名は今でもThe LaTeX Projectの中心的なメンバーです。

  14. TUGboat誌の論文“The LaTeX3 Programming Language — a proposed system for TeX macro programming”が出版された年で、この論文で初めてexpl3が公表されました。expl3の原型との開発は1990年頃から始められていたようで、これはLaTeX2e(1994年)よりも前のことです。

  15. ちなみに肝心のLaTeX3カーネルはどうなったのかというと、少なくとも当初考えていた「LaTeX2eとは(パッケージ実装レベルで)非互換な全く新しい形のLaTeX3」については「LaTeX2eの資産を捨てるのが非現実的」という理由で結局中止になり、「LaTeX2eに対して現代化改修を施す」ことになりました。今のLaTeX2eのカーネルは10年前とは様々な点で変わっているので、個人的には今あるLaTeXを「LaTeX3」と呼んでもかまわない気がしますね……:neutral_face:

  16. 「ネット上に公開しているもの」としてはbxkvcmdパッケージがあります。自分がLaTeX文書を作る際に何らかの機能を実装する必要が生じた場合は原則的にTeX言語ではなくexpl3を利用しています。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?