Standard ML のシンタクスハイライトが読みにくくて直した

このサイトで表示している Standard ML のコードには,自作のシンタクスハイライトが当たっています.

Astro で Standard ML のシンタクスハイライト - 芳賀 雅樹 のページ
デフォルトでハイライトされない言語を TextMate を用いた拡張で認識させます.
Astro で Standard ML のシンタクスハイライト - 芳賀 雅樹 のページ favicon silasol.la
Astro で Standard ML のシンタクスハイライト - 芳賀 雅樹 のページ

最低限のハイライトで視認できれば OK くらいの気持ちで,キーワードとコメントとリテラルあたりをざっくりと TextMate で定義していましたが,あとから SML 熱が強くなって記事を増やしていくうちに,ほかの言語のコードブロックと比較すると平坦に見えるなという不満が出てきました.

こういうのの実装は,地味に要るけれど面倒であんま楽しくないな (当社比) ってことで後回しになりがちです.巷では Opus 5 が話題ですが,とりあえず LLM の馬力でぬるっと壁打ちしつつ解決することにしました.

文法定義はこんなんで,こういう感じに現状はハイライトされます.

(* 同型再帰やってくぞ *)
datatype 'a Rec = REC of ('a Rec -> 'a)

(* Z コンビネーターだお *)
val Z = fn f =>
  (fn REC x => f (fn y => x (REC x) y))
  (REC (fn REC x => f (fn y => x (REC x) y)))

当初の課題感とか

こういう状態で長らく止まっていました.

  • キーワードや演算子や組み込みの型名には色が付く
  • 関数名やモジュール名や SOME / NONE や引数などは白のまま
  • github-dark だとキーワードと組み込みの型名がみんな赤い

こういうコードだと structurefuncase には赤くなる一方で IntRingaddSOME / NONE や引数 x y m はみんな白いみたいな感じです.(このページには調整後の色が出ています)

structure IntRing : RING =
struct
  fun add (x, y) = x + y
  val show = Int.toString
  fun pick n =
    case fib n of
      SOME m => m
    | NONE => 0
end

OCaml は github-dark だと,モジュール名や束縛名や引数などに,ちゃんと視認しやすく色が割り当てられているようです.アンダーラインも出てる.

module IntRing : RING = struct
  let add (x, y) = x + y
  let show = Int.to_string
  let pick n =
    match fib n with
    | Some m -> m
    | None -> 0
end

実装の拡張方針

Shiki の仕組みでは,定義された文法がトークンにスコープ名を付け,テーマはそのスコープ単位で色を当てています.同じテーマなのに物足りないようであれば,SML として渡しているスコープのバリエーションを増やせばよいということになります.

TextMate の文法は基本的に正規表現なので The Definition の構文規則どおりにパースする仕組みにはならなくて,字句と予約語に加えて「fun の直後は名前」みたいな束縛のヒューリスティックを足しました.

  • fun / val / structure の直後の名前
  • SOME / NONE や大文字で始まる識別子
  • : の直後の型注釈
  • funfn| の引数やパターン変数
val eq : t * t -> bool
fun f (x : int) : int list = [x]

微調整やバグの修正

引数をオレンジにしたら,もともと白かった括弧とのコントラストがきつくなったので,括弧の色を抑えて引数の色も落ち着かせるよう (github-dark に対して) SML 用スコープの配色を追加するなどもしました.対話しながらのカラー調整など,この手の細かい試行錯誤では生成 AI が便利です.

数値の正規表現に識別子の境界が足りておらず,こういう例で a1a1 に分けて塗られてしまうのも直しました.

fun add (((a1, b1), (c1, d1)), ((a2, b2), (c2, d2))) =
  ((a1 + a2, b1 + b2), (c1 + c2, d1 + d2))

コーディング目的で解析しているわけじゃないので,エディタほど厳密に色分けはしていなくて,f x y みたいな適用や普通の小文字の識別子などはバグると面倒なので塗ってません.

ブログとしての視認性を上げるという目的は充分達成できたので,ひとまずはこんなところかなと思います.