コンテンツへスキップ

アーキテクチャ

shikiphpはTextMate文法とVS Codeテーマを使い、ソースコードをテーマに沿ってハイライトしたHTMLに変換します。json/mbstring以外のPHP拡張、Nodeランタイム、ネイティブのOnigurumaバインディングは不要です。このページでは処理パイプラインを概説します。規範となる完全な仕様はリポジトリ内のARCHITECTURE.mdにあります。

処理パイプライン

コード
source code
  └─ Highlighter::codeToTokens()
       ├─ Grammar\Registry        loads the grammar (+ its includes / embedded langs)
       ├─ Grammar\Tokenizer       line by line, driving an OnigScanner per rule state
       │     └─ Oniguruma\OnigScanner.findNextMatch()
       │           └─ Oniguruma\PatternConverter   Oniguruma pattern → JS regex
       │                 └─ Shikiphp\Regex (Parser + Matcher)   JS RegExp runtime
       ├─ Theme\Theme.match(scopeStack) → StyleAttributes   per token
       └─ Render\ThemedToken[]   (text + style) per line
  └─ Render\HtmlRenderer  → <pre class="shiki">…</pre>

すべての処理はUTF-16コード単位のオフセット空間で行われます。これはJS正規表現エンジンとShikiのトークナイザーで使われる空間と同じです。ASCIIではバイトオフセットと一致します。

正規表現ランタイム

基盤となるのはShikiphp\Regexです。完全な純粋PHPECMAScript正規表現エンジンで、inline0/phasisから同梱されています(純粋PHPJavaScriptエンジン)。JS-RegExpソースとフラグを解析し、Onigurumaonig_searchセマンティクスで前方検索を実行します。

OnigurumaからJSへの変換

TextMateの文法はOnigurumaで記述されています。Shikiphp\Oniguruma\PatternConverterはoniguruma-to-esのPHP移植版です。Onigurumaパターンを、正規表現エンジンが受け入れるJS-RegExp互換のソースとフラグ文字列に変換します。これは、ShikiのJavaScript正規表現エンジンが使うOnigurumaからRegExpへの変換経路と同じです。そのため、shikiphpはエンジンの出力とオフセット単位で一致できます。変換器は、文法で使われる実用的な構文にも対応します。POSIXクラス、強欲量指定子とアトミックグループ、インラインフラグ、名前付きグループと後方参照、Unicodeプロパティなどです。

スキャナーとPCREの高速経路

Shikiphp\Oniguruma\OnigScannerはvscode-onigurumaのスキャナーを再現します。パターン一覧からルールごとに一度構築され、開始位置以降で最も左にある一致を見つけます。

ツリーをたどるマッチャーは正確ですが高速ではないため、スキャナーには同等性を条件とするPCRE高速経路が2段階あります。PcreTranslatorは、結果を保証できる場合に限り、コンバーターのJSソースをネイティブPCREパターンに書き換えます。

  • 完全一致。 すべてのキャプチャを含め、変換結果がマッチャーと同じ動作をする場合、パターンをそのままPCREで実行します。名前付きグループ、後方参照、\p{}、\b、\G、アトミック動作のエミュレーション、およびJavaScriptの意味がPCREと異なるキャプチャ(量指定グループ内または後読み内)は、この段階では除外されます。
  • 位置のみ。 キャプチャだけが異なる可能性がある場合、2つ目の変換ではそれらの構文(\bと\Bを正確なASCII先読み・後読みとして扱い、アトミックグループをネイティブ(?>…)として扱う。\p{…}はPCREでコンパイルできる場合)を許可します。PCREは次の一致位置だけを見つけ、マッチャーがそのオフセットから再実行して実際のキャプチャを生成します。後方参照と\Gはここでも除外されます。

それ以外はすべてマッチャーだけで処理されます。Shiki 3.23.0に同梱された文法では、重複のない14,234個のパターンのうち約39%がPCREで完全実行され、約57%が位置判定段階を使い、約4%はマッチャーにとどまります。この分類は、リポジトリ内の開発ツール(bin/.oracle-tools/pcre-equivalence.php)で検証されます。このツールはシナリオ入力と合成文字列のセットを使って高速経路とマッチャーを比較し、インデックス、終了位置、さらに最初の段階ではすべてのキャプチャ範囲について、差異が一切ないことを確認します。

文法とテーマ

  • 文法(Shikiphp\Grammar)はvscode-textmateの忠実な移植版です。Registryがスコープ名で文法を解決し(include参照、埋め込み言語、インジェクションを含む)、Tokenizerが行ごとにルールとスコープのスタックを管理します。
  • テーマ(Shikiphp\Theme)はvscode-textmateのテーマ照合を移植したものです。前景色とフォントスタイルは、親スコープとの照合を伴うスコープセレクターの詳細度に基づいて決まります。

レンダリング

Shikiphp\Render\HtmlRendererはShikiが構築するものと完全に同じpre.shiki > code > span.line > span HASTツリーを生成し、シリアライズします。単一テーマモードでは通常の色を出力し、デュアルテーマモードでは--shiki-light / --shiki-dark CSS変数を出力します。

Shikiとの照合検証済み

Shiki自体を正解の基準とし、shiki 3.23.0に固定して、そのJavaScript正規表現エンジンを使っています。scenarios/ディレクトリには214個のフィクスチャがあり、それぞれ1つの言語とテーマに対する入力です。全体で同梱されている253個の文法のうち167個、lang: 'ansi'、さらに65個すべてのテーマをカバーします。bin/test-regressionは両方のエンジンでそれぞれをハイライトし、先頭と末尾の空白を取り除いた後のHTMLが一致することを検証します。オプションとトランスフォーマーは、同じShikiと@shikijs/transformers 4.2.0から取得した出力との同等性テストで確認します。絶対条件は、ハイライト出力がShikiより後退しないことです。