Architektur
shikiphp wandelt Quellcode mithilfe von TextMate-Grammatiken
und VS Code-Themes in farbig hervorgehobenes HTML um. Es benötigt keine zusätzlichen PHP-Erweiterungen außer json/mbstring, keine Node-Laufzeitumgebung und keine native Oniguruma-Bindung. Diese Seite fasst die Verarbeitungskette zusammen. Die vollständige verbindliche Spezifikation findest du in ARCHITECTURE.md im Repository.
Die Pipeline
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>
Alles arbeitet mit Offsets im UTF-16-Code-Unit-Raum – demselben Raum wie die JS-RegEx-Engine und der Tokenizer von Shiki. Bei ASCII entspricht er den Byte-Offsets.
Die RegEx-Laufzeit
Die Basis bildet Shikiphp\Regex, eine vollständige reine ECMAScript-RegEx-Engine aus PHP, die als Vendor-Code aus inline0/phasisPHP übernommen wurde (eine reine JavaScript-Engine). Sie parst eine JS-RegExp-Quelle samt Flags und führt Vorwärtssuchen mit der Oniguruma-onig_search-Semantik aus.
Oniguruma-zu-JS-Konvertierung
TextMate-Grammatiken sind in Oniguruma geschrieben. Shikiphp\Oniguruma\PatternConverter ist ein PHP-Port von
oniguruma-to-es: Er wandelt ein Oniguruma-Muster in eine mit JS-RegExp kompatible Quelle und eine Flags-Zeichenfolge um, die die Regex-Engine akzeptiert. Das ist derselbe Oniguruma-→-RegExp-Pfad, den Shikis JavaScript-Regex-Engine verwendet. Dadurch kann shikiphp offsetgenau dieselbe Ausgabe wie diese Engine erzeugen. Der Konverter unterstützt die Konstrukte, die in echten Grammatiken vorkommen: POSIX-Klassen, possessive Quantifizierer und atomare Gruppen, Inline-Flags, benannte Gruppen und Rückverweise, Unicode-Eigenschaften und mehr.
Der Scanner und sein PCRE-Schnellpfad
Shikiphp\Oniguruma\OnigScanner bildet den Scanner von vscode-oniguruma nach: Er wird einmal pro Regel aus einer Liste von Mustern erstellt und findet den am weitesten links liegenden Treffer an oder nach einer Startposition.
Der Matcher, der den Baum durchläuft, ist korrekt, aber langsam. Deshalb hat der Scanner einen auf Äquivalenz geprüften PCRE-Schnellpfad mit zwei Stufen. Ein PcreTranslator wandelt die JS-Quelle des Konverters nur dann in ein natives PCRE-Muster um, wenn sich das Ergebnis nachweislich korrekt verhält:
- Vollständiger Treffer. Wenn sich die Übersetzung einschließlich aller Captures identisch zum Matcher verhält, wird das Muster direkt mit PCRE ausgeführt. Benannte Gruppen, Rückverweise,
\p{},\b,\G, die Emulation atomarer Gruppen und Captures, deren JavaScript-Semantik von PCRE abweicht (innerhalb einer quantifizierten Gruppe oder eines Lookbehinds), sind in dieser Stufe ausgeschlossen. - Nur Position. Wenn sich nur Captures unterscheiden könnten, lässt eine zweite Übersetzung diese Konstrukte zu (
\bund\Bals exakte ASCII-Lookarounds, atomare Gruppen als natives(?>…),\p{…}, sofern PCRE es kompiliert). PCRE ermittelt dann nur die Position des nächsten Treffers; der Matcher läuft an diesem Offset erneut, um die tatsächlichen Captures zu erzeugen. Rückverweise und\Gbleiben auch hier ausgeschlossen.
Alles andere wird nur mit dem Matcher verarbeitet. Bei den mitgelieferten Grammatiken aus Shiki 3.23.0 laufen etwa 39 % der 14.234 unterschiedlichen Muster vollständig über PCRE, etwa 57 % nutzen die Positionsstufe und etwa 4 % bleiben beim Matcher. Ein Entwicklungswerkzeug im Repository (bin/.oracle-tools/pcre-equivalence.php) prüft die Klassifizierung, indem es den Schnellpfad mit dem Matcher vergleicht – Index, Ende und bei der ersten Stufe jeder Capture-Bereich – und zwar anhand der Szenario-Eingaben und einer Reihe synthetischer Zeichenfolgen. Abweichungen sind nicht zulässig.
Grammatik und Theme
- Grammatik (
Shikiphp\Grammar) ist eine originalgetreue Portierung vonvscode-textmate: EinRegistrylöst Grammatiken anhand des Gültigkeitsbereichsnamens auf (einschließlich Referenzen aufinclude, eingebetteter Sprachen und Injections), und einTokenizerverwaltet die Regel- und Gültigkeitsbereichsstapel zeilenweise. - Theme (
Shikiphp\Theme) portiert das Theme-Matching vonvscode-textmate: Vordergrundfarbe und Schriftstil werden anhand der Spezifität des Scope-Selektors mit Abgleich übergeordneter Scopes aufgelöst.
Rendering
Shikiphp\Render\HtmlRenderer erstellt genau denselben pre.shiki > code > span.line > span-HAST-Baum wie Shiki und serialisiert ihn anschließend. Im Single-Theme-Modus werden einfache Farben ausgegeben, im Dual-Theme-Modus --shiki-light / --shiki-dark-CSS-Variablen.
Gegen Shiki validiert
Shiki selbst dient als Referenz und ist auf shiki 3.23.0 mit seiner JavaScript-Regex-Engine festgelegt. Das Verzeichnis scenarios/ enthält 214 Testfälle, jeweils eine Eingabe in einer Sprache und einem Theme. Zusammen decken sie 167 der 253 mitgelieferten Grammatiken, lang: 'ansi' und alle 65 Themes ab. bin/test-regression hebt jeden Testfall mit beiden Engines hervor und prüft, dass HTML nach dem Entfernen führender und nachgestellter Leerzeichen identisch ist. Optionen und Transformer werden mit Paritätstests anhand von Ausgaben geprüft, die mit denselben Versionen von Shiki und @shikijs/transformers 4.2.0 erfasst wurden. Die unverrückbare Regel lautet: Die Hervorhebungsausgabe darf sich gegenüber Shiki nicht verschlechtern.