⛄️

【入門記事】 SATySFi のコマンド記法

2020/12/10に公開

この記事は SATySFi advent calendar 2020 の10日目の記事です。
昨日は fiveseven さんによる記事「SATySFi で \widehat を実装する」でした。
明日は na4zagin3 さんによる記事「SATySFi で『太字』にしたい(satysfi-fss の紹介)」です。

monaqa です。もう12月の上旬も終わろうとしていますね。12月といったら師走、師走といったら SATySFi です。楽しく SATySFi でマークアップしましょう!

SATySFi でマークアップするには、コマンドを使いこなすことが必要不可欠です。逆に、コマンドさえ完璧に使いこなせればあなたはもうすでに立派な SATySFist、になれる可能性があります。そこで、アドベントカレンダーという折角の舞台を活かして「SATySFi のコマンドを使いこなす上でもしかすると役に立つかもしれない3つのこと」をまとめたいと思います。

  1. 【12/3】SATySFi の型について
  2. 【12/10】SATySFi のコマンドを書く方法 (本記事!
  3. 【12/17】SATySFi のコマンドを定義する方法

ここで説明する内容は基本的に The SATySFibook に記載されています。詳しい説明はそちらを参照してください。ただし、この記事に関して誤りを発見された場合は記事下のコメントまたは Twitter の @mo_naqa などにご連絡いただけると助かります。
また、以下の解説は SATySFi v0.0.5 を想定しています。将来のバージョンで仕様が変わる可能性があるためご注意ください。

はじめに

本記事では

  • SATySFi のコマンドの種類
  • SATySFi のコマンド記法

について説明します。説明するのはあくまで構文的な側面のみであり、「段落を作成するにはどんなコマンドを使う」「文書を強調するにはどんなコマンドを使う」といった組版言語としての機能は本記事では触れません。

SATySFi には様々な種類のコマンドがあり、書き方もそれぞれ異なります。例えば以下のような文書を考えてみましょう。

@require: stdja

StdJa.document (|
  ... 省略 ...
|) '<

  +p{
    Hello, \SATySFi;!
    Hello, \emph{world}!
  }
  +eqn(${
    \frac{\alpha}{2}
  });

>

上の例では6つのコマンドが登場しています。

コマンド 種類 効果
+p{ «text» } ブロックコマンド 引数のテキストから段落を作成する
\SATySFi; インラインコマンド \text{SAT\kern-1.5pt\raisebox{-2.5pt}{Y}SF\kern-0.7pt\raisebox{-2.5pt}{I}}」を出力する
\emph{ «text» } インラインコマンド 引数の中身を強調する
+eqn( «math» ); ブロックコマンド 引数の数式を別行立てで組む
\frac{ «n» }{ «d» } 数式コマンド 引数を分子分母に持つ分数を組む
\alpha 数式コマンド \alpha」を出力する

上の例を見るだけでも分かる通り、 \ から始まるものと + で始まるもの、 ; で終わるものとそうでないもの、など異なる書き方が存在することが分かりますね。これらはいいかげんに書いて良いものではなく、予め定められている文法規則を守らなければ、文法エラーや型エラーを引き起こしてしまいます。

コマンドの種類

先週の記事で述べたとおり、文書に関わる型のうちユーザがリテラルとして直接記述可能なものは3種類ありました。

  • インラインテキスト (inline-text)
  • ブロックテキスト (block-text)
  • 数式 (math)

それに対応して、コマンドも全部で3種類あります。

  • インラインコマンド (inline-cmd): inline-text リテラルの中で用いられるコマンド
  • ブロックコマンド (block-cmd): block-text リテラルの中で用いられるコマンド
  • 数式コマンド (math-cmd): math リテラルの中で用いられるコマンド

それぞれのコマンドはそれぞれの型のリテラル中でのみ用いることができ、それ以外の場所(プログラムモード中、数式中など)では原則用いることができません。つまり、コマンドを混同すると確実にコンパイルエラーとなります。そのため、3つのコマンドの種類を正しく区別することはとても重要です。

インラインコマンド

インラインコマンドはインラインテキストの中で用いることができるコマンドです。

文法の原則

まずは最も単純なインラインコマンドの例を見てみましょう。

{やっぱり \SATySFi; はサイコー!}

この \SATySFi; という箇所がインラインコマンドです。単に「\text{SAT\kern-1.5pt\raisebox{-2.5pt}{Y}SF\kern-0.7pt\raisebox{-2.5pt}{I}}」というテキストを出力するだけなので、引数はありません。

次は少しだけ複雑になった、引数を取るコマンドの例です。

{続きは \url(`https://example.com`); で。}

この例では \url(`https://example.com`); という箇所がインラインコマンドです。今適当に名前だけ作ったコマンドですが、なんとなくどういうマークアップが期待されるかは想像がつきますね。引数の文字列を等幅書体のテキストにし、ハイパーリンクを付けて出力する、という動作になりそうです(場合によってはフォントサイズや文字色を変えたりもするかもしれません)。このように、コマンドは1つ以上の引数を取ることもできます。

より一般に、N 個の引数を持つインラインコマンドは、以下のような記法で書くことができます。

\command-name(arg-1)(arg-2)(arg-3) ... (arg-N);

このように、インラインコマンドはコマンド名 \command-name、丸括弧で囲まれた引数 (arg-n)、末尾のセミコロン ; の3要素からなります。

なお、インラインコマンドにも型がついていて、

[type-1; type-2; ...; type-N] inline-cmd

のように表します。 type-nn 番目の引数 arg-n に対応する型です。したがって引数に入れられる値は特定の型に制限されており、なんでも入れられるわけではありません。例えば

[int; int] inline-cmd

という型を持つコマンド \add-and-show があったとして、

\add-and-show(1)(2);

のように用いることはできても、

\add-and-show(1.0)(`hoge`);

のように用いることはできません[1]

またコマンドの定義によっては、オプション引数をとることができる場合があります。オプション引数は引数の直前に ?: を付けて指定する必要があります。

\command-name?:(opt-1)?:(opt-2) ... ?:(opt-M)(arg-1)(arg-2) ... (arg-N);

オプション引数を取ることのできるコマンドには以下のような型が付きます。

[opttype-1?; ...?; opttype-M?; type-1; type-2; ...; type-N] inline-cmd

type? のように、型名の直後に ? のついているものがオプション引数の型を表しています。

以上がインラインコマンドの記法、その「原則」です。この規則に従った書き方では、たとえば単に文字を強調するだけの \emph コマンドも以下のように書かなければなりません。

ここを\emph({強調});します

LaTeX の記法と比べると少し面倒ですね。でも心配いりません。実は、上の例は以下のように書くこともできます。

ここを\emph{強調}します

ほとんど LaTeX と変わりませんね。引数が特定の条件を満たすとき、この例のように糖衣構文を用いてより簡潔に書くことができるのです。

糖衣構文1:丸括弧の省略

引数が直接表記されたリストまたはレコードである場合、丸括弧が省略できます。

元の表記 糖衣構文
\cmd([1; 2; 3;]); \cmd[1; 2; 3;];
`\cmd(( width = 1pt; height = 2pt
糖衣構文1の例

糖衣構文2:丸括弧と末尾のセミコロンの省略

特定の引数から後続する引数が全て inline-text または block-text のリテラルとなっている場合に限り、その部分を簡潔に書くことができます。

元の表記 糖衣構文
\emph({強調}); \emph{強調}
\parbox(200pt)('< ... >); \parbox(200pt)< ... >
\theorem({ title })('< ... >); \theorem{ title }< ... >
糖衣構文2の例

元の表記と糖衣構文には

  • 丸括弧 () が省略されている
  • ブロックテキストの括弧が '<> のペアではなく <> になっている
  • 最後のセミコロンが省略されている

という違いがあります。

また、SATySFiには inline-text list や箇条書き (itemize) リテラルを表す特別な糖衣構文も用意されており、これらを用いる場合にも丸括弧やセミコロンを省略することができます(省略できる条件は、 inline-text を引数とする際のルールに従います)。

\centering{| この文は | 中央揃えの段落として | 組まれる。 |}
\listing{
  * 箇条書き1
  * 箇条書き2
    ** 箇条書き2.1
    ** 箇条書き2.2
  * 箇条書き3
}

インラインコマンドの文法まとめ

インラインコマンドは基本的に以下の形で使います。

\command-name(arg-1) ... (arg-N);
  • 丸括弧で囲われた引数には、様々な型の値(式)を入れることができる
  • 頻繁に登場する inline-text 型や block-text 型などのリテラルを引数に持つ場合、より楽に書ける糖衣構文がある
    • inline-text list 型や itemize 型にも同様の構文が適用できる
  • 引数の前に ?: を付けるとオプション引数になる
    • オプション引数は必須引数より前に書く必要がある

ブロックコマンド

ブロックコマンドの記法は、インラインコマンドのそれとほとんど変わりません。唯一の違いは開始文字が \ ではなく + であることだけです。

一般に、 M 個のオプション引数と N 個の必須引数を持つブロックコマンドは、以下のような文法により呼び出されます。

+command-name?:(opt-1)?:(opt-2) ... ?:(opt-M)(arg-1)(arg-2) ... (arg-N);

このコマンドには以下のような型が付きます。

[opttype-1?; ...?; opttype-M?; type-1; type-2; ...; type-N] block-cmd

ブロックコマンドはインラインコマンドと同様の糖衣構文を備えています。たとえば

+p{The quick brown ...}

という記法における +p[inline-text] block-cmd 型を持つブロックコマンドであり、上の記述も

+p({The quick brown ...});

の糖衣構文です。

ブロックコマンドの文法まとめ

ブロックコマンドは基本的に以下の形で使います。

+command-name(arg-1) ... (arg-N);
  • 丸括弧で囲われた引数には、様々な型の値(式)を入れることができる
  • 頻繁に登場する inline-text 型や block-text 型などのリテラルを引数に持つ場合、より楽に書ける糖衣構文がある
    • inline-text list 型や itemize 型にも同様の構文が適用できる
  • 引数の前に ?: を付けるとオプション引数になる
    • オプション引数は必須引数より前に書く必要がある

数式コマンド

数式コマンドは、開始文字が \ であることもあって見かけ上インラインコマンドと似ています。しかし、「数式の中で用いられる」という性質上、その記法はインラインコマンドと大きく異なるため、しっかりと区別する必要があります。

原則

一般に、 M 個のオプション引数と N 個の必須引数を持つ数式コマンドは、以下のような文法により呼び出されます。

\command-name?:!(opt-1)?:!(opt-2) ... ?:!(opt-M)!(arg-1)!(arg-2) ... !(arg-N)

ややこしいですが、必須引数は !( ... ) という要素、オプション引数は ?:!( ... ) という要素がそれぞれ連なっています。数式コマンドには以下の型が付きます。

[opttype-1?; ...?; opttype-M?; type-1; type-2; ...; type-N] math-cmd

インラインコマンドの記法と比較すると、数式コマンドの記法の「原則」には以下のような違いがあります。

  • 最後にセミコロンが付かない。
  • 引数の前に ! がつく。

原則の通りに書くならば、 LaTeX での \frac{1}{2}\frac!(${1})!(${2}) と書かなければなりません(\frac の引数である 12 も、数式として組まれる以上 math 型であるため、 ${1}${2} の形をとる必要があります)。これはこれで正しい構文ですが入力するのはきわめて面倒であり、やはり \frac{1}{2} と簡潔に書きたいところです。

もちろん、そのための糖衣構文が用意されています。

糖衣構文1: math 型の括弧の省略

インラインコマンドにおける inline-text 型の引数と同様、特定の引数から後続する引数が全て math 型の場合、その部分を簡潔に書くことができます。

元の表記 糖衣構文
\paren!(${ ... }) \paren{ ... }
\frac!(${n})!(${d}) \frac{n}{d}
\hoge?:!(${a})!(${b}) \hoge?:{a}{b}
糖衣構文1の例

このように、コマンド名と引数を直接波括弧でつなげることができ、 ! も付かなくなります。\frac{e^x}{2}\sqrt{x + 1} など、数式コマンドの引数は殆どの場合 math 型になりますから、この糖衣構文は非常によく用いられます。

糖衣構文2: inline-text, block-text 型の括弧の省略

引数が inline-textblock-text のリテラルである場合、丸括弧を省略できます。

元の表記 糖衣構文
\text!({テキスト}) \text!{テキスト}
糖衣構文2の例

数式コマンドの文法まとめ

数式コマンドは基本的に以下の形で使います。

\command-name!(arg-1)!(arg-2) ... !(arg-N)
  • インライン・ブロックコマンドと異なり、末尾にセミコロンを付けない
  • !( ... ) の構文を用いて様々な型の引数を受け取ることができる
  • math 型の引数はよく使うため、楽に記述できる糖衣構文が用意されている
  • 引数の前に ?: を付けるとオプション引数になる
    • オプション引数は必須引数より前に書く必要がある

SATySFi のコマンド記法のありがたみ

SATySFi のコマンド記法はややこしいものの、LaTeX のコマンド記法とは異なり、引数がいくつあるか(どこからどこまでが引数なのか)ひと目で分かるようになっています。

たとえば LaTeX において

\command{equation-1}あああ

という記述があったとき、 \command の引数はどこからどこまででしょうか。{equation-1} だけが引数である…かもしれませんが、そうとも限りません。{equation-1} かもしれませんし、引数を取らないコマンドという可能性もあります。このように LaTeX では、予めそのコマンドに対する知識がない限り、どこまでがコマンドの引数なのか見ただけで判断することはできません。

それに対し、もし SATySFi のインラインテキスト中に

\command{equation-1}あああ

という記述があり、かつこれが静的解析に通っている場合、即座に \command の引数が {equation-1} 1つであると判断できます。

というのも、仮に \command が必須引数を1つも取らないコマンドであった場合

\command;equation-1あああ

と書かなければコンパイルエラーとなってしまいますし、{equation-1} の2つが必須引数だった場合は

\command{equation-1}{あ}ああ

と書かなければ が引数扱いされないからです。

このように、SATySFi のコマンド記法には

  • ユーザがそのコマンドを見ただけで、コマンドの引数がどこからどこまでなのかすぐ把握できる
  • ユーザが引数の個数や種類を誤っていた場合、静的解析により直ちに間違いを発見できる

という利点があるのです。コマンド含めてしっかりと型システムの恩恵に与れるのはありがたいことですね!

おまけ:SATySFi のコマンド記法がややこしい理由

まあ有り難みは分かったけど、もう少しなんとかシンプルにならなかったの、という方のために。SATySFi のインラインコマンド記法がややこしい理由を、開発者である @bd_gfngfn さんが自ら説明されているツイートがあります。

今日の夜は、組版用言語を設計する大変さに思いを馳せてみるのもいいかもしれません。

終わりに

本記事では SATySFi のコマンド記法について説明しました。来週は、自分でコマンドを定義する方法について説明します。

脚注
  1. なお、多相型を持つコマンドを定義することはできます。たとえば ['a; 'a ref] inline-cmd のような感じです。 ↩︎

Discussion