Skip to content

シンタックスハイライト

Onion は自分の字句面を 3 箇所に書いています。正本はそのうち 1 つだけです。

位置づけ ファイル 利用者
正本 grammar/JJOnionParser.jj コンパイラ
TextMate 文法 vscode-onion/syntaxes/onion.tmLanguage.json VS Code とその他の TextMate 系
Pygments lexer tools/pygments-onion/ このドキュメントサイト

ハイライタが 2 つある理由

docs サイトは MkDocs で生成しており、ハイライトは Pygments が担当します。Pygments は TextMate 文法を読めず、Material for MkDocs にも「静的生成のまま TextMate 文法を使う」 公式の経路がありません。そのため同じ字句面を、2 つの言語で、2 つの利用者向けに書いています。

これはドリフトの温床であり、実際に起きていました。Pygments lexer を用意する前、TextMate 文法のカバレッジはおよそ 70% まで腐っていました。traitinstance、そしてソフトキーワード (toolrequiresshapelawexamplefromderive)が全滅、加えてスキーム付き 生文字列(re"…"file"…"http"…")、|> パイプライン演算子、バッククォート識別子も 未対応でした。言語の一番特徴的な部分だけが色の付いていない状態で、しかも何も落ちませんでした。

ドリフトガード

src/test/scala/onion/compiler/tools/SyntaxHighlightingDriftSpec.scala が、キーワード集合を パーサから機械的に導出して、両方のハイライタをそれに従わせます。

  • ハードキーワードTOKEN ブロックの <K_NAME: "kw"> から
  • ソフトキーワードは文法が使う 2 系統のセマンティック先読み、 getToken(n).image.equals("x")la("x") / la2("x") ヘルパから。 両方を走査する必要があります——inla("in") の形でしか現れません。

検査は両方向です。順方向はパーサに足してハイライタに足し忘れたキーワードを、逆方向は パーサから消したのにハイライタに残った腐った項目を捕まえます。さらに、抽出そのものが 言語の差別化要素を今も見つけられているかも検査するので、文法ファイルを再編しても ガードが素通りすることはありません。

キーワードを足すとき

  1. grammar/JJOnionParser.jj に足す。
  2. sbt 'testOnly *SyntaxHighlightingDriftSpec' を走らせる。キーワード名つきで落ちる。
  3. vscode-onion/syntaxes/onion.tmLanguage.jsontools/pygments-onion/onion_pygments/lexer.py に足す。

ソフトキーワードの場合は、パーサの先読みが受理する唯一の形にルールを固定してください。 ソフトキーワードはそれ以外の位置では普通の識別子なので、val shape = 1 は無色のままで なければいけません。既存のルールは \btool\b(?=\s+[a-zA-Z_]\w*\s*\() のような先読みで これを実現しています。

キーワードのルールは、両ハイライタで意図的にスキームリテラルより先に置いています。 JJOnionParser.jj の字句解析器はスキームトークンになりかけたキーワード接頭辞を押し戻すので、 return"x"return の呼び出しではなく「キーワード+文字列」になります。キーワードを 先に照合することでこの挙動を再現しています。

ローカルでの docs ビルド

pip install mkdocs-material mkdocs-glightbox ./tools/pygments-onion
mkdocs serve

./tools/pygments-onion のインストールは省略できません。入れないと Pygments に onion lexer が存在せず、すべての ```onion フェンスが素のテキストとして描画されます。しかも mkdocs は何も警告しません——ページが見た目だけおかしくなります。

フェンスの検査

キーワード以外の字句形式が抜けていても、キーワードの穴としては現れません。Pygments の Error トークンとして現れ、それは無装飾のテキストとして描画されるだけでビルドは成功します。 そこでフェンスを直接 lex します。

python tools/pygments-onion/check_docs.py docs README.md

これは .github/workflows/docs.yml のデプロイ前と、プルリクエストで実行されます。 シェバンの抜けはこれで見つかりました——5 ページ 7 ブロックが、#!/usr/bin/env onion# に対して静かにエラートークンを出していました。

GitHub について

```onion フェンスは github.com では色が付きません。そしてこれはこのリポジトリ側からは 直せません。GitHub はフェンスの情報文字列を github-linguist の言語リストと照合しており、 そこに言語を追加するには、その拡張子のファイルが複数のリポジトリにまたがって 2000 件以上 インデックスされている必要があります。Onion はまるで届きません。.gitattributeslinguist-language も助けになりません——あれはファイルの分類を変えるもので、Markdown 内の フェンスのハイライトには効かないからです。

.gitattributes で得られるのは .on ファイル自体の色で、これは run/ 配下のサンプル プログラムにとって意味があります。表示上は Scala に写像し、リポジトリの言語統計からは外しています。

*.on linguist-language=Scala linguist-detectable=false

共通する構文——defvalvarclassextendscaseenum、文字列、コメント、数値、 そして Scala に文字列補間があるおかげで re"…" まで——は正しく色が付きます。conformsforeachselecttool といった Onion 固有のキーワードは付きません。これが他言語の文法を 借りることの正直な代償であり、フェンスのタグを付け替えずに onion のまま残している理由でもあります。 DocExamplesCompileSpec と readiness の抽出器はどちらもこのタグで Onion コードを見つけているので、 付け替えるとコンパイル検査が黙って止まります。