Перейти до вмісту

Архітектура

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, яким користується рушій регулярних виразів JavaScript у Shiki, що дає змогу shikiphp зіставляти його вихід із точністю до зміщення. Конвертер обробляє конструкції, які використовуються в реальних граматиках: класи POSIX, квантифікатори з присвоюванням і атомарні групи, вбудовані прапорці, іменовані групи та зворотні посилання, властивості Unicode та інше.

Сканер і швидкий шлях PCRE

Shikiphp\Oniguruma\OnigScanner повторює роботу сканера vscode-oniguruma: його один раз створюють для кожного правила зі списку шаблонів, і він знаходить найлівіший збіг у заданій початковій позиції або після неї.

Обхід дерева дає правильний результат, але працює повільно, тому в сканері є швидкий шлях PCRE із перевіркою еквівалентності, що має два рівні. PcreTranslator переписує джерело JS конвертера у власний шаблон PCRE лише тоді, коли може довести результат:

  • Повний збіг. Якщо перетворення поводиться так само, як засіб зіставлення, включно з усіма захопленнями, шаблон виконується безпосередньо в PCRE. Іменовані групи, зворотні посилання, \p{}, \b, \G, емуляція атомарних груп і захоплення, семантика JavaScript яких відрізняється від PCRE (усередині групи з квантифікатором або перевірки назад), не допускаються на цьому рівні.
  • Лише позиція. Якщо можуть відрізнятися лише захоплення, друге перетворення допускає ці конструкції (\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 pre.shiki > code > span.line > span, як і Shiki, а потім серіалізує його. Режим однієї теми записує звичайні кольори; режим двох тем записує змінні CSS --shiki-light / --shiki-dark.

Перевірено на відповідність 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.