前回は「Typstで \expandafter する」を実現するために「絵を描く」という方針を採用しましたが、これには「TeX言語🤮的要素が乏しい」という問題点がありました。TeX言語的なネタに仕上げようとするなら、やはり「トークン列の展開制御」にマジメに取り組む必要がありそうです。Typstで「TeXトークン列の展開制御」を行うには一体どうすればいいのでしょうか。
Typstのパッケージで \expandafter しようと試みる
Typstはトッテモスゴイので「Typst上でTeXのコードを実行する」ためのパッケージが既に作られています。Universeで軽く探してみたところ、以下のものが見つかりました。
MiTeXで \expandafter したかった話
「TypstでTeXのコードを実行する」ためのパッケージの中で恐らく一番有力そうなのがMiTeXです。Rustで実装されたWASMプラグインでの処理のため高速に動作するのが特徴です。例えば以下のような機能があります。
mi(«LaTeX記述»)[content]: LaTeXのインライン数式のコードをTypstのインライン数式に変換する。mitex(«LaTeX記述»)[content]: LaTeXのディスプレイ数式のコードをTypstのブロック数式に変換する。
※«LaTeX記述»の引数は「文字列またはrawエレメント1」で与える規則になっていて、これは以降で紹介する他のパッケージについても同様です。\のエスケープが不要なrawエレメントが便利なので以降の例ではこちらを使います。
例えば以下のようにしてTypst文書にLaTeXの数式を書けます。
#import "@preview/mitex:0.2.5": *
Result: // `…`の中にTeXコードを書く
#mi(`c = \sqrt{a^2 + b^2}`)

ちゃんとTeXのコードを扱えているようです。ではさっそく \expandafter を使ったコードを試してみましょう!
#import "@preview/mitex:0.2.5": *
Result:
// \expandafter 的なコードを書いてみる
#mi(```
\def\Duck{d_uc^k}
\def\Twice#1{#1#1}
\expandafter\Twice\Duck
```)
\expandafter\Twice\Duckを一回展開すると\Twice d_uc^kとなり、さらに\Twiceを展開して結果はdd_uc^kとなるはずです。
このソースをコンパイルすると……うまくいきません😢
error: plugin errored with: error: unknown command: \Duck ┌─ @preview/mitex:0.2.5\mitex.typ:18:8
しかも「\Duckが未定義である」という不可解なエラーになっています。少し調べてみた感じでは、MiTeXはTeX言語🤮レベルの処理をサポートしていないようです2。つまり\sqrt3等のLaTeXの命令は全て一種のプリミティブとして“直接実行”される仕組であると思われます。
結局、そもそも「トークンの展開制御」という概念がないため「\expandafter できる」ことは当然全く期待できません(ざんねん🙃)
m-jaxonで \expandafter したかった話
m-jaxonパッケージは「WASMプラグインでJavaScript処理系(QuickJS)を動作させて、その上でMathJaxを動かす」という(やや強引💪な)方法でLaTeXの入力を処理します。同じ作者がJavaScript処理系を動かすためのjogsというパッケージを作製していて、このm-jaxonパッケージはその応用例として作られたようです。
※元々実用目的ではないためメンテナンスされておらず、Typstの0.12.0版以前でしか動作しません4。以下では0.10.0版での動作結果を示します。
m-jaxonパッケージでは以下の機能が使えます。
render(«LaTeX記述»)[content]: LaTeXの数式をMathJaxでレンダリングした組版結果のコンテンツ。
オプション引数:inline[bool;既定=false]: インライン数式であるか(偽ならブロック数式)。
MathJaxの利用者なら知っているかもしれませんが、MathJaxではTeX言語🤮レベルの機能(\defや\over等)がある程度サポートされています。だから次のようなコードが動作します。
// Typst v0.10.0を使用
#import "@preview/m-jaxon:0.1.1"
Result:
// 一旦 \expandafrer を取りやめ
#m-jaxon.render(```
\def\Duck{d_uc^k}
\def\Twice#1{#1#1}
\Twice\Duck
```, inline: true)
\expandafterのない\Twice\Duckは一回展開すると\Duck\Duckとなり、最終的な展開結果はd_uc^kd_uc^kとなります。

\expandafterを付けてみましょう!
// Typst v0.10.0を使用
#import "@preview/m-jaxon:0.1.1"
Result:
// \expandafter を復活
#m-jaxon.render(```
\def\Duck{d_uc^k}
\def\Twice#1{#1#1}
\expandafter\Twice\Duck
```, inline: true)

おっと、赤文字が出てしまいました😲 MathJax使用者にはお馴染みだと思いますが、赤文字は当該箇所でエラーが発生したことを表します。この場合のエラーは要するに「\expandafterは定義されていない」ということです。m-jaxonを使っても「Typstで \expandafter する」のは無理なようです(ざんねん🙃)
それでも \expandafter しようと頑張ってみる
絶望的な状況😭になってしまいました。既存のTypstパッケージは \expandafter をサポートしていません。しかもその原因は「WASMで動いているTeXコード処理器が \expandafter をサポートしていない(恐らくTeXの展開処理を模倣していない)」からであるため、Typst側のコードを改修して解決する見込みもありません。もはや「新しいWASMプラグインをつくる」くらいしか道は残されてないのでしょうか……😟
……いや待ってください。m-jaxonパッケージはJavaScriptのTeXコード処理器であるMathJaxを使っていましたが、著名な「JavaScriptのTeXコード処理器」はもう1つあります。そう、KaTeXです。
KaTeXで \expandafter したい話
KaTeXは「MathJaxの代替となる」ことを目指して開発されたJavaScriptライブラリで、高速に動作することを特徴としています。そして、今回の件において重要なことですが、\expandafter をサポートしています。これは実際にKaTeXのサイトトップにあるデモアプリで試してみれば判ります。

※実はKaTeXはTeXのトークン列の展開をある程度模倣する動作を行います。なので \expandafter だけでなく \noexpand や \futurelet も使えます😲
KaTeXもMathJaxと同じくJavaScriptのライブラリなので、m-jaxonが「Typst上でMathJaxを動かしている」のと同じ手順を踏めば「Typst上でKaTeXを動かす」ことができるはずです。
ただし、MathJaxの代わりにKaTeXを使う場合には注意点があります。それは「KaTeXから受け取ったデータを数式として組版する簡単な手段がない」ことです。MathJaxのAPIには「SVGとして出力する」機能があり、m-jaxonはSVGを経由5して実際に数式を版面に出力しています。ところがKaTeXのサーバサイドAPIには「MathMLまたはHTMLとして出力する」機能しかないので、組版した数式を出力できないのです。
従って、KaTeXを使う方針には実用的価値はほとんどありません。とはいえ、今回の話の目的はあくまで「\expandafter を動かすこと🤯」であり、数式とかには全くサッパリ興味はないので、この点は全く問題になりません。
KaTeXで \expandafter してみる話
というわけで、早速つくってみました💪
- Typst:Typst上でKaTeXを動作させる(Gist/zr-tex8r)
このk-tieモジュール6を使うには以下の手順を行います。
- KaTeXのリリース版のアーカイブをGitHubのReleaseから7入手する。
- KaTeXのアーカイブ中の katex.min.js ファイルと上記Gistの中の k-tie.typ をカレントディレクトリに配置する。
- Typst文書中で
import "k-tie.typ"でモジュールを読み込む。
k-tieモジュールは以下の機能をもちます。
to-mathml(«LaTeX記述», …)[str]: LaTeXコードをKaTeXを用いてMathMLコードに変換する。«LaTeX記述»は複数指定可能(可変長)で、改行区切りで連結した文字列がKaTeXに渡される。to-string(«LaTeX記述», …)[str]:to-mathmlでMathMLに変換した後に、結果のMathMLデータに含まれる文字だけを抽出した文字列を返す。\expandafter の動作確認用🙃
それではまずk-tieが想定通りに動作するかを確認してみましょう。
#import "k-tie.typ" #k-tie.to-string(``` \alpha\times\beta ```)

どうやらうまくいっているようです🙂
今の場合、to-string()がMathMLから拾い出した文字列は「α×β」となり、これがそのまま版面に出力されています。数式として出力しているのではないことに注意してください。フォントについてもテキスト用の“Libertinus Serif”が使われています。
「Typst上でKaTeXを動かす」ことに成功したので、早速 \expandafter が正しく動作するかを試してみましょう!
#import "k-tie.typ"
// 前提のマクロ定義
#let macros = ```
\def\Duck{d_uc^k}
\def\Twice#1{#1#1}
```
// \expandafter なし
- #k-tie.to-string(macros, `\Twice\Duck`)
// \expandafter あり
- #k-tie.to-string(macros, `\expandafter\Twice\Duck`)

KaTeXでもっともっと \expandafter してみる話
最後に「Typst上で \expandafter を“ちゃんと”処理できるか」を確認してみましょう。もちろんこれは「KaTeXが \expandafter を“ちゃんと”処理できるか」に依存しています。先ほどよりも複雑な、「\expandafter 鎖」を含んだコードを実行してみます。
#import "k-tie.typ"
// 前提のマクロ定義
#let macros = ```
\def\A{\B\B}\def\B{\C\C}\def\C{88}
\def\Enclosed#1{[#1]}
```
// 展開制御しない(\expandafter なし)
- #k-tie.to-string(macros, ```
\Enclosed\A
```)
// 先に \A を1回展開(\expandafter 1個)
- #k-tie.to-string(macros, ```
\expandafter\Enclosed\A
```)
// 先に \A を2回展開(\expandafter 3個)
- #k-tie.to-string(macros, ```
\expandafter\expandafter\expandafter\Enclosed\A
```)
// 先に \A を3回展開(\expandafter 7個)
- #k-tie.to-string(macros, ```
\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter\Enclosed\A
```)

カンペキ😍
まとめ
というわけで、春のTeX言語キャンペーン🌸絶賛開催中です!😃
- rawエレメント自体はcontent値であって文字列(str値)ではありません。↩
-
このため
\defという機能もないので\defは単に無視されて、次の\Duckを実行しようとして(もちろんTeXの文法に従えばこんなことはありえない)未定義エラーになります。ちなみに、\expandafterも単に無視されます。↩ -
もちろんLaTeXでは
\sqrtはTeXのマクロとして実装されています。↩ - 0.10.0版以前の古いintrospectionの書き方を使っているので、0.12.0版では非推奨警告となり、0.13.0版ではエラーになります。↩
-
Typstの
image()関数はSVG画像をサポートしています。↩ - typst.tomlを用意してあるので、ローカルレポジトリにインストールしてパッケージとして使うこともできます。↩
- 「Assets」の下にある「tarball」および「zip」のリンクからアーカイブをダウンロードできます。↩
-
数式中の上付・下付の添字については、
to-stringで文字列に変換する際にその情報が落ちてしまいますが、もちろんMathMLのコードの中では反映されています(興味がある人はto-mathmlで確認してみてください)。↩