SMLFormat: Pretty Printer for Standard ML

@author YAMATODANI Kiyoshi
@version $Id: OVERVIEW_ja.txt,v 1.9 2008/08/10 13:44:01 kiyoshiy Exp $

========================================
1. SMLFormat

SMLFormatは次の二つから構成される。

  * smlformat
  * formatlib


====================
1.1. smlformat

smlformatは単独で使用するツールである。
smlformatへの入力はtype/datatype宣言を含むSMLソースファイル(*.ppg)である。
このtype/datatype宣言には、その型の値を文字列化して出力する際の書式を、特殊なコメント形式で指定する。このコメントをフォーマットコメント(format comment)と呼ぶ。
smlformatは、type/datatype宣言とフォーマットコメントとを解析し、フォーマッタ(formatter)と呼ぶ関数のSMLコードを生成する。
ある型tに対して生成されるフォーマッタは、型tの値を受け取り、その値を整形出力するための中間表現を返す関数である。この中間表現をフォーマット式(format expression)と呼ぶ。

  -------(Absyn.ppg)-----------------------------------------------
  structure Absyn =
  struct

    (*%
     *)
    datatype exp
      = (*%
         * @format(const * loc) {const}
         *)
        EXPCONSTANT of constant * loc
      | (*%
         * @format(cond * ifTrue * ifFalse * loc)
         *            N0{ "if" 2[ +d {cond} ]
         *             +1 "then" 2[ +d {ifTrue} ]
         *             +1 "else" 2[ +d {ifFalse} ] }
         *)
        EXPIF of exp * exp * exp * loc
      | (*%
         * @format(exp * rule rules * loc)
         * N0{ "case" 2[+d {exp}] 2[+1 "of" ]+ {rules(rule)(~2[ +1 "|"] +)} }
         * @format:rule(pat * exp) {{pat} + "=>" +1 {exp}}
         *)
        EXPCASE of exp * (pat * exp) list * loc

        :

  end
  -----------------------------------------------------------------

Absyn.ppgを入力として与えると、smlformatはつぎのように入力ファイルの内容にフォーマッタの定義を挿入してSMLファイルを生成する。

  -------(Absyn.ppg.sml)-------------------------------------------
  structure Absyn =
  struct

    (*%
     *)
    datatype exp
      = 
        :

    fun format_exp x = ...

  end
  -----------------------------------------------------------------

このformat_expはつぎの型を持つ。

  val format_exp : exp -> FormatExpression.expression list

smlformatコマンドは次のコマンドオプションを受け取る。

  --stdout
    .ppg.smlファイルに替わり標準出力に結果のコードを書き出す。

  --with-line-directive
    コードの位置をSMLコンパイラに指示する行番号ディレクティブを
    結果のコードに挿入する。

====================
1.2. formatlib

formatlibは、SMLFormatストラクチャを提供する。
SMLFormatストラクチャは、prettyPrint関数を実装する。
prettyPrint関数は、フォーマット式と出力桁数を引数として受け取り、フォーマット式のテキスト表現を、桁数になるべく収まるように整形して出力する。
prettyPrint関数はつぎの型を持つ。

  val prettyPrint :
        PrinterParameter.printerParameter ->
          FormatExpression.expression list ->
            string

smlformatが生成したフォーマッタとSMLFormat.prettyPrintを用いて、つぎのようにAbsyn.exp型の式expressionの値を整形出力することができる。

  print
    (SMLFormat.prettyPrint
     {newlineString = "\n", spaceString = " ", columns = 60}
     (Absyn.format_exp expression))

ここでは桁数として60桁を指定している。
たとえばつぎのように出力される。

    123456789012345678901234567890123456789012345678901234567890
    ------------------------------------------------------------
    let
      val exn = getException context
      val message =
          case exn
            of SystemError => "SystemError"
             | UserError msg =>
               "User:" ^ msg ^
               (
                concatWith "\n"
                (
                 map (frameToString context) (getFrames context)
                )
               )
    in raise Error message end

フォーマットコメントでの書式指定を変更することにより、たとえば次のように出力形式を調整することができる。

    123456789012345678901234567890123456789012345678901234567890
    ------------------------------------------------------------
    let val exn = getException context
        val message = case exn of
                          SystemError => "SystemError"
                        | UserError msg =>
                          "User:" ^ msg ^
                            (
                             concatWith "\n"
                               (
                                map (frameToString context)
                                  (getFrames context)
                               )
                            )
    in raise Error message end


========================================
2. フォーマット式


====================
2.1. 記法

桁数colとフォーマット式の列 exp1 ... expn を入力として、
文字列 text に変換することを

  exp1 ... expn
  col=>
  text

と表記する。
桁数は省略する場合もある。

  exp1 ... expn
  =>
  text


====================
2.2. 文字列定数

二重引用符で囲まれた文字列定数はそのまま出力される。

  "jugemu"
  =>
  jugemu

連続する文字列定数は連結して出力される。

  "jugemu" "jugemu"
  =>
  jugemujugemu

間に空白を挟みたい場合は、文字列定数" "を使用する。

  "jugemu" " " "jugemu" " " "gokounosurikire"
  =>
  jugemu jugemu gokounosurikire

この例で分かるように、" "は式間の空白と識別しにくい。


====================
2.3. 空白指示子

" "のかわりに空白指示子(space indicator)'+'を使用することができる。

  "jugemu" + "jugemu" + "gokounosurikire"
  =>
  jugemu jugemu gokounosurikire


====================
2.4. 改行指示子

文字列長の合計が指定桁数を超える場合も出力は改行されない。

  "jugemu" + "jugemu" + "gokounosurikire" + "kaijarisuigyono"
  40=>
  jugemu jugemu gokounosurikire kaijarisuigyono
  ----------------------------------------
  1234567890123456789012345678901234567890

改行指示子(newline indicator)により、改行すべき位置を指定することができる。
改行指示子には、1以上の数値で優先順位を指定する。数の小さいほうが優先度が高い。

改行の必要がない場合、改行指示子は出力内容に影響しない。

  "jugemu" 2 "jugemu" 1 "gokounosurikire" 2 "kaijarisuigyono"
  50=>
  jugemujugemugokounosurikirekaijarisuigyono
  --------------------------------------------------
  12345678901234567890123456789012345678901234567890

出力内容が桁数に収まらない場合、優先度の高い改行指示子から順に改行される。

  "jugemu" 2 "jugemu" 1 "gokounosurikire" 2 "kaijarisuigyono"
  40=>
  jugemujugemu
  gokounosurikirekaijarisuigyono
  ----------------------------------------
  1234567890123456789012345678901234567890

優先度2より優先度1が優先されるので、まず、二番目の改行指示子で改行される。

同じ優先度を持つ改行指示子は、そのうちのいずれかの位置で改行する必要がある場合、そのすべての位置で改行される。

  "jugemu" 2 "jugemu" 1 "gokounosurikire" 2 "kaijarisuigyono"
  20=>
  jugemu
  jugemu
  gokounosurikire
  kaijarisuigyono
  --------------------
  12345678901234567890

この例では、優先度1をもつ改行指定子がひとつと、優先度2をもつ二つの改行指示子が含まれている。桁数が20である場合、まず優先度1の改行指定子で改行する必要がある。さらに、優先度2の改行指定子のうち、最初の指示子では改行する必要がないが、もうひとつの改行指示子では改行の必要がある。このため、両方の改行指示子の位置で改行される。

厳密には、優先度をnとする改行指定子Nの位置で改行すべきかどうかをつぎのように決定する。

  1) nと等しいあるいはnより低い優先度の改行指定子で改行する場合は改行する。

  2) 優先度がnより高い改行指定子のうちNの前方および後方で直近の二つの指定子をFとBとする。つぎの二つの数値ＬとWを比較し、W < L となる場合は改行する。

    L = FとBとに挟まれたフォーマット式の列を改行せずに出力するために必要な桁数

    W = Fの位置で改行したと仮定した場合のFの直後のカラム位置から、指定桁数までの桁数

         F         N                        B
     ... 1  "abc" +3 "def" 4 "ghi" +3 "jkl" 2 ...
          <-------------------------------->

    FとBにはさまれたフォーマット式を改行なしで出力するには 14(= 3 + 1 + 3 + 0 + 3 + 1 + 3)桁必要である。また、Fで改行すると、その直後は一カラム目から出力が開始される。つまり、Fの直後でのカラム位置から指定桁数までの桁数は、指定桁数に等しい。したがって、指定桁数が14桁より少ない場合にはNでの改行が必要になる。後で説明するように、Fの直後のカラム位置は必ずしも第一カラムであるとは限らない。

  3) 以上のいずれにも該当しない場合、改行しない。

注意：文字列定数内に"\n"を使用すれば常にその位置で改行されるが、現在のSMLFormatは文字列定数内に改行文字その他タブ文字などの書式文字を含めることを認めていない。


==========
2.4.1. 空白指示子と改行指示子

空白指示子と改行指示子は組み合わせて使用することができる。この場合、改行の必要がないならば空白一文字が出力される。

  "jugemu" +2 "jugemu" +1 "gokounosurikire" +2 "kaijarisuigyono"
  50=>
  jugemu jugemu gokounosurikire kaijarisuigyono
  --------------------------------------------------
  12345678901234567890123456789012345678901234567890

改行の必要がある場合は、空白を出力せずにその位置で改行する。

  "jugemu" +2 "jugemu" +1 "gokounosurikire" +2 "kaijarisuigyono"
  40=>
  jugemu jugemu
  gokounosurikire kaijarisuigyono
  ----------------------------------------
  1234567890123456789012345678901234567890

注意：上記の効果を有効にするには、空白文字と改行文字を続けて指定しなければならない。空白指示子と改行指示子の間に空白を挟むとそれぞれ個別に解釈され、空白指示子はつねに空白文字を出力する。

注意：文字列定数" "を使用した場合には改行の有無にかかわらず常に空白一文字が出力される。

  "jugemu" +2 "jugemu" " "1 "gokounosurikire" +2 "kaijarisuigyono"
  40=>
  jugemu jugemu 
  gokounosurikire kaijarisuigyono
  ----------------------------------------
  1234567890123456789012345678901234567890

分かりにくいが、出力結果の一行目の行末には空白一文字が出力されている。

==========
2.4.2. 劣後改行指示子

これまでの改行指示子には、すべて優先度を指定した。
優先度を指定しない改行指示子を使用することもできる。
これを劣後改行指示子(deferred newline indicator)と呼ぶ。
これまでの優先度を指定する改行指示子を優先改行指示子(preferred newline indicator)と呼ぶ。
劣後改行指示子は'd'と表記する。
すべての優先改行指示子の位置で改行しても指定桁数を超過する場合に限り、劣後改行指示子が指定された位置で改行される。

  "jugemu" +d "jugemu" +1 "gokounosurikire" +2 "kaijarisuigyono"
  30=>
  jugemu jugemu
  gokounosurikire
  kaijarisuigyono
  ------------------------------
  123456789012345678901234567890

この例では、優先改行指示子では改行されているが、劣後改行指示子では改行されない。
さらに小さな桁数を指定すると劣後改行指示子の位置でも改行される。

  "jugemu" +d "jugemu" +1 "gokounosurikire" +2 "kaijarisuigyono"
  10=>
  jugemu
  jugemu
  gokounosurikire
  kaijarisuigyono
  ----------
  1234567890


==========
2.4.3. 劣後改行指示子間の関係

前に述べたように、ある改行指示子で改行の必要がある場合、それと同じか、それより高い優先度の改行指示子のすべてで改行される。したがって、次の例では、優先度2の改行指示子で改行する必要がないにもかかわらず、最後の劣後改行指示子で改行の必要があるため、すべての改行指示子で改行される。

  "jugemu" +2 "jugemu" +1 "gokounosurikire" +d "kaijarisuigyono"
  30=>
  jugemu
  jugemu
  gokounosurikire
  kaijarisuigyono
  ------------------------------
  123456789012345678901234567890

ただし、劣後改行指示子の間では、優先度の優劣は定義されない。したがって、つぎの例では、三番目の劣後改行指示子では改行するが、最初の劣後改行指示子では必要がないため改行しない。

  "jugemu" +d "jugemu" +1 "gokounosurikire" +d "kaijarisuigyono"
  30=>
  jugemu jugemu
  gokounosurikire
  kaijarisuigyono
  ------------------------------
  123456789012345678901234567890


==========
2.5. インデントスタック

改行指示子で改行する際の行頭インデント幅を、インデント幅と直後の'['で指定することができる。']'により、インデント幅指定はキャンセルされる。

  "jugemu" +2 "jugemu" 5[ +1 "gokounosurikire" ] +2 "kaijarisuigyono"
  40=>
  jugemu jugemu
       gokounosurikire kaijarisuigyono
  ----------------------------------------
  1234567890123456789012345678901234567890

2番目の改行指示子の直前でインデント幅5が指定されているため、出力結果の二行目の行頭が5桁インデントされている。
3番目の改行指示子では、直前にインデント幅の指定が取り消されるため、改行する際のインデント幅はデフォルトの0となる。

  "jugemu" +2 "jugemu" 5[ +1 "gokounosurikire" ] +2 "kaijarisuigyono"
  30=>
  jugemu
  jugemu
       gokounosurikire
  kaijarisuigyono
  ------------------------------
  123456789012345678901234567890

インデント幅指定を3番目の改行指示子の後ろで取り消すよう変更すると、出力結果はつぎのようになる。

  "jugemu" +2 "jugemu" 5[ +1 "gokounosurikire" +2 "kaijarisuigyono"]
  30=>
  jugemu
  jugemu
       gokounosurikire
       kaijarisuigyono
  ------------------------------
  123456789012345678901234567890

インデント幅はスタック状に管理される。
'['は、インデントスタックの先頭にインデント幅をプッシュすることを指示し、']'は、インデントスタックの先頭要素をポップすることを指示する。
ある改行指示子で改行する際のインデント幅は、その時点でインデントスタックに積まれているインデント幅の合計値により決定される。

  "jugemu" +2 "jugemu" 5[ +1 "gokounosurikire" 3[ +2 "kaijarisuigyono"]];
  30=>
  jugemu
  jugemu
       gokounosurikire
          kaijarisuigyono
  ------------------------------
  123456789012345678901234567890

この例では、三番目の改行指示子の位置で改行する場合のインデント幅は3 + 5 = 8桁となる。

  "jugemu" 3[ +1 "jugemu" 5[ +2 "gokounosurikire" 3[ +3 "kaijarisuigyono"]]]
  30=>
  jugemu
     jugemu
          gokounosurikire
             kaijarisuigyono
  ------------------------------
  123456789012345678901234567890

この式の3番目の改行指示子で改行する際のインデント幅は、3 + 5 + 3 = 11桁となる。

インデント幅には負数を指定することもできる。

  "jugemu" +2 "jugemu" 5[ +1 "gokounosurikire" ~3[ +2 "kaijarisuigyono"]];
  30=>
  jugemu
  jugemu
       gokounosurikire
    kaijarisuigyono
  ------------------------------
  123456789012345678901234567890

この例では、三番目の改行指示子でのインデント幅は5 + (-3) = 2桁となる。

注意：インデント幅の合計が0より小さくなる場合、エラーとなる。
たとえば、つぎのフォーマット式では三番目の改行指示子で改行する場合にインデント幅が 3 + (-5) = -2 となるためエラーとなる。

  "jugemu" +2 "jugemu" 3[ +1 "gokounosurikire" ~5[ +2 "kaijarisuigyono"]];

====================
2.6. ガード

以上では、すべての改行指示子の優先度がグローバルスコープを共有していた。
ガード(guard)を用いると、優先度のスコープを分割したりネストすることができる。

ガードは、フォーマット式の列を"{"と"}"で囲むことで定義する。

  {"jugemu" +2 "jugemu"} +1 {"gokounosurikire" +2 "kaijarisuigyono"}

注意：インデントスタックの対応するプッシュとポップが異なるガードに属してはならない。
たとえば次のフォーマット式は正しくない。

  {"jugemu" 2[ +2 "jugemu"} +1 {"gokounosurikire" ] +2 "kaijarisuigyono"}


==========
2.6.1. ガードのネスト

ガードをネストさせた場合、囲む側のガードに属する改行指示子は、囲まれる側の改行指示子より高い優先度を持つ。

  {{"jugemu" +1 "jugemu"} +1 "gokounosurikire" +2 "kaijarisuigyono"}
  30=>
  jugemu jugemu
  gokounosurikire
  kaijarisuigyono
  ------------------------------
  123456789012345678901234567890

上の例では、最初の改行指示子は優先度1を指定されているが、外側のガードに属する改行指示子より優先度が低い。このため、外側のガードに属する優先度1および2の改行指示子で改行されているにもかかわらず、内側のガードの改行指示子で改行されていない。

外側のガードに属する劣後改行指示子と、内側のガードに属する優先改行指示子との間には、優先関係を規定していない。

  {"jugemu" +d "jugemu" +1 {"gokounosurikire" +1 "kaijarisuigyono"}}
  30=>
  jugemu jugemu
  gokounosurikire
  kaijarisuigyono
  ------------------------------
  123456789012345678901234567890

==========
2.6.2. ガードの分割

包含関係にないガード間では、それらに含まれる改行指示子の間に優先度の優劣関係は定義されない。

  {"jugemu" +2 "jugemu"} +1 {"gokounosurikire" +2 "kaijarisuigyono"}
  30=>
  jugemu jugemu
  gokounosurikire
  kaijarisuigyono
  ------------------------------
  123456789012345678901234567890

最初の改行指示子と三番目の改行指示子は別のガードに属している。
したがって、三番目の改行指示子で改行しているが、最初の改行指示子では必要がないため改行していない。


==========
2.6.3. ガードと基準カラム

改行指示子での改行後の先頭カラムは、正確には、基準カラム(base column)と、ガード内でインデントスタックにプッシュされたインデント幅の合計とを加算した値により決定される。
ガードに囲まれていない場合、基準カラムは0である。

ガードが開始されると、ガードが開始されたカラム位置がガード内の基準カラムとなる。

  "jugemu" + { "jugemu" 5[ +1 "gokounosurikire" ] ~3[ +1 "kaijarisuigyono" ]}
  30=>
  jugemu jugemu
              gokounosurikire
      kaijarisuigyono
  ------------------------------
  123456789012345678901234567890

この例では、内側のガードは二番目の"jugemu"の開始位置、すなわち8桁目から開始される。したがって、ガード内の基準カラムは8となる。ガード内の改行指示子で改行する場合の先頭カラムは、それぞれ、8 + 5 = 13, 8 + (-3) = 5となる。

つぎの例では、ガードの前後でインデントスタックがプッシュ・ポップされている。

  "jugemu" 3[ +1 { "jugemu" 5[ +1 "gokounosurikire"] ~3[ +1 "kaijarisuigyono"]}]
  30=>
  jugemu
     jugemu
          gokounosurikire
  kaijarisuigyono
  ------------------------------
  123456789012345678901234567890

この場合、ガードは最初の改行指示子による改行とインデントの直後から開始され、ガード内の基準カラムは4となる。ガード内の改行指示子で改行する場合の先頭カラムは、それぞれ4 + 5 = 9, 4 + (-3) = 1となる。


====================
2.7. 改行定数

改行定数はバックスラッシュと'n'で表記する。
改行定数はシンタックスシュガーといってよい。
改行定数は、他のいずれの改行指示子よりも高い優先度をもつ改行指示子と、それにつづくヌル文字の長大な列とみることができる。

  "jugemu" \n "jugemu"
  20=>
  jugemu
  jugemu
  --------------------
  12345678901234567890

この例では、指定された20文字の一行で出力できるにもかかわらず、改行されている。

  "jugemu" 1 { 4[ "jugemu" +2 "gokounosurikire" \n "kaijarisuigyono" ] }
  50=>
  jugemu
  jugemu gokounosurikire
      kaijarisuigyono
  --------------------------------------------------
  12345678901234567890123456789012345678901234567890

この例に見られるように、改行定数を最も内側で囲むガードより上位に位置するすべての改行指示子の箇所で改行される。
また、改行定数での改行は、他の改行指示子と同様に、インデントスタックにしたがってインデントされる。


========================================
3. 結合指示子

SMLFormatは、プログラミング言語処理系での解析構文木のテキスト表現出力処理の実装補助を主要な目的とする。
多くのプログラミング言語は、プログラム要素の親子関係をソースコードのテキストで正しく表現できるよう、構文要素間の生成規則に加えて結合規則を定義している。

SMLFormatは、フォーマット式の出力結果にプログラミング言語の結合規則を反映するためにフォーマット式間の結合力を表す結合指示子(assoc indicator)を導入する。

結合指示子は、ガード開始記号("{")の直前に結合の方向と強度を次のように記述して指定する。

  L10{ "map" + "getContents" + "documents" }
  R5{ "first" + "::" + "second" + "::" + "others" }
  N0{ "if" + "matched" +1 "then" + "Matched" +1 "else" + "Fail" }

'L'はガード内の要素間が左結合であることを指示する。
同様に、'R'は右結合であることを指示する。
'N'は結合方向を考慮しないことを指示する。
結合強度は数値で指定し、より大きな数値がより強い結合強度を表す。
ガード内要素間の結合力は結合方向と結合強度の両方から決定される。


====================
3.1. ガードの保護

SMLFormatは、結合指示子が指定されたガードを、必要に応じて括弧で囲む。


==========
3.1.1. 親子ガードの結合力の比較

たとえば、加算演算の結合力をL1、乗算演算の結合力をL2とし、算術式をつぎのようなフォーマット式で表す。

  L2{ L1{ "x" "+" "y" } + "*" + L1{ "v" + "w" }}

このフォーマット式の出力結果は、

  x + y * v + w

ではなく、

  (x + y) * (v + w)

とすべきである。

SMLFormatは、ガード内要素間の結合力と、そのガードを要素として含む上位ガード内の要素間結合力を比較して、ガードを括弧で囲むべきか決定する。
つまり、ガードCの結合力をSとし、Cを外から囲むガードPの結合力をTとすると、SがTよりも「弱い」ならば、C内の要素が、P内でCに隣接する要素と結合するのを防ぐためCを括弧で囲む必要がある。


==========
3.1.2. 結合方向と位置関係

ガードを括弧で囲むべきか判定するためには、親子関係にあるガードの結合力間の強弱関係に加えて、親ガード中での子ガードの位置を考慮に入れなければらない。

たとえば、関数適用の結合力をL10とし、二重にネストした関数適用式をつぎのフォーマット式で表す。

  L10{ L10{ "f" + "x" } + L10{ "g" + "y" }}

このフォーマット式の出力結果は

  f x g y

ではなく

  (f x) (g y)

とすべきである。
さらに、左結合であることから、左側の関数適用を囲む括弧を取り除いて

  f x (g y)

とすることができる。しかし、右側の関数適用を囲む括弧は省略できない。


同様に、関数型を構成する型構成子'->'の結合力をR1とすると、フォーマット式

  R1{ R1{ "t1" + "->" + "t2" } + "->" + R1{ "s1" + "->" + "s2" }}

の出力結果は

  t1 -> t2 -> s1 -> s2

ではなく

  (t1 -> t2) -> (s1 -> s2)

さらに

  (t1 -> t2) -> s1 -> s2

とすべきである。


====================
3.2. 結合指示子の除去

SMLFormatは、以下に説明する手順にしたがって、フォーマット式中の結合指示子付きガードを、結合指示子を持たないガードと、必要ならばガードを囲む括弧に変換する。

括弧による囲い込みの要不要は、上位ガードから「継承」する結合力と、ガードに指定された結合力との比較によって決定する。

==========
3.2.1. 継承する結合力

上位ガードから継承された結合力をSとし、フォーマット式をeとする。
eの子要素に対して継承する結合力をつぎのように決定する。

  e = T{ exp1 ... expk }の場合
    T = Ln である場合
         exp1 ... expk 中のガードまたは文字列定数のうち最も左に位置する式
        に対してLnを継承する。
         その他の要素に対しては Nn を継承する。
    T = Rn である場合
         exp1 ... expk 中のガードまたは文字列定数のうち最も右に位置する式
        に対してRnを継承する。
         その他の要素に対しては Nn を継承する。
    T = Nn である場合
         exp1 ... expk のそれぞれに対して Nn を継承する。

  e = {exp1 ... expk}の場合
    S = Ln である場合
         exp1 ... expk 中のガードまたは文字列定数のうち最も左に位置する式
        に対してLnを継承する。
         その他の要素に対しては Nn を継承する。
    S = Rn である場合
         exp1 ... expk 中のガードまたは文字列定数のうち最も右に位置する式
        に対してRnを継承する。
         その他の要素に対しては Nn を継承する。
    S = Nn である場合
         exp1 ... expk のそれぞれに対して Nn を継承する。

  その他の場合
    eは子要素をもたないので結合力の継承は不要。


==========
3.2.2. 結合指示子の'<'関係

結合指示子上での二項関係'<'を次のように定義する。

     An < Bm, if n < m (A,B are L,R or N)
     Ln < Nn
     Rn < Nn
     p < q, if p < r and r < q

また、

  p <> q == ((p = Ln and q = Rn) or (p = Rn and q = Ln))

と定義する。

==========
3.2.3. ガードの保護

上位ガードから継承された結合力をS、下位ガードに指定された結合指示子の結合力をTとすると、下位ガードを囲む括弧の必要性をつぎのように判断する。

  a) S < T または S = T ならば、ガードを囲む括弧は不要。
  b) その他、すなわち T < S または S <> T である場合、ガードを括弧で囲む。


==========
3.2.4. 変換例

上位ガードから継承した結合力をSとし、結合指示子をフォーマット式eから取り除いて新たに式e'を得ることを

  S | e => e'

と表記する。

先にあげた三つのフォーマット式の変換手順を示す。
継承する結合力の初期値としてN0を用いる。

=====
例１
 L2{ L1{ "x" "+" "y" } + "*" + L1{ "v" + "w" } }

  L1 < L2                      L1 < N2
  --------------------------   --------------------------
  L2| L1{"x" "+" "y"}          N2| L1{"v" "+" "w"}
    | => "(" {"x" + "y"} ")"     | => "(" {"v" + "w"} ")"   N0 < L2
  -----------------------------------------------------------------
  N0| L2{L1{"x" "+" "y"} + "*" + L1{"v" + "w"}}
    |        => {"(" {"x" + "y"} ")" + "*" + "(" {"v" + "w"} ")"}

=====
例２
 L10{ L10{ "f" + "x" } + L10{ "g" + "y" } }

  L10 = L10             L10 < N10
  -------------------   ---------------------------
  L10| L10{"f" + "x"}   N10| L10{"g" + "y"}
     | => {"f" + "x"}      | => "(" {"g" + "y"} ")"   N0 < L10
  ------------------------------------------------------------
  N0| L10{L10{"f" + "x"} + L10{"g" + "y"}}
    |            => {{"f" + "x"} + "(" {"g" + "y"} ")"}

=====
例３
 R1{ R1{ "t1" + "->" + t2" } + "->" + R1{ "s1" + "->" + "s2" } }

  R1 < N1                              R1 = R1
  -------------------------------      ---------------------------
  N1| R1{"t1" + "->" + t2"}            R1| R1{"s1" + "->" + "s2"}
    | => "(" {"t1" + "->" + t2"} ")"     | => {"s1" + "->" + "s2"}  N0 < R1
  -------------------------------------------------------------------------
  N0| R1{R1{"t1" + "->" + t2"} + "->" + R1{"s1" + "->" + "s2"}}
    |       => {"(" {"t1" + "->" + t2"} ")" + "->" + {"s1" + "->" + "s2"}}

==========
3.2.5. cut

SMLのlet式やtuple式、レコード式のように前後をキーワードや特殊記号で囲まれている式は、その式が置かれている文脈にかかわらず、カッコで囲む必要がない。
しかし、前述の結合指示子ではこのような場合に適切な結合度を指定することができない。

例として、つぎのSMLの式をフォーマット式であらわすことを考える。

  f (g x, y)     --(*)

SMLでは関数適用式は他の式より高い結合度をもつ。
したがって、exp1, exp2をそれぞれ関数式と引数式を表すフォーマット式とすると、関数適用式は次のようなフォーマット式で表すことができる。

  L10{ exp1 + exp2 }

つぎに、tuple式を

  N0{ "(" exp1 "," + ... "," + expn ")" }

であらわすことにする。
すると、式(*)をあらわすフォーマット式は

  L10{ "f" + N0{ "(" L10{ "g" + "x" } "," + "y"")" } }

となる。このフォーマット式を出力すると、

                    N0 < L10
                    ---------------------------------
                    N0| L10{ "g" + "x" } => "g" + "x"
                    ------------------------------------------
                    N0| "(" L10{ "g" + "x" } "," + "y" ")"
                      |       => "(" "g" + "x" "," + "y" ")"    N0 < N10
                    ----------------------------------------------------
                    N10| N0{ "(" L10{ "g" + "x" } "," + "y"")" }
  L10| "f" => "f"      |     =>  "(" "(" "g" + "x" "," + "y" ")" ")"
  ------------------------------------------------------------------
  N0| L10{ "f" + N0{ "(" L10{ "g" + "x" } "," + "y"")" } }
    |    => "f" + "(" "(" "g" + "x" "," + "y" ")" ")"

つぎのようにtuple式が冗長なカッコで囲まれてしまう。

  f ((g x, y))

これはtuple式の結合度が関数適用式の結合度より弱いためである。
そこで、tuple式の構成要素の結合度をN10に上げて

  N10{ "(" exp1 "," + ... "," + expn ")" }

とする。式(*)をあらわすフォーマット式は

  L10{ "f" + N10{ "(" L10{ "g" + "x" } "," + "y"")" } }

となり、これを出力すると、

                    L10 < N10
                    ------------------------------------------
                    N10| L10{ "g" + "x" } => "(" "g" + "x" ")"
                    ------------------------------------------
                    N10| "(" L10{ "g" + "x" } "," + "y" ")"
                       |   => "(" "(" "g" + "x" ")" "," + "y" ")"
                    ---------------------------------------------
                    N10| N10{ "(" L10{ "g" + "x" } "," + "y"")" }
  L10| "f" => "f"      |  =>  "(" "(" "g" + "x" ")" "," + "y" ")"
  ---------------------------------------------------------------
  N0| L10{ "f" + N10{ "(" L10{ "g" + "x" } "," + "y"")" } }
    |    => "f" + "(" "(" "g" + "x" ")" "," + "y" ")"

により、tuple内の関数適用式がカッコで囲まれてしまう。

  f ((g x), y)

この問題は、ガードに指定された結合力が、上位ガードから継承する結合力と下位ガードに指定された結合力の両者と比較されることに原因がある。
これを解決するため、結合指示子を拡張し、上位ガードからの結合力の継承を遮断(cut)し、ガードの結合力を下位ガードの結合力とのみ比較することを指示できるようにする。
先頭を"!"とする結合指示子をcut付き結合指示子と呼び、この指示子が指定されたガードは上位ガードの結合力にかかわらず、括弧で囲まれないものとする。

これを用いて、tuple式をあらわすフォーマット式を

  !N0{ "(" exp1 "," + ... "," + expn ")" }

とする。

式(*)をあらわすフォーマット式は

  L10{ "f" + !N0{ "(" L10{ "g" + "x" } "," + "y"")" } }

となり、これを出力した結果は、

                    N0 < L10
                    ---------------------------------
                    N0| L10{ "g" + "x" } => "g" + "x"
                    ------------------------------------------
                    N0| "(" L10{ "g" + "x" } "," + "y" ")"
                      |         => "(" "g" + "x" "," + "y" ")"
                    ---------------------------------------------
                    N10| !N0{ "(" L10{ "g" + "x" } "," + "y"")" }
  L10| "f" => "f"      |     =>  "(" "g" + "x" "," + "y" ")"
  ---------------------------------------------------------------
  N0| L10{ "f" + !N0{ "(" L10{ "g" + "x" } "," + "y"")" } }
    |    => "f" + "(" "g" + "x" "," + "y" ")"

により、

  f (g x, y)

となり、不必要な括弧が挿入されない。


========================================
4. フォーマッタ生成

SMLFormatは、type宣言およびdatatype宣言に与えられたコメントからフォーマッタ関数コードを生成する。


====================
4.1. 基本フォーマッタ関数

intやstringなど組み込みあるいはSML Basis Libraryで提供される基本的な型に対しては、あらかじめBasicFormattersストラクチャにフォーマッタ関数が用意されている。
たとえばint型に対してはformat_int、string型に対してはformat_stringが用意されている。

====================
4.2. type宣言に対するフォーマットコメント

つぎのようなtype宣言

  type number = int

でnumber型を定義し、この型のフォーマッタを定義したい。

SMLFormatにフォーマッタを生成させるためには、つぎのようにコメントで注釈を与える。

  (*%
   *)
  type number =
                 (*%
                  * @format(value) "123"
                  *)
                 int

SMLFormatに対する指示は、"(*%"と"*)"で囲まれたコメントに記述する。
これをフォーマットコメントと呼ぶ。
type宣言に対するフォーマットコメントを型宣言ヘッダコメント、'='の右辺の型式に対するフォーマットコメントを定義型式コメントと呼ぶ。
これらのフォーマットコメントは、typeキーワードおよび型式（この場合は"int"）の直前に配置しなければならない。


==========
4.2.1. 型宣言ヘッダコメント

type宣言に対するフォーマッタを生成させるためには型宣言ヘッダコメントが必要である。
この例では型宣言ヘッダコメントに何も記述する必要はないので空のコメントを与える。


==========
4.2.2. 定義型式コメント

定義型式ヘッダコメントには、フォーマットタグをひとつ記述する。
フォーマットタグは以下の形式をとる。

  "@format(" typepat ")" template ... template

typepatには、型式に対するパターンを記述する。これをタイプパターンと呼ぶ。

templateには、フォーマットテンプレートを記述する。
フォーマットテンプレートは、コメント対象の型の値をテキスト出力する際の書式を指定する。
フォーマットテンプレートの構文規則はフォーマット式の構文規則を包含するので、フォーマット式をそのままフォーマットテンプレートとして記述できる。

==========
4.2.3. フォーマッタの生成

number型に対してSMLFormatはつぎのようなフォーマッタコードを生成する。
説明のため、"<<"と">>"で囲まれた部分にはフォーマット式とSML式を混在して記述している。SMLFormatが生成する実際のコードでは、この部分は FormatExpression.expression list を生成する。

  fun format_number x = case x of value => << "123" >>

====================
4.3. datatype宣言に対するフォーマットコメント

datatype宣言に対するフォーマットコメントは、型宣言ヘッダコメントと、各値構成子に対する定義型式コメントから構成される。

  (*% *)
  datatype maybeNumber =
                        (*% @format(value) "456" *)
                        SomeNumber of number
                      | (*% @format "none" *)
                        NotNumber

定義型式コメントのフォーマットタグには、値構成子のとる引数の型式に対するタイプパターンと、テンプレートを記述する。
引数をとらない値構成子に対するフォーマットタグでは、タイプパターンは不要である。

SMLFormatは、maybeNumberに対してつぎのフォーマッタを生成する。

  fun format_maybeNumber x =
      case x of
          SomeNumber value => << "456" >>
        | NotNumber => << "none" >>


====================
4.4. タイプパターン

SMLFormatは、フォーマットタグ中のタイプパターンから、定義型式の値とのパターンマッチをおこなうコードを生成する。
また、型式とタイプパターンとの照合により、タイプパターン中の識別子に対応する型が判定できる。

タイプパターン中に現れる識別子は、フォーマットテンプレート中で使用することができる。
フォーマットテンプレート中の識別子は、実行時にその識別子に束縛される値のフォーマット式表現を生成するコードに変換される。
つまり、テンプレート中に現れる識別子は、それに対応する型に対して定義されているテンプレートをその位置に展開することに相当する。このため、テンプレート中の識別子の出現を、テンプレートインスタンシエーション(template instantiation)と呼ぶ。

テンプレートインスタンシエーションを使用して、number型に対するフォーマットコメントをつぎのように変更する。

  (*% *)
  type number =
                 (*% @format(value) value *)
                 int

SMLFormatはつぎのフォーマッタを生成する。

  fun format_number x = case x of value => << format_int(value) >>

型式とタイプパターンの照合により識別子valueには型式intが対応するので、テンプレート中に出現する識別子valueは、int型のフォーマッタ(format_int)を呼び出すコードに変換される。

maybeNumberも同様に変更する。

  (*% *)
  datatype maybeNumber =
                        (*% @format(value) value *)
                        SomeNumber of number
                      | (*% @format "none" *)
                        NotNumber

  fun format_maybeNumber x =
      case x of
          SomeNumber value => << format_number(value) >>
        | NotNumber => << "none" >>


==========
4.4.1. 識別子型式パターン

識別子はいずれの型式ともマッチするタイプパターンとして使用できる。


==========
4.4.2. tuple型式パターン

tuple型式に対しては、つぎのようにタイプパターンを指定することができる。

  (*% *)
  type region =
               (*% @format(left * right) "left=" left + "right=" right *)
               int * int

SMLFormatは、region型に対してつぎのフォーマッタを生成する。

  fun format_region x =
      case x of
          (left, right) =>
              << "left=" format_int(left) + "right=" format_int(right) >>


==========
4.4.3. レコード型式パターン

レコード型式についても同様である。

  (*% *)
  type range =
               (*% @format({min : minimum, max}) minimum + "<->" + max *)
               {min : int, max : int}

SMLFormatは、range型に対してつぎのフォーマッタを生成する。

  fun format_range x =
      case x of
          {min = minimum, max} =>
              << format_int(minimum) + "<->" + format_int(max) >>


==========
4.4.4. 引数をとる型構成子

SMLFormatは、listのように引数をとる型構成子に対してもフォーマッタを生成できる。

  (*% *)
  datatype 'a maybe =
                     (*% @format(value) value *)
                     Something of 'a
                   | (*% @format "none" *)
                     Nothing

このmaybeに対してSMLFormatが生成するフォーマッタは、型変数'aに対するフォーマッタを引数にとる。これをフォーマッタ引数と呼ぶ。

  fun format_maybe format_'a x =
      case x of
          Something value => << format_'a(value) >>
        | Nothing => << "none" >>


このmaybeを適用した型式に対するフォーマットコメントはつぎのように記述する。

  (*% *)
  type maybeString =
                (*% @format(str tycon) tycon(str) *)
                string maybe

型構成子適用に対するタイプパターンは、
  typepat ID
という形をとる。
この例では、識別子tyconには型構成子maybeがマッチし、パターンstrには型式stringがマッチする。

テンプレート中では、型構成子にマッチする識別子への引数に、型構成子への引数にマッチする識別子を与える形式で記述する。
この例では、tyconへの引数としてstrを与える形式となる。

補足：定義型式をみれば、識別子tyconにマッチするmaybeにはstringが引数として与えられていることは明らかである。したがって、テンプレート中で
    tycon(str)
のようにtyconに対する引数strも指定するのは冗長であるように思える。
しかし、これは、つぎに説明する追加引数を導入した結果、追加引数がどの識別子に与えられるものか対応を明らかにする必要が生じたための制約である。

maybeStringに対するフォーマッタは、つぎのようにformat_maybeの第一引数としてformat_stringを与える。

  fun format_maybeString x =
      case x of tycon => << (format_maybe format_string tycon) >>


==========
4.4.5. ワイルドカード型式パターン

 '_'をワイルドカードとして使うことができる。

  (*% *)
  type wildCard =
                  (*% @format(_ * n) n *)
                  (bool * int) 

ワイルドカード型式パターンは、タームに対するワイルドカードパターンに変換される。

  fun format_wildCard  x = 
      case x of
          (_, n) => 
          << format_int(n) >>


==========
4.4.6. 引用符付き識別子、ラベル

識別子やラベルが'd'あるいは数字である場合、改行指示子として解釈されることを防ぐため、一重引用符で囲まなければならない。

  (*% *)
  type quotedFields =
                    (*% @format({'d' : fd, '1' : f1}) fd f1 *)
                    {d : int, 1 : bool}

smlformatは次のフォーマッタを生成する。

  fun format_quotedFields  x = 
      case x of
           {d = fd, 1 = f1} => 
               << format_int(fd) format_bool(f1) >>


==========
4.4.7. 追加引数

フォーマッタは、フォーマット対象の値やフォーマッタ引数のほかに追加引数をとることができる。

  (*%
   * @params (label)
   *)
  type 'a anyLabeled =
                      (*% @format(value) label ":" value *)
                      'a

追加引数の名前は、型宣言ヘッダコメントに"@params"タグで指定する。
追加引数が複数必要な場合、

  (*%
   * @params (p1, ..., pk)
   *)

あるいは

  (*%
   * @params (p1)
   *     :
   * @params (pk)
   *)

のように記述する。

追加引数はテンプレート中で使用できる。
テンプレート中に出現する追加引数名は、実行時には、フォーマッタに対して呼び出し側から渡されるフォーマットテンプレートに置き換えられる。

anyLabeledに対して、SMLFormatはつぎのフォーマッタを生成する。

  fun format_anyLabeled (format_'a, label) x =
      case x of value => << label ":" format_'a(value) >>


型構成子Tのフォーマッタが追加引数をとる場合、Tにマッチする識別子をテンプレート中で使用する際には、フォーマッタ引数とは別に、追加引数も指定しなければならない。
任意のテンプレートを追加引数で渡すことができる。
フォーマッタがj個のフォーマッタ引数とk個の追加引数をとる場合、つぎの構文を使用する。

  ID(inst1, ..., instj)(temp1, ..., tempk)

フォーマッタが追加引数をとらない場合、後ろの括弧は不要である。

  ID(inst1, ..., instj)

フォーマッタがフォーマッタ引数をとらず、追加引数をとる場合は、

  ID()(temp1, ..., tempk)

としなければならない。

注意：SMLFormatは呼び出すフォーマッタの型を判定できないので、プログラマが呼び出し側のコード上でフォーマッタ引数と追加引数を別の括弧に分けることにより、これらを区別できるようにする。

たとえばanyLabeledを使用する側はつぎのように追加引数を渡す。

  (*% *)
  type intLabeled =
                      (*% @format(num tycon) tycon(num)("INT") *)
                      int anyLabeled

つぎのようなフォーマッタが生成される。

  fun format_intLabeled x =
      case x of tycon => << format_anyLabeled (format_int, "INT") tycon >>

注意：SMLFormatはフォーマッタが要求する追加引数がテンプレート中で指定されていなくてもエラーとしない。SMLFormatが生成したコードをコンパイルする際にエラーとなるはずである。


====================
4.5. 形式的定義

ここで、これまでに説明したフォーマッタ生成の形式的な定義を示す。


==========
4.5.1. 環境

フォーマッタ環境 F を型構成子名からフォーマッタ関数名へのマップとする。（'int'や'string'などは引数ゼロ個の型構成子とみなす。）

型環境 T を識別子から型へのマップとする。

追加引数集合 P を追加引数名の集合とする。


==========
4.5.2. フォーマットテンプレート変換

F,T,Pのもとでフォーマットテンプレートtemp からフォーマット式とSML式の混成exp を得ることをつぎのように書く。

  F,T,P | temp ==> exp

各テンプレートに対する規則を以下に示す。

(STRING)
    F,T,P | "string" ==> "string"
  
(INDICATOR)
    F,T,P | sp ==> sp

(INDSTART)
    F,T,P | ind[ ==> ind[

(INDEND)
    F,T,P | ] ==> ]

(SEQ)
    F,T,P | templ1 ==> exp1    F,T,P | templ2 ==> exp2
    --------------------------------------------------
    F,T,P | templ1 templ2 ==> exp1 exp2

(GUARD)
    F,T,P | temp ==> exp
    ---------------------------
    F,T,P | { temp } ==> { exp }

(INST1)
    T(ID) = (t1,...,tj) t    T(ID1) = t1   ...  T(IDj) = tj
    F(t) = f    F(t1) = f1  ...  F(tj) = fj
    F,T,P | temp1 ==> exp1
          :
    F,T,P | tempk ==> expk
    -------------------------------------------------------
    F,| ID(ID1,...,IDj)(temp1,..., tempk)
    T,|        ==>
    P |         f(f1, ..., fj, exp1, ... , expk)(ID)

    （この規則で生成されるのはSMLの関数適用式である。）

(INST2)
    T(ID) = t         F(t) = f
    --------------------------
    F,T,P | ID    ==>   f(ID)

    （この規則で生成されるのはSMLの関数適用式である。）

(INST3)
    P = P'+{ID}
    ---------------------
    F,T,P | ID   ==>   ID

    （この規則で生成されるのはSMLの変数式である。）


==========
4.5.3. フォーマッタ本体の生成

つぎに、F,Pのもとでフォーマットタグと変数名 xおよび型式 teから、SML式eを得る変換を

  F,P | @format(typepat) template, x, te  ==> e

と書く。
この規則の意味は、xはフォーマット対象の値を保持する変数であり、xの型がte、フォーマットタグはteに対する書式を指示するものである。

teがtuple型式または型構成子適用型式である場合についての規則を示す。
レコード型式については省略する。
'int'や'string'などの基本型は引数ゼロ個の型構成子適用型式として扱う。

(TUPLEtype)
    dom(F) includes {t1,...,tj}
    T = {ID1:t1, ..., IDj:tj}
    F,T,P | temp => e
    -------------------------------------------------
    F,| @format(ID1 * ... * IDj) temp,
    P | x, (t1 * ... * tj)
      |              ==> case x of (ID1,...,IDj) => e

(TYCONAPPtype)
    dom(F) includes {t,t1,...,tj}
    T = {ID:(t1,...,tj)t, ID1:t1, ..., IDj:tj}
    F,T,P | temp => e
    --------------------------------------------------
    F,| @format((ID1, ..., IDj) ID) temp,
    P | x, (t1, ..., tj) t 
      |                          ==> case x of ID => e


==========
4.5.4. フォーマッタの生成

以上を用いて、Fのもとで、SMLのtype宣言およびdatatype宣言から、フォーマッタ関数のコードと新たなフォーマッタ環境F'を得る手順を次の規則で示す。

ここでは、ひとつの型構成子だけがtype宣言およびdatatype宣言で定義されるものとする。
（'and'によって複数の型名が連続して定義される場合に対応するのは容易である。）


(TYPEdec)
    x,f1,...,fj are fresh variables
    F' = F+{t:format_t}
    F'' = F'+{'a1:f1, ..., 'aj:fj}
    P = {b1,...,bk}
    F'',P | formattag,x,t  ==>  e
    -------------------------------------------------------
    F | (*% @formatparams (b1,...,bk) *)
      | type ('a1,...,'aj) t =
      |         (*% formattag *) t
      |    ==>
      |            fun format_t(f1,...,fj,b1,...,bk) x = e,
      |            F'

(DATATYPEdec)
    x,x1,...,xj,f1,...,fj are fresh variables
    F' = F+{t:format_t}
    F'' = F'+{'a1:f1, ..., 'aj:fj}
    P = {b1, ..., bk}
    F'',P | formattag1,x1,t1  ==> e1
             :
    F'',P | formattagj,xj,tj  ==> ej
    -----------------------------------------------------
    F | (*% @formatparams (b1,...,bk) *)
      | datatype ('a1,...,'aj) t =
      |           (*% formattag1 *)  D1 of t1
      |         | ...
      |         | (*% formattagj *)  Dj of tj 
      |    ==>
      |      fun format_t(f1,...,fj,b1,...,bk) x =
      |          case x of D1 x1 => e1 | ... | Dj xj => ej,
      |      F'

値構成子が引数をとらない場合に関しては省略する。


====================
4.6. 複雑なパターン

これまでに示した例では、型構成子への引数、tuple型式やレコード型式の要素の型はいずれもひとつの識別子のみから成る単純なものであった。
SMLFormatは、より複雑な型式を扱うこともできる。


==========
4.6.1. ネストした型式に対するパターン

まず、ネストした型構成子適用型式、つまり型構成子の引数がさらに型構成子適用式である型式に対するフォーマッタも定義できる。

  (*% *)
  type maybeLabeledInt =
                        (*%
                         * @format(num may any) any(may(num))("INT")
                         *)
                        int maybe anyLabeled

maybeLabeledIntのフォーマッタはつぎのとおり。

  fun format_maybeLabledInt x =
      case x of
         any =>
           << format_anyLabeled (format_maybe format_int, "INT") any >>


tuple型式およびレコード型式の要素が、tuple型式あるいはレコード型式、型構成子適用型式である場合もフォーマッタを定義可能である。
つぎのmaybeRangeは、レコード型式の要素に型構成子適用型式が現れる例である。

  (*% *)
  type maybeRange =
       (*%
        * @format({min : min minMaybe, max : max maxMaybe})
        *       minMaybe(min) "<->" maxMaybe(max)
        *)
       {min : int maybe, max : int maybe}

  fun format_maybeRange x =
      case x of
        {min = minMaybe, max = maxMaybe} =>
         <<
          (format_maybe format_int minMaybe) "<->"
          (format_maybe format_int maxMaybe)
         >>


==========
4.6.2. 複合型式と識別子とのマッチ

これまでの例では、タイプパターン中の識別子は、照合対象の型式中のintやstringなどの単純型やmaybeなどの型構成子名とのみマッチしていた。
フォーマットテンプレートの構文では、識別子にレコード型式や型構成子適用型式など複合型式がマッチした場合に、そのフォーマット方法を記述することができない。

例として、つぎのtype宣言に対してフォーマッタを定義することを考える。

  (*% *)
  type labeledRange =
       (*%
        * @format(range any) any(range) <== ???
        *)
       {min : int, max : int} anyLabeled

識別子rangeにレコード型式がマッチするが、このレコード型の値に対するフォーマット方法を記述し、それをanyLabeledのフォーマッタに渡す手段がない。

この制約を回避する方法としては、つぎのように複数のtype/datatype宣言に分割することが考えられる。

  type range = {min : int, max : int}

  type labeledRange = range anyLabeled

しかし、この方法は煩雑である。


==========
4.6.3. ローカルフォーマットタグ

上記の問題に対して、SMLFormatでは、複合型式にマッチする識別子を、その複合型式に対して一時的に与えられた型名として扱い、その型に対するフォーマット指定を、定義型式コメント中の別のフォーマットタグで定義できるようにしている。

このフォーマットタグを、ローカルに型を宣言し、それに対するフォーマットを記述するという意味で、ローカルフォーマットタグと呼ぶ。
ローカルフォーマットタグに対して、定義型式に対するフォーマットタグをプライマリフォーマットタグと呼ぶ。

ローカルフォーマットタグは、

  "@format:"ID "(" typepat ")" template ... template

という形式で記述する。（"@format:"とIDの間には空白を挟んではならない。）

ローカルフォーマットタグを用いて、さきほどのlabeledRangeに対して、つぎのようにフォーマットコメントを記述できる。

  (*% *)
  type labeledRange =
       (*%
        * @format(range any) any(range)("Range:")
        * @format:range({min : min, max : max})
        *         "(min =" + min "," + "max =" + max ")"
        *)
       {min : int, max : int} anyLabeled

SMLFormatは、プライマリフォーマットタグのタイプパターン

  range any

と定義型式

  {min : int, max : int} anyLabeled

とのパターンマッチから、識別子rangeが型{min : int, max : int}をもつと判定する。
また、その下のローカルフォーマットタグは、識別子rangeを、タイプパターン

  {min : min, max : max}

とマッチする型式を指す名前として扱うことを指示する。
SMLFormatは、これらを総合して、ローカルに以下のように宣言されたものとみなす。

  type range = {min : int, max : int}

そして、プライマリフォーマットタグ中の識別子rangeがrange型をもつものとして扱い、range型に対するフォーマッタをローカルフォーマットタグから生成する。


SMLFormatは、このフォーマットコメントからつぎのフォーマッタ関数を生成する。

  fun format_labeledRange x =
      let
        fun format_range x =
            case x of
              {min = min, max = max} =>
              <<
                "(min =" + format_int(min) "," +
                "max =" + format_int(max) ")"
              >>
      in
        case x of
          any => format_anyLabeled (format_range, "Range:") any
      end

同様に、つぎのtree型にもローカルフォーマットタグを使用してフォーマットコメントを記述することができる。

  (*% *)
  datatype tree =
                 (*% @format(num) num *)
                 Leaf of int
               | (*%
                  * @format(child children) "{" children(child)(", ") "}"
                  * @format:child({child, label}) label "=" child
                  *)
                 Node of {child : tree, label : string} list

つぎのフォーマッタが生成される。

  fun format_tree x =
      case x of
          Leaf x => case x of num => << (format_int num) >>
        | Node x =>
          let
            fun format_child x =
                case x of
                    {child, label} =>
                    << (format_string label) "=" (format_tree child) >>
          in
            case x of
                children =>
                << "{" (format_list(format_child, ", ") children) "}" >>
          end


ネストした型構成子適用型式についても同様にローカルフォーマットタグを使用することができる。先ほどのmaybeLabeledIntを以下のように書き直すことができる。

  (*% *)
  type maybeLabeledInt =
                        (*%
                         * @format(maybeNum any) any(maybeNum)("INT")
                         * @format:maybeNum(num may) may(num)
                         *)
                        int maybe anyLabeled

つぎのフォーマッタが生成される。

  fun format_maybeLabledInt x =
      case x of
         any =>
           let
             fun format_maybeNum x =
                 case x of may => format_maybe format_int may
           in
             << format_anyLabeled (format_maybeNum, "INT") any >>
           end


注意：フォーマットタグは、並べる順序に関してつぎの制約にしたがわなければならない。
ある定義型式に対するフォーマットコメントで、フォーマットタグが

  @format( typepat0 ) ...
  @format:ID1( typepat1 ) ...
       :
  @format:IDk( typepatk ) ...

の順に並んでいるとき、IDi (1 <= i <= k)は、typepat0 ... typepat(i-1)のいずれかに出現してなければならない。
とくに、プライマリフォーマットタグをローカルフォーマットタグより先に記述することが必要である。


====================
4.7. カスタムフォーマッタ

フォーマットコメントは、それぞれのtype/datatype宣言について一回しか記述できない。また、intやstringなどシステムやライブラリで定義されている型に対してフォーマットコメントを記述することもできない。

SMLFormatは、ある型に対してすでに定義されているフォーマッタに替えて別のフォーマッタを使用したり、システムやライブラリなどで定義されている型に対するフォーマッタを指定する手段を用意している。これをカスタムフォーマッタと呼ぶ。

たとえば、word値を2進表記でフォーマットするフォーマッタをMyFormattersストラクチャに定義し、BasicFormatters.format_wordに替えて一部でこれを使用したいとする。

  fun myformat_binary x = << "0b" (Word.fmt StringCvt.BIN x) >>

あるtype/datatype宣言のフォーマットコメント中でカスタムフォーマッタを使用するには、宣言ヘッダコメント中につぎのタグを用いて指定する。

  @formatter(ID) qid

このタグは、IDを擬似的な型名として宣言し、この型に対するフォーマッタとしてqidが指す関数を使用することを指示する。
なお、qidはつぎの生成規則にしたがう。

  qid ::= ID
       |  ID "." qid

そして、定義型式コメント中で、識別子に対して使用するカスタムフォーマッタを指定する。タイプパターン中で指定する方法と、テンプレート中で指定する方法の二通りで指定できる。

例を示す。

  (*%
   * @formatter(binary) MyFormatters.myformat_binary
   *)
  type file =
             (*%
              * @format({name, flags : flags:binary})
              *           "{name=" name "," + "flags=" flags "}"
              *)
             {name : string, flags : word}

あるいはつぎのようにも書ける。

  (*%
   * @formatter(binary) MyFormatters.myformat_binary
   *)
  type file =
             (*%
              * @format({name, flags})
              *           "{name=" name "," + "flags=" flags:binary "}"
              *)
             {name : string, flags : word}

このfile型に対して、つぎのフォーマッタが生成される。

  fun format_file x =
      case x of
        {name, flags} =>
          <<
            "{name=" (format_string name) "," +
            "flags=" (MyFormatters.myformat_binary flags) "}"
          >>


====================
4.8. 形式的定義

ネストしたタイプパターン、ローカルフォーマットタグ、カスタムフォーマッタをサポートするため、形式的定義を拡張する。


==========
4.8.1. パターンマッチ


まず、型式tとタイプパターンtpから型環境Tを得る規則

    t <=> tp ==> T

を定義する。

(IDmatch)
    t <=> ID ==> {ID:t}

(TypedIDmatch)
    t <=> ID1 : ID2 ==> {ID1:ID2}

(TYCONAPPmatch)
    t1 <=> p1 ==> T1  ...  tj <=> pj ==> Tj
    ----------------------------------------
    (t1, ..., tj) t <=> (p1, ..., pj) ID
          ==> T1+...+Tj+{ID:(t1, ..., tj) t}

(TypedTYCONAPPmatch)
    t1 <=> p1 ==> T1  ...  tj <=> pj ==> Tj
    ----------------------------------------
    (t1, ..., tj) t <=> (p1, ..., pj) ID1 : ID2
          ==> T1+...+Tj+{ID1:(t1, ..., tj) ID2}

(TUPLEmatch)
    t1 <=> p1 ==> T1  ...  tj <=> pj ==> Tj
    -------------------------------------------
    (t1, ..., tj) <=> (p1,... pj) ==> T1+...+Tj


==========
4.8.2. SMLパターンの生成

つぎに、タイプパターンtypatから、SMLのパターンexpatを得る規則

    typat --> expat

を示す。

(IDpat)
    ID --> ID

(TypedIDpat)
    ID1 : ID2 --> ID1

(TYCONAPPpat)
    (tp, ..., tp) ID --> ID

(TypedTYCONAPPpat)
    (tp, ..., tp) ID1 : ID2 --> ID1

(TUPLEpat)
    tp1 --> pat1    ...    tpj --> patj
    ----------------------------------------
    (tp1 * ... * tpj) -->  (pat1, ..., patj)


==========
4.8.3. フォーマットテンプレート変換

INST1を修正するとともに、TypedINST1、TypedINST2を追加する。

(INST1)
    T(ID) = (t1,...,tj) t
    F(t) = f
    F,T,P | inst1 ARG==> f1
           :
    F,T,P | instj ARG==> fj
    F,T,P | temp1 ==> exp1
           :
    F,T,P | tempk ==> expk
    -----------------------------------------------
    F,| ID(inst1,...,instj)(temp1,..., tempk)
    T,|       ==>
    P |        f(f1, ..., fj, exp1, ... , expk)(ID)

(TypedINST1)
    F(ID2) = f
    F,T,P | inst1 ARG==> f1
           :
    F,T,P | instj ARG==> fj
    F,T,P | temp1 ==> exp1
           :
    F,T,P | tempk ==> expk
    -----------------------------------------------
    F,| ID1:ID2(inst1,...,instj)(temp1,..., tempk)
    T,|      ==>
    P |       f(f1, ..., fj, exp1, ... , expk)(ID1)

(TypedINST2)
    F(ID2) = f
    --------------------------
    F,T,P | ID1:ID2  ==>  f(ID1)


INST1とTypedINST1は、テンプレートインスタンシエーションからフォーマッタ関数名への変換

    F,T,P | inst ARG==> formatter

を用いる。この変換はつぎの四規則から成る。

(INST1-ARG)
    T(ID) = (t1,...,tj) t
    F(t) = f
    F,T,P | inst1 ARG==> f1
           :
    F,T,P | instj ARG==> fj
    F,T,P | temp1 ==> exp1
           :
    F,T,P | tempk ==> expk
    --------------------------------------------
    F,| ID(inst1,...,instj)(temp1,..., tempk)
    T,|        ARG==>
    P |         f(f1, ..., fj, exp1, ... , expk)

(TypedINST1-ARG)
    F(ID2) = f
    F,T,P | inst1 ARG==> f1
           :
    F,T,P | instj ARG==> fj
    F,T,P | temp1 ==> exp1
           :
    F,T,P | tempk ==> expk
    -----------------------------------------------
    F,| ID1:ID2(inst1,...,instj)(temp1,..., tempk)
    T,|         ARG==>
    P |            f(f1, ..., fj, exp1, ... , expk)

(INST2-ARG)
    T(ID) = t    F(t) = f
    ---------------------
    F,T,P | ID  ARG==>  f

(TypedINST2-ARG)
    F(ID2) = f
    -------------------------------
    F,T,P | ID1:ID2  ARG==>  f(ID1)


==========
4.8.4. フォーマッタ本体の生成


以上を用いると、前に示した(TUPLEtype)と(TYCONAPPtype)はつぎの規則に統合できる。

(TYPEtype)
    t <=> typepat ==> T
    T(ID1) <=> typepat1 ==> T1
    (T+T1)(ID2) <=> typepat2 ==> T2
         :
    (T+T1+...+Tj-1)(IDj) <=> typepatj ==> Tj
    Fj = F                            (* register formatters for alias type *)
    Fj-1 = Fj+{IDj:fj}
         :
    F1 = F2+{ID2:f2}
    F0 = F1+{ID1:f1}
    T' = T+{ID1:ID1,...,IDj:IDj}    (* overwrites entries for alias type *)
    T1' = T1+{ID2:ID2,...,IDj:IDj}
         :
    Tj' = Tj
    F0,T',P | templates ==> exp       (* generate exps for templates *)
    F1,T1',P | templates1 ==> exp1
         :
    Fj,Tj',P | templatesj ==> expj
    typepat --> pat
    typepat1 --> pat1
         :
    typepatj --> patj
    -----------------------------------------------------
    F, | @format(typepat) templates
    P  | @format:ID1(typepat1) templates1
       |       :
       | @format:IDj(typepatj) templatesj
       | x, t
       |
       |     ==>
       |
       |       case x of pat => let fun fj patj = expj
       |                               :
       |                            fun f1 pat1 = exp1
       |                        in exp end


==========
4.8.5. フォーマッタの生成

TYPEdecとDATATYPEdecを、宣言ヘッダコメントでのカスタムフォーマッタタグに対応して拡張する。

TYPEdec,DATATYPEdecのいずれも、定義型式に対するフォーマッタコードを生成する際に使用するフォーマッタ環境に、カスタムフォーマッタタグで指定されたフォーマッタを加える。

(TYPEdec)
    x,f1,...,fj are fresh names
    F' = F+{t:format_t}
    F'' = F'+{'a1:f1, ..., 'aj:fj}+{ID1:qid1, ..., IDn:qidn}
    P = {b1,...,bk}
    F'',P | formattag,x,t  ==>  e
    -------------------------------------------------------
    F | (*%
      |    @formatparams (b1,...,bk)
      |    @formatter(ID1) qid1
      |          :
      |    @formatter(IDn) qidn
      |  *)
      | type ('a1,...,'aj) t =
      |         (*% formattag *) t
      |    ==>
      |            fun format_t(f1,...,fj,b1,...,bk) x = e,
      |            F'

(DATATYPEdec)
    x,x1,...,xj,f1,...,fj are fresh names
    F' = F+{t:format_t}
    F'' = F'+{'a1:f1, ..., 'aj:fj}+{ID1:qid1, ..., IDn:qidn}
    P = {b1, ..., bk}
    F'',P | formattag1,x1,t1  ==> e1
             :
    F'',P | formattagj,xj,tj  ==> ej
    -----------------------------------------------------
    F | (*%
      |    @formatparams (b1,...,bk)
      |    @formatter(ID1) qid1
      |          :
      |    @formatter(IDn) qidn
      |  *)
      | datatype ('a1,...,'aj) t =
      |           (*% formattag1 *)  D1 of t1
      |         | ...
      |         | (*% formattagj *)  Dj of tj 
      |    ==>
      |      fun format_t(f1,...,fj,b1,...,bk) x =
      |          case x of D1 x1 => e1 | ... | Dj xj => ej,
      |      F'


====================
4.9. デフォルトフォーマットタグ生成

型宣言ヘッダコメントが指定されているにもかかわらず、ある定義型式に定義型式コメントが指定されていない場合、SMLFormatはその定義型式に対してフォーマットタグを自動生成する。これをデフォルトフォーマットタグと呼ぶ。そして、そのタグをもとに、プログラマが明示的にタグを指定した場合と同様に、フォーマッタコードを生成する。

たとえば、つぎのように型構成子maybeが定義されている場合

  (*% *)
  datatype 'a maybe =
                     Something of 'a
                   | (*% @format "none" *)
                     Nothing

SMLFormatは、値構成子Somethingに対してつぎのようなデフォルトフォーマットタグを生成したうえでフォーマッタコードを生成する。

  (*% *)
  datatype 'a maybe =
                     (*% @format(x1) {"Something" + {x1}} *)
                     Something of 'a
                   | (*% @format "none" *)
                     Nothing

デフォルトフォーマットタグによって生成されるフォーマッタは、フォーマット対象の値を、SMLのソースコード上で記述する場合と同様の形式にフォーマットする。
たとえば {name : string, age : int} 型の値 {name = "YAMADA", age = 20} を
  {name = "YAMADA", age = 20}
と出力し、int list型の値 [1, 3, 5] を
  [1, 3, 5]
と出力する。

デフォルトフォーマットタグの生成規則を以下に示す。
この規則は、型式tから、ひとつのプライマリフォーマットタグとj個のローカルフォーマットタグのリストを生成することを

  t => (typat, temps), {(id1, typat1, temps1), ..., (idj, typatj, tempsj)}

のように表記する。
プライマリタグはタイプパターンtypatとテンプレート列tempsから構成される。
ローカルフォーマットタグは、識別子、タイプパターン、テンプレート列から構成される。

(ATOMdeftags)
    x is fresh name
    ---------------
    ID
    =>
    (x, x), {}

(TUPLEdeftags)
    x1,...,xj are fresh names
    t1 => ((typat1, temp1), L1)
         :
    tj => ((typatj, tempj), Lj)
    ---------------------------------------------------------
    t1 * ... * tj
    =>
    ((x1, ..., xj), "(" x1 "," ... "," xj ")"),
    {(x1, typat1, temp1), ..., (xj, typatj, tempj)}+L1+...+Lj

(RECORDdeftags)
    x1,...,xj are fresh names
    t1 => ((typat1, temp1), L1)
         :
    tj => ((typatj, tempj), Lj)
    -----------------------------------------------------------------------
    {ID1:t1, ..., IDj:tj}
    =>
    ({ID1=x1, ..., IDj=xj}, "{" "ID1" "=" x1 "," ... "," "IDj" "=" xj "}"),
    {(x1, typat1, temp1), ..., (xj, typatj, tempj)}+L1+...+Lj

(TYCONAPPdeftags)
    x1,...,xj are fresh names
    t1 => ((typat1, temp1), L1)
         :
    tj => ((typatj, tempj), Lj)
    ---------------------------------------------------------
    (t1, ..., tj) ID
    =>
    ((x1, ..., xj) x, x(x1, ..., xj)),
    {(x1, typat1, temp1), ..., (xj, typatj, tempj)}+L1+...+Lj

tがList.listを適用する型構成子適用型式である場合は、追加引数をlist用のフォーマッタ(BasicFormatters.format_list)に渡す必要があるので規則を別に設ける。

(listAPPdeftags)
    x1,...,xj are fresh names
    t1 => ((typat1, temp1), L1)
         :
    tj => ((typatj, tempj), Lj)
    ---------------------------------------------------------
    (t1, ..., tj) list
    =>
    ((x1, ..., xj) x, "[" x(x1, ..., xj)(",") "]"),
    {(x1, typat1, temp1), ..., (xj, typatj, tempj)}+L1+...+Lj

tがArray.arrayやVector.vectorを適用する型構成子適用型式である場合も同様である。

tが関数型である場合は、値をパターンマッチによってそれ以上解析することができないので、固定の文字列定数を出力する。

(funAPPdeftags)
    x is a fresh name.
    --------------
    t1 -> t2
    =>
    (x, "<<fn>>"),
    {}

type宣言中の定義型式については、以上の規則で得られるプライマリフォーマットタグをそのまま使用する。
datatype宣言中の定義型式については、以上の規則で得られるプライマリフォーマットタグのテンプレート列の先頭に、値構成子の名前を挿入したうえで使用する。

====================
4.10. 型宣言

==========
4.10.1. 相互再帰するdatatype

ひとつのdatatype宣言中でandにより連結されて宣言された複数のデータ型に対しては、相互参照するフォーマッタコードが生成される。

==========
4.10.2. withtype

withtypeを用いた型宣言に対してもフォーマットコメントを記述できる。


  (*% *)
  datatype tree =
                 (*% @format(num) num *)
                 Leaf of int
               | (*% @format(child children) "{" children(child)(", ") "}" *)
                 Node of node list

  withtype node =
                 (*% @format({child, label}) label "=" child *)
                 {child : tree, label : string}

つぎのフォーマッタが生成される。

  fun format_tree x =
      case x of
          Leaf x => case x of num => << (format_int num) >>
        | Node x =>
          case x of
              children =>
              << "{" (format_list(format_node, ", ") children) "}" >>
  and format_node x =
      case x of
          {child, label} =>
              << (format_string label) "=" (format_tree child) >>

==========
4.10.3. abstype

abstypeにたいしてもフォーマットコメントを記述できる。

    (*% *)
    abstype set =
            (*% @format(element elements) "{" elements(element)(", ") "}" *)
            SET of element list
    withtype element =
             (*% @format(value) value *)
             string
    with
      fun create () = ...
      fun addTo (v, set) = ...
      fun isMemberOf (v, set) = ...
    end

これまでにみた型宣言ではフォーマッタコードは宣言の直後に生成されていたが、abstypeに対してはフォーマッタコードはつぎのように宣言内部に生成される。

    (*% *)
    abstype set =
            (*% @format(element elements) "{" elements(element)(", ") "}" *)
            SET of element list
    withtype element =
             (*% @format(value) value *)
             string
    with
      fun format_set x = ...
      and format_element x = ...
      fun create () = ...
      fun addTo (v, set) = ...
      fun isMemberOf (v, set) = ...
    end

====================
4.10.4 datatype replication

  datatype s = datatype M.t

の形式で定義されるデータ型については、つぎのようにフォーマッタを指定する。

    (*%
     * @formatter(Absyn.region) Absyn.format_region
     *)
    datatype region = datatype Absyn.region


====================
4.12. exception宣言

例外構成子に対してもフォーマットコメントを記述できる。
  
  (*% *)
  exception
  (*%
   * @format({fileName, line : leftLine, col : leftCol} *
   *         {line : rightLine, col : rightCol, ...} *
   *         message)
   * fileName ":" leftLine "." leftCol "-" rightLine "." rightCol ":" message
   *)
  ParseError of
  {fileName:string, line:int, col:int} *
  {fileName:string, line:int, col:int} *
  string

例外構成子が拡張可能であるため、例外構成子に対して生成されるフォーマッタは、以下のように、datatype宣言やtype宣言に対して生成されるフォーマッタとは異なる。

  local
    fun format x =
        case x of
          ParseError
          (
            {fileName, line = leftLine, col = leftCol},
            {line = rightLine, col = rightCol, ...},
            message
          ) =>
           <<
             (format_string fileName) ":"
             (format_int leftLine) "." (format_int leftCol) "-"
             (format_int rightLine) "." (format_int rightCol) ":"
             message
           >>
        | _ => (!SMLFormat.BasicFormatters.format_exn_Ref) x
    val _ = SMLFormat.BasicFormatters.format_exn_Ref := format
  in end

format_exn_Refおよびformat_exnは、BasicFormattersストラクチャにつぎのように定義されている。

  val format_exn_Ref =
      ref
      (fn exn => 
          let val text = General.exnMessage exn
          in [FE.Term (size text, text)] end)

  fun format_exn exn = !format_exn_Ref exn

注意: 現在のSMLFormatの実装は、例外宣言に対するフォーマッタ生成について、以下の二点に関して制限を設けている。
1, 後述の"prefix"タグを例外宣言に対して指定できない。
2, 次の形式の例外定義に対してフォーマットコメントを指定することはできない。
     exception E1 = E2

====================
4.12. prefixタグ

prefixタグを用いると、ひとつのtype/datatype宣言に対して複数のフォーマッタを自動生成することができる。
prefixタグは文字列一つをパラメータに取る。このパラメータは生成されるフォーマッタの名前のプレフィクスとして使用される。

  (*%
   * @prefix summary
   *)
  (*%
   * @prefix detail
   *)
  type address =
                (*%
                 * @prefix summary
                 * @format({zip, state, city}) state
                 *)
                (*%
                 * @prefix detail
                 * @format({zip, state, city})
                 *   "zip=" zip ",state=" state ",city=" city
                 *)
                {zip : string, state : string, city : string}

このaddressに対して、つぎの二つのフォーマッタが生成される。

  fun summaryaddress x =
      case x of
        {zip, state, city} => << format_string state >>

  fun detailaddress x =
      case x of
        {zip, state, city} =>
          <<
            "zip=" (format_string zip)
            ",state=" (format_string state)
            ",city=" (format_string city)
          >>

prefixタグは名前空間を指定する意味ももつ。
あるフォーマッタからは、同じプレフィクスを指定されたフォーマットコメントから生成されたフォーマッタのみを呼び出すことができる。
（例外として、format_stringやformat_intなどBasicFormattersで宣言されているフォーマッタはいずれのフォーマッタからも呼び出すことができる。）

  (*%
   * @prefix detail
   *)
  type customer = 
                  (*%
                   * @prefix detail
                   * @prefix({name, address, tel})
                   *      "name=" name ",address=" address ",tel=" tel
                   *)
                  {name : string, address : address, tel : string}

以下のように、このcustomerに対して生成されるフォーマッタは、addressに対して生成されたフォーマッタのうち、prefixをdetailとするフォーマッタを内部で呼び出す。

  fun detailcustomer x =
      case x of
        {name, address, tel} =>
          <<
            "name=" (format_string name)
            ",address=" (detailaddress address)
            ",tel=" (format_string tel)
          >>

異なるprefixを指定されたフォーマッタを呼び出すには、formatterタグを使用する。

prefixタグが指定されていない場合は、"format_"がプレフィクスに指定されたものとみなす。

datatypeについても同様にprefixタグを用いて複数のフォーマッタを生成できる。

  (*%
   * @prefix formatPlain
   *)
  (*%
   * @prefix formatHTML
   *)
  datatype block = 
                  (*%
                   * @prefix formatPlain
                   * @format(text) text
                   *)
                  (*%
                   * @prefix formatHTML
                   * @format(text) text
                   *)
                  Text of string
                |
                  (*%
                   * @prefix formatPlain
                   * @format(block) block
                   *)
                  (*%
                   * @prefix formatHTML
                   * @format(block) "<B>" block "</B>"
                   *)
                   Bold of block

つぎのフォーマッタが生成される。

  fun formatPlainblock x =
      case x of
        Text text => << format_string text >>
      | Bold block => << formatPlainblock block >>

  fun formatHTMLblock x =
      case x of
        Text text => << format_string text >>
      | Bold block => << "<B>" formatHTMLblock block "</B>" >>


========================================
