本文へ移動
mie 仕様
REPL Playground

mie 整形 (mie fmt) 仕様

本書は mie fmt の仕様を定める。起動ディスパッチ全体は mie-command.md、言語の意味論は language-spec.md を参照。

mie ソースを正規形へ整形する。AST を再構成して出力するため、整形後のソースは元と構文木が等価 (同じ意味) になる。CI・保存時フック・エディタ連携での自動整形を想定する。

構文

mie fmt [-w] [-l] [path...]

引数の並びは フラグ → パス の順。path はファイルまたはディレクトリ (ディレクトリは配下を再帰して .mie を収集)。収集ファイルはパス昇順で処理する。

フラグ意味
(なし)整形結果を stdout に書く
-wファイルを整形結果で上書き (内容が変わる場合のみ書き込む)
-l整形すると変わるファイルのパス名のみを列挙する (書き込みはしない)

path を 1 つも与えない場合は stdin を読み stdout に整形結果を書く。このとき -w / -l は対象ファイルが無いため引数不正 (exit code 2)。

整形規則

  • インデントは半角スペース 2。タブ・末尾空白は除去する。
  • 文区切りはセミコロンではなく改行に正規化する (1 行に収める inline 形を除く)。
  • 中置/前置式は優先順位に基づき意味維持に必要な括弧のみ残す (冗長な括弧は除去)。
  • 文/ボディ位置のタプルは括弧を外す ({x | (a, b)}{x | a, b}、多値返却 (q, r)q, r)。曖昧さの出る代入の右辺 (r := (a, b)) や式中 (f((a, b))) では維持する。
  • 1 引数の呼び出しは並置形 f x に正規化する (f(x)f xobj.m(x)obj.m x)。ただし引数が適用より弱い式 (中置/前置など) のときは結合が変わらないよう括弧を残す (f(a + b)f (a + b)f(-x)f (-x))。後置チェーンの基底が並置になる場合も全体を括る (print(r.step(2).to_list())print ((r.step 2).to_list()))。
    • 例外: 値コンストラクタと型コンストラクタ (大文字始まりの callee) は単一引数でも括弧を保つ。値コンストラクタ (Some(x) / Ok(v) / Err(e)・ユーザ定義タグ) と型コンストラクタ (List(T) / Option(T)) が対象で、値位置・@match の値コンストラクタパターン (Ok(amt) => …)・型位置のいずれでも並置化しない (Ok 5Ok(5)x: List Intx: List(Int))。spec / prelude がこれらを Ok(v) / Some(x) => … / List(T) と括弧付きで記す表記 (language-spec.md §8.4 / §17、prelude.md §13) に揃え、複数引数 OneOf(A, B) とも一貫させる。小文字始まりの関数適用 (op(5)op 5) は従来どおり並置化する。
    • 例外: 後置 ! / ? で直接 kick / bail される単一引数適用は括弧呼び出し形 f(x)! を保つ。並置形 f x は後置演算子より弱く結合する (language-spec.md §8.3) ため、その結果を kick するには囲み括弧 (f x)! が要る。この形では並置化せず括弧呼び出し形に正規化する ((http.get "x")!http.get("x")!(fs.read p)!.unwrap!fs.read(p)!.unwrap!(store.get k)?store.get(k)?)。future の await をはじめ kick / bail は頻出し、囲み括弧より読みやすく、元から並置化しない複数引数 fs.write(a, b)! とも一貫する。callee 自身が並置適用のとき ((f g x)!f g) は囲み括弧を保つ。大文字始まりの値/型コンストラクタは上の規則どおり括弧を保つため本例外の対象外。
  • 0 引数 f() と 2 引数以上のタプル形 f(a, b) は元の形を保つ! postfix も保つ。
  • 文字列はエスケープを正規化して "..." で再クォートする (複数行 """...""" はそのまま保つ)。
  • { slot-list | body } などのオブジェクトは、元ソースが複数行に跨る (または内部にコメントを含む) 場合のみ複数行に展開し、そうでなければ 1 行に畳む。本体付きオブジェクトの仮引数 (スロット列) は開き括弧と同じ行に置く ({ amt | のようなラムダ形)。複数行でも開き括弧と同じ行に header (スロット/inner-name) が続く波括弧は { の後に 1 スペースを入れる。
  • 1 行に畳んだ波括弧 {...} は内側を 1 スペースで余白を取る ({ a, b | a + b }{ running = false }{ i < 10 })。ただし body 省略のデータオブジェクトは末尾 |} を詰める ({ x = 1, y = 2 |})。複数行に展開した slot-list (データオブジェクト {} / リスト []) はスロットを改行区切りで縦に並べ、スロット間にカンマを付けない (改行が区切りを兼ねる。language-spec.md §1.3)。1 行 inline 形は従来どおり , 区切り。複数行に展開したデータオブジェクトは閉じ |} を単独行に置く (最終スロットが値つき name slot name := value のとき)。末尾が bare な default-less name slot / 位置スロットの場合は | を最終スロット行に付けたまま閉じる。同様に、トップレベル (ファイルレベル) のスロット/ボディ区切り | も単独行に置く。空オブジェクト {} は余白なし。角括弧 [...] は 1 行に収まるなら従来どおりタイトに保つ ([1, 2, 3])。
  • 制御構造 (if / when / while) が値を消費しない位置 (文・ボディ位置) にあるときは、分岐/本体ブロックを 1 行ソースでも複数行へ展開する (改行尊重の例外)。値位置 — 代入 RHS (:= / =)・呼び出し引数・中置/前置の被演算子・後置 ! / ? の対象・条件式 — では 1 行を保つ。ブロック本体の文 (末尾の返り値位置も含む) は値を消費しない位置とみなす (run := { | if c {a} {b} } は展開)。while の条件ブロックは値位置として 1 行を保ち、本体のみ展開する。タプル形 if(c, {t}, {e}) はタプル形のまま分岐ブロックだけ展開する (AST 保存)。この規則は AST を保存し冪等。mie lint --fix の並置化経路も同じ展開を通す。
  • 行幅 (既定 120 桁、mie.fmt.mie で変更可 — 「整形設定」) を超える行は slot-list を複数行へ展開する (展開専用 — 既存の改行尊重は不変で、短い複数行を 1 行へ畳むことはしない)。対象はオブジェクト {} / リストリテラル [] / 分割代入の左辺 [a, b, c]。1 行に収めるとその行が設定された行幅 (既定 120 桁・文字数) を超える場合に複数行へ展開する。判定は行頭からの桁と同じ行の後続文脈 (例 ] := @import "...") を勘定に入れ、まず最も外側を展開し、子は深いインデントで再測定してなお収まらないものだけをさらに展開する (最小限の展開)。展開後のレイアウトは上記 slot-list の縦並び規則 (改行区切り・末尾カンマなし) に従う。折り返せる slot-list を含まない行や、分割しても短くならない行はそのまま残す (best effort)。タプル (a, b) / 呼び出し引数列 f(a, b) / 中置・メソッド連鎖は行幅超過による自動展開の対象外。
  • 元ソースが複数行で書かれたタプル () / 括弧呼び出しの引数列は、その改行を維持して縦展開する (要素を改行区切りで並べ・末尾カンマなし・閉じ括弧は開いた行のインデントに置く)。1 行で書かれたものは 1 行のまま。冗長な「カンマ + 改行」はカンマを落として改行区切りに正規化する ((1,⏎2)(⏎ 1⏎ 2⏎))。
  • @match は位置を問わず常に複数行へ展開する (arm は 1 行 1 arm、@match 行から 1 段インデント。閉じ }@match 行のインデントに置く)。1 行で書かれた @match r { Ok(v) => v; Err(e) => e } も arm ごとの改行区切りへ正規化する — 制御構造の「値位置では 1 行を保つ」例外 (上記) は @match には適用しない。帰結として、@match を部分木に含むオブジェクト {} / リスト [] は 1 行に畳めないため複数行へ展開する。また値位置の @match は、裸で出すと再パースで結合・意味が変わる位置 — 並置引数 (f (@match …))・後置 ! / ? の対象・中置/前置の被演算子・slot-list 要素 — でグルーピング括弧を保つ。
  • 複数行になる body の @match アームは => の後で改行して body を 1 段深くぶら下げるpattern [| guard] => body の body が複数行 (多文 body、または if (…) {…} / when (…) {…} のように整形後に改行を含む単一式 body)、あるいは頭と body を 1 行に畳むとその行が行幅 (「整形設定」) を超える場合、=> までを頭行に置き body を次行 (インデント +1) に展開する (代入 body と同じ縦レイアウト)。頭 (パターン・ガード) と body 式そのものは reflow しない (best effort・冪等)。body が単一行に収まり、頭込みでも行幅内のアームだけ head => body の 1 行に保つ。=> の前後は 1 スペースに正規化し、アーム間で => の桁を揃えない (揃えると 1 arm の編集が全 arm の差分になる。行末コメントの前は 2 スペース)。
  • コメントは保持する。独立行コメントは直後の要素の前に、行末コメントは同じ行の末尾に付け直す。
  • 空行は意味のある区切りとして 1 行だけ保持する (連続空行は 1 行に畳む)。
  • ファイル末尾は改行 1 個で終える。
  • 整形は冪等 (整形済みソースを再度整形しても変化しない)。

出力と終了コード

状況code
全ファイル整形成功 (-l で差分なしを含む)0
-l で差分あり / いずれかのファイルに構文エラー1
I/O エラー / 引数不正2

構文エラーのあるファイルは整形せず、診断を stderr に出してそのファイルをスキップする (他ファイルの処理は継続)。-l が差分ありで 1 を返すのは、CI で mie fmt -l . を整形チェックに使えるようにするため。

整形設定 (mie.fmt.mie)

整形パラメータはリポジトリ内の設定ファイル mie.fmt.mie で変更できる。設定ファイルが無ければ既定値で整形する (既定の挙動)。

設定ファイルは #[mie] ライブラリモジュールで、スロットにパラメータを書く。現在変更できるのは行折り返し幅 width (正の整数) のみ。

#[mie]
width := 100
  • 探索: 整形対象の各ファイルのあるディレクトリから上位へ遡り、最初に見つかった mie.fmt.mie を使う (per-file 解決)。パスを与えない (stdin) ときはカレントディレクトリから遡る。
  • 既定: mie.fmt.mie が見つからない、または width スロットが無い場合は既定 width = 120
  • 未知スロットは無視する (前方互換。将来の設定項目追加に備える)。
  • 不正な設定 (構文エラー / width が非整数 / width <= 0) は引数不正扱い (exit code 2) とし、診断を stderr に出してそのファイルは書き換えない。