LaTeX
TeX

LaTeXで括弧を自動で補うマクロを書く

概要

LaTeXで文書を作成していると,とりわけ計算機科学系の記事や論文を書いていると「数式やプログラムの式を木構造だけ与えればあらかじめ定義された結合の強さをもとに自動で括弧を補って挿入してくれるコマンドを定義したいな」という気持ちになってきます(なってこない人も以下なってくるものと仮定してお読みください)。

例えば(非常に小さい例ですが)「$\mathbf{let}\ x = g\ (f\ n)\ y\ (n + 1)\ \mathbf{in}\ \cdots$」などというプログラムを文書中に書き表したいとき,構文木を意識して

\letin{x}{\app{g}{\paren{\app{f}{n}}|y|\paren{\binplus{n}{1}}}}{〈以降の内容〉}

などと打ちたくなると思います。ただし \letin\paren\binplus\app は適切に定義したコマンドとし,具体的には以下のように定義できます(\app| で区切った可変長引数を扱うために既にLaTeXを逸脱してTeX言語的な記述が登場していますが,とりあえず数行で定義できるということを把握してもらえれば読めなくても全く問題ありません):

\makeatletter
  \newcommand{\paren}[1]{(#1)}
  \newcommand{\binplus}[2]{#1 + #2}
  \newcommand{\letin}[3]{\mathbf{let}\ #1 = #2\ \mathbf{in}\ #3}
  \newcommand{\app}[2]{#1\ \local@app#2|\local@endofargument}
  \def\local@unique{\local@uniquecontent}
  \def\local@app#1|#2\local@endofargument{%
    #1\ifx\local@unique#2\local@unique\else
      \ \local@app#2\local@endofargument\fi}
\makeatother

しかし,この形式化では括弧を毎回手で打たなければなりません。さらには,例えば後で「読者はこのOCaml風の構文に慣れてない気がするし,もうちょっとC言語風の記法にしようかな」などと判断して「$\mathbf{let}\ x = g(f(n), y, n + 1)\mathbf{;} \cdots$」という具合の記法に全部書き換えたくなると,文書全体を目grepして括弧の位置をつけ替えねばならないことになってしまいます。木構造と“演算子の結合の強さ”の情報さえあればどこに括弧を補うべきかは本来おのずと決められることなので,括弧の手打ちはプログラマの美徳:“怠惰”(=冗長な情報を与える作業を厭う)に反する重大な問題です1 2

本記事は,簡単に言えばこういった括弧の要不要を“結合の強さ”から自動で決定してくれるコマンドを定義するためのパッケージを創ってみた,という紹介です。整備不足や怠惰3など諸々の理由でCTANにアップロードしたりはしていませんが,以下のGitHubリポジトリで公開しています:

github.com/gfngfn/gfnsynt

使い方

本パッケージでは,結合の強さは整数(および無限大)で扱うことにし,数値が大きいほど“結合が強い”とします。結合が強いとはすなわち“より括弧をつけなくてよい”ということで,例えば $(1 + (2 \times 3))$ は $\times$ の結合の方が $+$ よりも強いので括弧を省略して「$1 + 2 \times 3$」と書けるが,$(1 \times (2 + 3))$ は(最も外側を除いて)括弧が省略できず「$1 \times (2 + 3)$」と書かれる,という具合です。

結合の強さをコマンドに与えて定義する

結合の強さの情報がないことには括弧は補えませんから,コマンドの定義時に結合の強さを与えてやる機構が必要です。\newsyntax コマンドを使うことで実現します:

\newsyntax{〈a:結合の強さの整数値〉}{〈\cs:制御綴〉}[〈o:オプション引数〉][〈m:引数の個数〉]{〈S:置換される内容〉}

[〈o〉] および [〈m〉] の部分は省略可能です。要するに通常の \newcommand の用法で先頭の \newcommand の部分を \newsyntax{〈a:整数〉} に置き換えただけです。このコマンドにより制御綴 〈\cs〉 が結合の強さ 〈a〉 をもつコマンドとして定義できます。

括弧を補うか否かの閾値を与えて引数を書く

実際に「結合が或る閾値以上の場合は括弧をつけず,未満の場合は括弧をつける」という処理を書くには \syntaxarg コマンドを使います:

\syntaxarg{〈s:括弧がつかない結合の強さの最低値の整数〉}{〈\csp:括弧をつけるコマンド〉}{〈T:括弧をつけるかつけないか判定する対象〉}

結合の強さが定義されていないコマンドや文字が 〈T〉 として与えられた場合は結合の強さを無限大として扱います(すなわち,いかなる整数の閾値を指定しても括弧が補われません)。

上に挙げた $\times$ と $+$ を再び例にとると,それぞれ以下のように定義した \bintimes\binplus で自動で括弧を補うコマンドが定義できます:

\newsyntax{10000}{\paren}[1]{(#1)}
\newsyntax{3}{\bintimes}[2]{%
  \syntaxarg{3}{\paren}{#1} \times \syntaxarg{3}{\paren}{#2}}
\newsyntax{2}{\binplus}[2]{%
  \syntaxarg{2}{\paren}{#1} + \syntaxarg{2}{\paren}{#2}}

以下はこれを使った入力と出力です:

% -*- coding: utf-8 -*-
\documentclass{jsarticle}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage{gfnsynt}
\newsyntax{10000}{\paren}[1]{(#1)}
\newsyntax{3}{\bintimes}[2]{%
  \syntaxarg{3}{\paren}{#1} \times \syntaxarg{3}{\paren}{#2}}
\newsyntax{2}{\binplus}[2]{%
  \syntaxarg{2}{\paren}{#1} + \syntaxarg{2}{\paren}{#2}}
\begin{document}
  \indent
    これは\texttt{gfnsynt}パッケージのテストです.
    各コマンドが結合の強さに応じて括弧の要不要を判定して
    必要な場合だけ挿入してくれます:
    \[ \binplus{1}{\bintimes{2}{3}} \]
    \[ \bintimes{1}{\binplus{2}{3}} \]
  \par
\end{document}

スクリーンショット 2017-12-21 22.36.28.png

具体的用例集

プログラミング言語や型の理論で頻出の構文はリポジトリのこのへんのファイルでいくつも定義しています(投げやりですみません)。

おわりに

私自身が実際に論文の執筆に使用した実績(?)があるものの,バグが取り切れていないおそれがあります。お気づきの際は是非GitHubリポジトリのIssue,この記事のコメント欄,Twitterでのそれとない示唆,など何らかの形式でご指摘頂ければ幸いです。

また,gfnsynt パッケージにはここに紹介した以外にも他にいくつか機能がありますが,怠惰により書き切れません4。気が向けば,或いは要望があれば加筆したいと思います。


  1. 実際には著者がプログラムのどこを強調したいかによって無くてもよい括弧をつけたりしますが,そういう本質的に必要な括弧は(少なくとも非統計的な処理としては自動で判定できるはずがないので)手動で書くことになります。 

  2. もっと言えば「記号列を“ベタ打ち”する方法でも適切に文法を定めればLALR(1)などの構文解析により構文木を復元することができるのだから木構造を手書きすることだって冗長だ」という気もするわけですが,TeX/LaTeXでそういう仕組みを実現するのはかなり困難だろうと考え,そこは妥協しています。 

  3. この怠惰はプログラマの美徳ではないダメな方の怠惰です。 

  4. 勿論これもダメな方の怠惰によるものです。