マクロツイーター

はてダから移行した記事の表示が崩れてますが、そのうちに直せればいいのに(えっ)

TeX でデータ構造する場合のヤヤコシイ話



背景

  • 「効率のよいデータ構造・アルゴリズム」を TeX 言語(や expl3 言語)で実装する機運が高まっている[1]。
  • 複雑なデータ構造・アルゴリズムを実装する目的は優れた効率を得るためであり、それゆえ、「形だけ真似たが実装に問題があって本来の効率が得られていない」ようでは、そのデータ構造・アルゴリズムを実装する(少なくとも実用上の)意味はない。「ほぼ常に Θ(n2) 時間かかるクイックソート」の例が有名だろう。
  • 従って「効率のよいデータ構造・アルゴリズムTeX 言語で実装する話」をするためには、少なくとも「理論的な計算量を測る」手段があることが前提条件となる。(※以降では、漸近的な時間計算量に話を限る。)
  • TeX 言語処理系の処理の様態は、通常の RAM 機械と比べると、自明に等価であるとはいえない。
  • すなわち、TeX 言語における「計算のステップ数」の定義を決めたい。

組版とは無関係な計算に限定する。(データ構造の話なので。)

2 つのモデル

組版処理を考えない場合は、TeX 処理系の動作は「トークンを実行すること」(展開可能トークンの場合は展開を実行と見なす)の繰り返しと見なすことができる。ここに着目した最も単純なモデルとして次のようなものを考える。

[モデル①]
トークンの種類に関わらず、1 トークンの実行に 1 ステップかかる。

このモデルに従うと、例えば次のようなマクロ \S が定義されていた場合:

\def\S#1\E{}

以下の 2 つのプログラム(もちろん実質何もしない)の実行はどちらも 1 ステップとなる。

\S A\E
\S AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\E

これは不合理な気がする。TeX 処理系は \S を展開する際に、少なくとも全ての A を走査する必要がある。このような「トークンの読書き」にかかる時間を考慮したのが次のモデルである。

[モデル②]
トークンの実行で m 個のトークンが読み込まれて n 個のトークンが書き出される場合、そのトークンの実行は m + n ステップかかる。

一般の TeX 処理系で漸近的な時間計算量を考える場合、①と②のどちらが妥当であろうか。

トークンの読書き」の時間は無視できるかできないか

TeX プログラミングでデータ構造を表す場合に、「そのサイズに比例する長さのトークン列」が使われることはよくある。次に示す「\doリスト」はその一例である。

\do{2603}\do{26C4}\do{26C7}\do{1F363}\do{1F986}

従って、①と②のどちらを採用するかで、データ構造の実装の時間量に関する優劣の判断が異なってくる可能性は大きい。

もちろん、厳密に漸近的時間量をみるのであれば、「トークンの読書き」にかかる時間はゼロではないのだから、構造のサイズが増加していくと、最終的には「実行」よりも「読書き」の方が有意になり、従って②が妥当ということになるだろう。しかし現実的に考えると、TeX 処理系の実装上の空間量制限のもとでは、せいぜい数千のオーダのサイズのデータしか扱えそうにない。だから、「現実的な範囲」においては「読書き」の時間は無視できる、という可能性も捨てきれない。

チョット実験してみる

というわけで、調べてみた。

  • 実験用文書“Len-n”(n=1,10,100,1000 を用意した)

    \documentclass{article}
    \def\S#1\E{}
    \def\A{\S AAA……AAA\E}%←'A'をn個並べる
    \begin{document}
    \A\par %100万回繰り返す
    \A\par
    \A\par
    ……
    \end{document}
    
  • 比較対照用文書“Base”

    \documentclass{article}
    \def\S#1\E{}
    \def\A{}
    \begin{document}
    \par %100万回繰り返す
    \par
    \par
    ……
    \end{document}
    
  • つまり、“Len-n”は“Base”と比べるとマクロ展開 200 万回分の処理が余計に必要になる。“Len-1”と“Len-1000”はトークン実行の回数は等しいが、“Len-1000”の方が「トークンの読書き」に時間がかかる。
  • 従って、「“Base”対“Len-1”」の時間差と「“Len-1”対“Len-1000”」の時間差を比べれば、「現実的にトークンの読書きの時間が無視できるか」が判断できるはずである。
  • TeX 処理系として TeX Live 2018 最新の latex コマンドを使った。
  • 5 回のウォームアップの後、100 回のコンパイル時間を計測した。
結果
文書所要時間平均(秒)標準偏差(秒)
Base2.2850.016
Len-12.4900.027
Len-102.6400.019
Len-1004.1110.025
Len-100018.8370.072
  • Base と Len-1 の差: 0.205 秒
  • Len-1 と Len-1000 の差: 16.347 秒

まとめ

参考文献

[1] ツイッタァー、オレ的タイムライン

(続く)

画期的な関数型組版言語を作ってみた(※ただし画期的)

夏! 酷暑! ゆきだるま☃︎

チョット SATySFi を語ってみる

さて、ナントカ☃︎の日恒例の「本質的な問題を画期的なナニカで解決する」ネタですが、今年は関数型組版言語について考えてみました。

SATySFi のここがスゴイ!

関数型組版言語といえばやっぱり、SATySFi ですね。SATySFi がリリースされてから早くも半年が過ぎました。この間も、開発は着々と進められています。

SATySFi の開発目標は「TeXLaTeX の“よりよい代替”となること」です。このため、TeXLaTeX と同じマークアップ言語処理系を採用しつつ、TeXLaTeX の“アレな点”を克服するための言語設計を採用しています。

  • TeX 言語は極めて“動的な”言語であるため、コードに不備があった場合に支離滅裂な動作が起こり、結果として意味不明なエラーが発生しやすい。LaTeX も結局 TeX 言語上のマクロに過ぎないため同様である。
    →SATySFi は強い表現力をもつ静的型付けを採用している。これにより、“明らかに意味を持たない”間違った記述が、コンパイルの早い段階に、的確な型エラーとして報告される。
  • TeX 言語は全ての実行制御を「トークン列上のマクロ展開」という形で行う。この方式はそもそも通常規模のプログラミングには向いておらず、何よりも世間で広く用いられている言語と大きく異なる思考を必要とする。このことが TeX 言語の習得(つまり LaTeX 上の機能開発)を著しく困難なものにしている。
    →SATySFi はマークアップとプログラミングの文法を明確に分離し、後者ではごく普通の外見の言語(ML 風の関数型言語)を採用している。

まとめると、SATySFi は「非アレな組版言語処理系」を目指しているわけです。

SATySFi のここがダメ!

このようにスバラシイ SATySFi ですが、私の個人的な見解としては、決定的な“弱点”を抱えてると考えています。その弱点とは

型システムが複雑すぎて、一般のヒト類にとって理解困難である

ということです。

もちろんこの“弱点”は「一般的にヒト類は複雑な型システムの理解が不得手である[要出展]」という性質に起因するものであるため、SATySFi 自体の問題ではありません。しかし、逆にいうと、原因が我々ヒト類のもつ生来的性質にあるからこそ、その改善の可能性が著しく限られてしまう、というのもまた確かです。

結局、理論上は的確な型エラーが提示されたとしても、ヒト類がそれが理解できないという事態が必然的に多発することになるでしょう。これは SATySFi の目標から考えて重大な問題であることは間違いありません。

型システムがアレになる、本質的理由

なぜ SATySFi の型システムは複雑なのでしょうか。実はこれは SATySFi が組版用途の言語であることに根源的な理由があります。

つまり、我々の表す文書が複雑な構造をもつがゆえに、SATySFi は複雑な型システムを持たざるを得ないわけです。すなわち、ここで述べた問題は、ある意味で「本質的に解決困難」といってよいでしょう。

本質的な問題、画期的な解決

では、このような「本質的に解決困難」はどう解決すればよいでしょうか。本ブログの読者なら、既にお判りのことだと思います。そう、

出力をゆきだるま☃︎に変えればよい

のです。型システムの複雑さの根源は文書構造の複雑さにあるのでした。従って、文書構造の複雑性を排除して、その内容を☃︎に統一することにすれば、型システムの単純化が実現できるのです。

型システムを極力単純にすることには大きな利点があります。それによって、静的型エラーが起こる可能性を排除することも可能となるためです。つまり、字句解析を通ったプログラムは常に型付け可能になるわけです。さらに、型付けが健全であれば、動的な型エラーが起こることもありません。結果的に、ユーザは言語の型システムについての知識を何も持たなくても済むことになります。すなわち、ヒト類に変更を課する必要性から解放されるわけです。

画期的な静的組版言語「scSATySFi」

そういうわけで、早速作ってみました。

Windows(64bit)の場合は、リリースのページにビルド済のバイナリ(scsatysfi.exe)が置いてあるので、それをダウンロードして適当な(実行パスの通った)ディレクトリに配置すればOKです。他の環境の場合は、README にある通り、Go をインストールして「go get」してください。

とにかく scSATySFi を使ってみる

インストールができたら、早速使ってみましょう。せっかくのナントカの日なので、☃︎を出力してみます。scSATySFi の文法は非常に単純で、☃︎を出力するには、単に☃︎(U+2603)を書くだけでOKです。LaTeX や SATySFi にあるような「文書構造を示すための記述」は一切ありません。

[essential.scty]
☃︎

では早速この文書ファイルをコンパイルしましょう。以下のコマンドを実行します。

scsatysfi essential.scty

以下のような端末出力とともに、PDF ファイル essential.pdf が出力されました。

# scsatysfi essential.scty
 ---- ---- ---- ----
  target file: 'essential.pdf'
  dump file: 'essential.scsatysfi-aux' (won't be created)
  parsing 'essential.scty' ...
 ---- ---- ---- ----
  reading 'essential.scty' ...
  type check passed. (essential)
 ---- ---- ---- ----
  evaluating texts ...
  evaluation done.
 ---- ---- ---- ----
  writing pages ...
 ---- ---- ---- ----
  output written on 'essential.pdf'.

essential.pdf の中身を確認してみましょう。

完璧ですね!

チョット注意
  • scSATYsFi は静的型付き言語ですが、完全な型推論機能をもっているため、ユーザが型を明示的に書く必要は全くありません。実際、scSATySFi には型を明示する記法はありません。
  • もしかしたら(キーボードに「☃︎」のキーがないなどの不幸な理由で)文字「☃︎」の入力が難しい場合もあるでしょう。そのため、scSATySFi では「☃︎」を「8」で代用できるようになっています。(なお「⛄︎」(U+26C4)「⛇」(U+26C7)でも「☃︎」と等価になります。)それ以外の文字は scSATySFi の文法では現れないため、「☃︎」「⛄︎」「⛇」「8」および空白類文字(空白・水平タブ・改行)以外の文字が含まれると文法エラーとなります。((例外として、コメント開始文字である「🦆」が現れた場合、その行の残りの文字は無視されます。また「🦆」は「2」で代用できます。))
  • ☃︎がもっともっと入った文書を作ろうとして次のように書いたとします。
    ☃︎ ☃︎ ☃︎ ☃︎ ☃︎
    
    この文書ファイルはコンパイルは通りますが、残念ながら出力される☃︎は常に一つだけになります。複数の☃︎を扱おうとすると、型システムが複雑になってヒト類の手に負えなくなってしまうからです。一つの文書で済ませるなんて横着をせずに、☃︎の文書をたくさん作りましょう。その方が素敵でしょう。
    ※実際には、「☃︎ ☃︎ ☃︎ ☃︎ ☃︎」は scSATySFi の式であり、それを評価した結果の値が☃︎になります。なので、出力は☃︎一つになるわけです。

scSATySFi のチョット理論的な話

既に述べた通り、scSATySFi ではユーザが型システムを理解する必要は全くないのですが、興味のある人のために、scSATySFi の型システムについて簡単に説明しておきます。

構文・型

型が 1 つしかないという、極限まで単純化した型システムになっています。

簡約規則

この簡約規則について、以下の性質が成立します。*1

  • すべての項が強正規化可能である。
  • Church-Rosser 性(合流性)をもつ。
型付け規則
     

この型付け規則について、以下の性質が成立します。

  • 全ての項は型付け可能である。つまり、静的型エラーが発生しない。
  • 任意の項について、型情報が全く与えられない状態から当該の項の型を推論できる。またその推論を効率的に(線形時間で)行うためのアルゴリズムが存在する。

この 2 つの性質があるため、ユーザはコード作成時・コンパイル時の何れにおいても、言語の型システムについて一切気にする必要がありません。

具象構文について

実は、適用演算については、結合則が成立します。*2

(☃︎ ☃︎) ☃︎ = ☃︎ (☃︎ ☃︎)

従って、一般に☃︎が複数ある式においては、式の値は括弧の位置によらず一定になります。例えば、次の 3 つの式はどれも同じ値をもちます。

(​(​(☃︎ ☃︎) ☃︎) ☃︎) ☃︎
(☃︎ ☃︎) (​(☃︎ ☃︎) ☃︎)
☃︎ (​(☃︎ (☃︎ ☃︎)) ☃︎)

このため、scSATySFi の具象構文においては、「演算の優先順位を表す括弧を常に省略する」という規則を採用しています。これにより、ユーザは☃︎以外の記号を一切書かなくて済みます。

☃︎ ☃︎ ☃︎ ☃︎ ☃︎

scSATySFi のチョット素敵な使い方

  • 文法を極力簡単にすることを優先させたため、出力の☃︎をカスタマイズする機能は敢えて省いています。ただし例外として、☃︎のマフラーの色に限っては、コマンド起動時のオプション“--muffler”で指定できます。
    scsatysfi --muffler="green!50!black" essential.scty
    ここで“--muffler”に指定する値は、LaTeX の xcolor パッケージの「色式」に従います。

まとめ

というわけで、「SATySFi の設計はスバラシイけど、実際に自分が使うのは難しそう……」と思っている人は、ぜひとも画期的な scSATySFi を試してみてください。scSATySFi で ☃︎な文書をどんどん作って、酷暑の夏を乗り切りましょう!

*1:煩雑さを避けるため、この記事では証明は一切省略します。

*2:括弧 ( ) は抽象構文における演算子の優先順位を示す補助記号です。

TeX 言語で goto してみる的なナニカ

Gotoh アルゴリズムが既に使用済、ということは、どうやらマジメに goto を考えるしかない*1ようである。

作ってみた

もちろん出力はごくフツーの FizzBuzz になる。

まとめ

こんなことをしている場合じゃない。

*1:素敵な「goto のロゴ」を出力する、という案も考えたが、あまりにもアレである。

そういえば ZXjafont が新しくなった(v0.4)

もう随分昔のこと(2018 年 5 月)だけど。

本記事では、この版の新機能について解説する。

ZXjafont って何

簡単にいうと、PXchfon とか luatexja-preset の機能にあるような「和文フォントのプリセット設定」を XeLaTeX + zxjatype の環境で行うためのもの。*1

% XeLaTeX文書; UTF-8
\documentclass{article}
\usepackage{zxjatype}% 欧文文書で部分的に日本語を使う
\usepackage[ms]{zxjafont}% 和文をMSフォントに
\begin{document} % 欧文は変わらない(Latin Modern)
{\TeX} is ultra-アレ.
\sffamily {\LaTeX} is アレ-escent.
\end{document}

実は、XeLaTeX 上で BXJS クラスの jafont オプションで和文フォントの種類を設定する場合には zxjafont パッケージが使われる。なので、zxjafont を直接利用する人は少ないとしても、間接的に利用した人はそれなりにいるだろう。

% XeLaTeX文書; UTF-8
\documentclass[xelatex,ja=standard,a4paper,
  jafont=ms]{bxjsarticle}% 和文をMSフォントに
\begin{document} % 欧文は変わらない(Latin Modern)
{\TeX}は激アレ。
\sffamily {\LaTeX}は微アレ。
\end{document}

プリセットの種類が増えた

luatexja-preset や pxchfon に追随するため、以下のプリセットを追加した。

  • ume:梅フォント。
  • yu-win10:游書体(Windows 10 搭載版)。
  • sourcehan: Source Han Serif(源ノ明朝)+ Source Han Sans(源ノ角ゴシック)、非サブセット版。
  • sourcehan-jp: Source Han Serif(源ノ明朝)+ Source Han Sans(源ノ角ゴシック)、地域別サブセット版。
  • noto: Noto Serif CJK JP + Noto Sans CJK JP(非サブセット版)。
  • noto-jp: Noto Serif JP + Noto Sans JP(地域別サブセット版)。

すなわち、BXJS クラスの jafont=yu-win10 などの設定が、XeLaTeX でも通るようになる。

% XeLaTeX文書; UTF-8
\documentclass[xelatex,ja=standard,a4paper,
  jafont=yu-win10]{bxjsarticle}
\begin{document}
{\TeX}\textbf{激アレ}% XeLaTeXで☃する場合は \jachar が必要
\sffamily \jachar{}\textbf{非アレ}\end{document}
源ノ系フォントに関する注意

基本的に、zxjafont はフォントを fontspec で指定する際にフォント名での指定<(ファイル名ではなく)を行う。従って、源ノ系フォントを利用する場合、フォント名での呼出に対応できる方法でフォントファイルを配置する必要がある。大概の TeX システムでは、OS にフォントをインストールすれば大丈夫である。$TEXMFLOCAL に配置したいという場合、Windows なら以下の記事も参考になるだろう。

Source Han Serif/Sans(非サブセット版)には 3 種類のファイル形式(Super-OTC 版、OTC 版、言語別 OTF 版)があるが、これらは同一のフォント名をもつので、全て同じプリセット sourcehan で指定することになる。地域別サブセット版だけは異なるフォント名(Source Han Serif/Sans JP)をもつため、別のプリセット sourcehan-jp を指定する。Noto フォントについても同様で、非サブセット(Noto 〜 CJK JP)の 3 つは noto、地域別サブセット(Noto 〜 JP)は noto-jp を指定する。

% XeLaTeX文書; UTF-8
\documentclass[xelatex,ja=standard,a4paper,
  jafont=sourcehan% Source Han Serif/Sans (JPじゃない奴)
% jafont=sourcehan-jp% Source Han Serif/Sans JP
% jafont=noto% Noto Serif/Sans CJK JP
% jafont=noto-jp% Noto Serif/Sans JP (CJKじゃない奴)
]{bxjsarticle}
\begin{document}
{\TeX}\textbf{激アレ}\sffamily \jachar{}\textbf{非アレ}\end{document}

2004JIS するか 90JIS するか

luatexja-preset に追随して字形選択のオプション*2を新設した。

  • jis2004: 2004JIS 字形を選択する。
  • 90jis: 90JIS(2000JIS)字形を選択する。
  • どちらも指定しない場合は、フォントの既定の字形が使われる。

BXJS クラスでの使用例を示す。
※“japaram={font={...}}”を指定すると、zxjafont に ... のオプションが渡される。この指定方法は LuaLaTeX でも全く同じ((LuaLaTeX では ... の部分のオプションが luatexja-preset に渡される。))であるため、エンジンオプションさえ lualatex に変えれば以下の例は LuaLaTeX でも通用する。

% XeLaTeX文書; UTF-8
\documentclass[xelatex,ja=standard,a4paper,
  jafont=sourcehan,japaram={font={jis2004}}]{bxjsarticle}
\begin{document}
巷で\textsf{}\textbf{葛飾区}\end{document}
% XeLaTeX文書; UTF-8
\documentclass[xelatex,ja=standard,a4paper,
  jafont=sourcehan,japaram={font={90jis}}]{bxjsarticle}
\begin{document}
巷で\textsf{}\textbf{葛飾区}\end{document}

その他諸々

  • 非推奨の古いプリセット指定(ipa-dx 等)に対して警告が出るようになった。

*1:ただし、ZXjafont は zxjatype を読み込まない(つまり“和文非対応”の)状態にも対応していて、この場合は通常の総称ファミリを設定対象とする。

*2:オプションの命名が不統一であるが、これも luatexja-preset に合わせたため。

チョット arara してみた件

arara とは

簡単に言うと、arara とは「LaTeX 用ビルドツール」の一種であり、つまり、latexmk と同類のツールである。

arara の一番の特徴は「やりたいことを明示する」という点である。latexmk がコンパイルの手順を暗黙的に決めている点が多いのに対し、arara では手順を書いたファイルを事前に用意する必要がある。(ただし、pdflatex や biber などの有名なツールに対する手順ファイルはソフトウェア配布に含まれている。)

また、arara では文書ファイルにどの手順を適用すべきかも明示する必要がある。このために、文書ファイルの先頭にコメントの形で指示を記述する。例えば、下記のコメント行をファイルの先頭に書くと、そのファイルには pdflatex 用の手順が適用されることになる。

% arara: pdflatex

さっそく arara で文書をコンパイルしてみた

準備

TeX Live 2018 には arara が含まれているので、これを利用することにする。

arara は Java で実装されているので、Java 処理系(JRE)のインストールが必要である。

今回は、カスタムの設定を利用するので、そのための前設定を行う。以下、ホームディレクトリ(Windows では %UserProfile%)を ~ で表す。

  • カスタム設定用のディレクト~/.arara/rules を作成する。
  • カスタム設定を有効にするために、以下の内容の設定ファイル(YAML 形式)を ~/araraconfig.yaml に配置する。((“@{userhome}”がホームディレクトリを表す。YAML 形式の文法的制約のため、@{...} を含む値を記述する場合には、特に意味の無いタグ <arara> を前置する必要があるらしい。))

    [araraconfig.yaml]
    !config
    paths:
    - <arara> @{userhome}/.arara/rules
    filetypes:
    - extension: saty
      pattern: "^\\s*%\\s+"
    
カスタムの規則を作成する

先ほど説明したような“コンパイルの手順”のことを、arara では「規則」(rule)と呼ぶ。規則は YAML 形式のファイルを用いて定義される。

ここでは以下のような規則を作成した。

[satysfi.yaml]
!config
identifier: satysfi
name: SATySFi
command: <arara> satysfi @{fullpath} "@{file}"
arguments:
- identifier: fullpath
  flag: <arara> @{isTrue(parameters.fullpath,"--full-path")}

このファイルを ~/.arara/rules/satysfi.yaml に配置した。

文書に“指令”を追加する

arara で文書をコンパイルする場合、文書の先頭に所望の動作をコメントの形で書き込む。このコメント行のことを「指令」(directive)という。最も単純な形では指令は以下のようになる。

% arara: <規則名>

ここでは次のような文書ファイルを用意した。

[araratest.saty]
% arara: satysfi
@require: stdja

StdJa.document (|
  title = {とにかくararaしてみる};
  author = {某ZR};
  show-title = true;
  show-toc = false;
|) '<
  +pn {※ただし\TeX;以外}
>
文書を arara する

それでは、arara を起動して先ほどの文書 araratest.saty をコンパイルしてみよう。以下のコマンドを実行する。

arara araratest

実行結果は……

  __ _ _ __ __ _ _ __ __ _
 / _` | '__/ _` | '__/ _` |
| (_| | | | (_| | | | (_| |
 \__,_|_|  \__,_|_|  \__,_|

Running SATySFi... SUCCESS

……どうやら無事に arara できたようである。


ZR「いやだって、arara で TeX 文書をコンパイルしてもネタにならないじゃん」
*「そもそも SATySFi を出せば何でもネタになるという発想がアレ」
ZR「………………」

LuaLaTeX で和文しない速さを調べてみた

風評が流れるのは憂慮すべきことである。

そういうわけで、欧文の LuaLaTeX のコンパイル速度について、簡単に調べてみることにする。

実験してみた

実験方法
  • テスト用文書として「世界人権宣言(UDHR)の英語の全文を 50 回記したもの」(udhrmain.tex)を使う。レターサイズで 313 ページ。
  • Windows 10 + TeX Live 2018 の環境において、pdfLaTeX と LuaLaTeXコンパイル所要時間を比較する。
  • 3 回予備で実行した後、9 回実行して所要時間を計測、その中間にある 5 回分の平均値を求めた。
実験結果
  • pdfLaTeX: 1.497 秒
  • LuaLateX: 5.560 秒

LuaLaTeX の方が 3.71 倍遅い

※生データは result.txt の通り。

まとめ

というわけで、「LuaTeX(LuaTeX-ja ではない)のタイプセットは遅い」という風評が打破されて、代わりに「LuaTeX(LuaTeX-ja ではない)のタイプセットは遅い」という知見が確立された。めでたしめでたし。

TeX Live 2018 で日本語ファイル名するとアレ(1)

TeX Live 2018 において「入力エンコーディングの既定値を UTF-8 にする」という重要な変更が行われた(参考)。その副作用として、「LaTeX 起動時に、非 ASCII 文字を含む文書ファイル名を使うとエラーになる」という不具合が報告されている。

(不具合の例)
C>uplatex TeXはアレ.tex
This is e-upTeX, Version 3.14159265-p3.8.1-u1.23-180226-2.6 (utf8.uptex) (TeX Li
ve 2018/W32TeX) (preloaded format=uplatex)
 restricted \write18 enabled.
entering extended mode
! I can't find file `TeX'.
<to be read again>
                   \protect
<*> TeX
        ヘアレ.tex
(Press Enter to retry, or Control-Z to exit)
Please type another input file name: x
(c:/texlive/2018/texmf-dist/tex/latex/tools/x.tex
pLaTeX2e <2018-05-20u02> (based on LaTeX2e <2018-04-01> patch level 4)

本記事では、この不具合について、てきとーに書き流す。

※ソースファイル上での \input での読込については対象外。

アレは本当にソレのせいなのか

そもそも、(La)TeX における非 ASCII のファイル名の扱いはまだまだアドホックなところが多分に残っている。なので、この問題の対策に関しては、

  • UTF-8 既定化」が原因であるパターン
  • そもそも原理的に失敗するパターン

の見極めが必要である。例えば、WindowsTeX Live)上の XeLaTeX では非 ASCII のファイル名(例えば「xelatex TeXはアレ.tex」)は失敗するが、これは「UTF-8 既定化」とは無関係*1であり、最初から失敗するパターンに該当する。

アレの原因を調べる方法

非 ASCII のファイル名のエラーの原因が「UTF-8 既定化」であるかは以下の何れかの方法で調べられる。

  • TeX Live 2017 以前では同じコマンド起動が成功していたのであれば、「UTF-8 既定化」が原因である。
  • LaTeXlatex/platex/uplatex/pdflatex)の代わりに敢えて plain TeX(etex/eptex/euptex/pdftex)を起動してみる。例えば、「uplatex TeXはアレ.tex」の代わりに「euptex TeXはアレ.tex」を実行する。「UTF-8 既定化」が原因であるならば、plain TeX は指定のファイルを読み込むはず*2である。もちろん、“期待通り”読み込まれた場合は、先頭の「\documentclass」の箇所で「! Undefined control sequence.」のエラーになるだろう。((\documentclassLaTeX 専用の命令である。念のため。))

アレになるパターン

大雑把にいうと、「ファイル名のバイト列を inputenc が“欧文の UTF-8”として解釈しようとしたが、形式不正であった」という場合にエラーになる。

Windows な欧文 (pdf)LaTeX

※日本語版の Windows を仮定する。

pdflatex TeXはアレ.tex

Windows なので、ファイル名は SJIS で与えられる。*3inputenc の機構はこのバイト列を UTF-8 でデコードしようとするが、UTF-8 としては形式不正であるためエラーになる。

Windows な upLaTeX
uplatex TeXはアレ.tex

Windows なので、ファイル名は SJIS で与えられる。upLaTeX は非 ASCII 文字をまず UTF-8和文として解釈してみる((pTeX 系の場合は、まず「非 ASCII 文字を和文として解釈する」という過程が入る。例えば、Windows 上の「platex TeXはアレ」の場合は、まず SJIS和文として解釈してそれが成功するため、“欧文の UTF-8”として解釈される過程には進まないためエラーは発生しないのである。))が失敗し、次に、inputenc が欧文の UTF-8 としてデコードしようとして、形式不正であるためエラーになる。

コマンドラインの文字列については入力漢字コード(-kanji オプション)は適用されず、和文の解釈については、常に内部漢字コード(-kanji-internal オプション)が適用されるようである。TeX Live の upLaTeX の内部漢字コードは UTF-8 である。

Linux/macpLaTeX

※「ロケールUTF-8 である」と仮定する。

platex アレ

# または
platex -kanji=utf8 アレ

※先述の通り、入力漢字コード(-kanji)は無関係。

  • ロケールUTF-8 なので、ファイル名は UTF-8 のバイト列“E3 82 A2 E3 83 AC”で与えられる。
  • Linux/mac 上の TeX Live の pLaTeX の内部漢字コードは EUC である。従って、platex はこれを EUC和文として解釈してみる。
  • その結果、EUC として解釈できる“A2 E3”が和文“≪”と見なされ、残りは欧文のバイトとして扱われる。つまり“^^e3^^82≪^^83^^ac”となる。
  • これを inputenc が欧文の UTF-8 バイト列として解釈しようとする。
  • 元々は正当な UTF-8 のバイト列であったが、途中が和文に置き換わっているため不正であり、エラーになる。((具体的には、“^^e3^^82≪”の 3 トークンを(E3 が先頭の 3 バイトの列と見なして)“1 文字”として読んだ後、UTF-8 の非先頭バイトの^^83に引っかかる。))

ややこしい。

※なお、TeX Live 2017 以前の場合は、「inputenc が欧文 UTF-8 として解釈する」という過程は入らない。この場合、TeX エンジンは“^^e3^^82≪^^83^^ac”という名前のファイルを探索するが、その際に和文の“≪”は再び EUCエンコードされるため、結局「E3 82 A2 E3 83 AC」という元のバイト列に戻る。ロケールUTF-8 であるため、結果的に、OS が探すファイル名の文字列は期待通り「アレ」になる。

(続くはず)

*1:XeLaTeX・LuaLaTeX は当初から UTF-8 専用なので「UTF-8 既定化」の対象にならない。

*2:UTF-8 既定化」は LaTeX の方針なので、plain TeX は無関係である。

*3:正確にいうと、「コマンドラインの文字列を SJIS(CP932)でエンコードしたバイト列が argv に与えられる」ということ。