Архитектура
shikiphp преобразует исходный код в подсвеченный HTML с помощью грамматик TextMate и тем VS Code, без расширений PHP за исключением json/mbstring, без среды выполнения 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 — полноценный движок регулярных выражений ECMAScript, полностью реализованный на PHP и поставляемый вендором из inline0/phasis (движок PHP JavaScript). Он разбирает исходный код RegExp JS с флагами и выполняет поиск вперед с семантикой Oniguruma onig_search.
Преобразование Oniguruma → JS
Грамматики TextMate написаны на Oniguruma. Shikiphp\Oniguruma\PatternConverter — это перенос PHP oniguruma-to-es: он преобразует шаблон Oniguruma в исходный код и строку флагов, совместимые с RegExp JS, которые принимает движок регулярных выражений. Это тот же путь Oniguruma → RegExp, который использует движок регулярных выражений Shiki JavaScript, что позволяет shikiphp сопоставлять его вывод со смещениями с точностью до позиции. Преобразователь обрабатывает конструкции, используемые в реальных грамматиках: классы POSIX, притязательные квантификаторы и атомарные группы, встроенные флаги, именованные группы и обратные ссылки, свойства Unicode и многое другое.
Сканер и ускоренный путь PCRE
Shikiphp\Oniguruma\OnigScanner повторяет работу сканера vscode-oniguruma: он создается один раз для каждого правила из списка шаблонов и находит самое левое совпадение на заданной начальной позиции или после нее.
Обход дерева для сопоставления работает правильно, но медленно, поэтому в сканере есть ускоренный путь PCRE с проверкой эквивалентности из двух уровней. PcreTranslator преобразует исходный текст шаблона JS в шаблон PCRE для нативного движка, только если может доказать корректность результата:
- Полное совпадение. Если преобразование ведет себя так же, как средство сопоставления, включая все захваты, шаблон напрямую выполняется в PCRE. Именованные группы, обратные ссылки,
\p{},\b,\G, эмуляция атомарных групп и захваты с отличающейся от PCRE семантикой JavaScript (внутри квантифицированной группы или ретроспективного просмотра) на этот уровень не допускаются. - Только позиция. Если могут различаться только захваты, второе преобразование разрешает такие конструкции (
\bи\Bкак точные ASCII-условия просмотра, атомарные группы как нативные(?>…),\p{…}при успешной компиляции в PCRE). Тогда PCRE лишь определяет позицию следующего совпадения, а средство сопоставления повторно запускается с этого смещения, чтобы получить реальные захваты. Обратные ссылки и\Gздесь также не допускаются.
Все остальное обрабатывается только средством сопоставления. Для встроенных грамматик в Shiki 3.23.0 около 39% из 14,234 различных шаблонов полностью выполняются в PCRE, около 57% используют уровень определения позиции, а около 4% остаются за средством сопоставления. Классификация проверяется инструментом разработки в репозитории (bin/.oracle-tools/pcre-equivalence.php), который сравнивает ускоренный путь со средством сопоставления — индекс, конец и для первого уровня каждый диапазон захвата — на входных данных сценариев и наборе искусственных строк; расхождений не допускается.
Грамматика и тема
- Грамматика (
Shikiphp\Grammar) — точный переносvscode-textmate:Registryопределяет грамматики по имени области видимости (включая ссылкиinclude, встроенные языки и внедрения), аTokenizerподдерживает стек правил и областей видимости построчно. - Тема (
Shikiphp\Theme) переносит сопоставление тем изvscode-textmate: цвет переднего плана и стиль шрифта определяются специфичностью селектора области видимости с учетом родительских областей.
Отрисовка
Shikiphp\Render\HtmlRenderer создает в точности то же дерево HAST, что и Shiki pre.shiki > code > span.line > span, а затем сериализует его. В режиме одной темы записываются обычные цвета; в режиме двух тем — переменные --shiki-light / --shiki-dark CSS.
Проверено на Shiki
Сам Shiki служит эталоном; он закреплен на версии shiki 3.23.0 со своим движком регулярных выражений JavaScript. В каталоге scenarios/ находится 214 тестовых примеров, каждый из которых содержит один входной файл для одного языка и одной темы; вместе они охватывают 167 из 253 встроенных грамматик, lang: 'ansi' и все 65 тем. bin/test-regression выполняет подсветку каждого примера обоими движками и проверяет, что HTML совпадает после удаления пробелов в начале и конце. Параметры и преобразователи проверяются тестами на соответствие выводу, полученному из того же Shiki и @shikijs/transformers 4.2.0. Обязательное условие: результат подсветки не должен регрессировать относительно Shiki.