Architettura
shikiphp trasforma il codice sorgente in HTML evidenziato con temi, usando grammatiche TextMate e temi VS Code, senza estensioni PHP oltre a json/mbstring, senza runtime Node e senza binding nativo Oniguruma. Questa pagina riassume la pipeline; il contratto normativo completo è in ARCHITECTURE.md nel repository.
La 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>
Tutte le operazioni usano offset in unità di codice UTF-16 — lo stesso spazio del motore regex JS e del tokenizer di Shiki. Per ASCII equivale agli offset in byte.
Il runtime regex
Alla base c’è Shikiphp\Regex, un motore regex ECMAScript completo e puro-PHP, integrato da inline0/phasis (un motore PHP JavaScript). Analizza una sorgente RegExp JS con i flag ed esegue ricerche in avanti con la semantica Oniguruma onig_search.
Conversione da Oniguruma a JS
Le grammatiche TextMate sono scritte in Oniguruma. Shikiphp\Oniguruma\PatternConverter è un porting PHP di oniguruma-to-es: converte un pattern Oniguruma in una sorgente e una stringa di flag compatibili con JS-RegExp, accettate dal motore regex. È lo stesso percorso Oniguruma → RegExp usato dal motore regex di ShikiJavaScript, che consente a shikiphp di corrispondere al suo output offset per offset. Il convertitore gestisce i costrutti usati dalle grammatiche nel mondo reale: classi POSIX, quantificatori possessivi e gruppi atomici, flag inline, gruppi con nome e riferimenti all’indietro, proprietà Unicode e altro.
Lo scanner e il suo percorso rapido PCRE
Shikiphp\Oniguruma\OnigScanner rispecchia lo scanner di vscode-oniguruma: viene costruito una volta per regola a partire da un elenco di pattern e trova la corrispondenza più a sinistra in corrispondenza o dopo una posizione iniziale.
Il matcher che percorre l’albero è corretto, ma lento; perciò lo scanner dispone di un percorso rapido PCRE con equivalenza verificata, articolato in due livelli. Un PcreTranslator riscrive la sorgente JS del convertitore come pattern PCRE nativo solo quando può dimostrare che il risultato:
- Corrispondenza completa. Se la traduzione si comporta in modo identico al matcher, incluse tutte le catture, il pattern viene eseguito direttamente in PCRE. Gruppi con nome, riferimenti all’indietro,
\p{},\b,\G, emulazione dei gruppi atomici e catture la cui semantica JavaScript differisce da PCRE (all’interno di un gruppo quantificato o di un lookbehind) vengono esclusi da questo livello. - Solo posizione. Se possono differire solo le catture, una seconda traduzione ammette questi costrutti (
\be\Bcome lookaround ASCII esatti, gruppi atomici come(?>…)nativi,\p{…}se PCRE riesce a compilarlo). PCRE trova solo la posizione della corrispondenza successiva; il matcher viene poi rieseguito a quell’offset per produrre le catture effettive. Anche i riferimenti all’indietro e\Grestano esclusi.
Tutto il resto viene eseguito solo dal matcher. Nelle grammatiche incluse a Shiki 3.23.0, circa il 39% dei 14,234 pattern distinti viene eseguito interamente in PCRE, circa il 57% usa il livello di sola posizione e circa il 4% resta al matcher. La classificazione viene verificata da uno strumento di sviluppo nel repository (bin/.oracle-tools/pcre-equivalence.php), che confronta il percorso rapido con il matcher — indice, fine e, per il primo livello, intervallo di ogni cattura — sugli input degli scenari e su un insieme di stringhe sintetiche, senza ammettere alcuna divergenza.
Grammatica e tema
- Grammatica (
Shikiphp\Grammar) è un port fedele divscode-textmate: unRegistryrisolve le grammatiche in base al nome dello scope (incluse le referenzeinclude, i linguaggi incorporati e le iniezioni), mentre unTokenizermantiene gli stack delle regole e degli scope riga per riga. - Tema (
Shikiphp\Theme) porta il matching dei temi divscode-textmate: il colore di primo piano e lo stile del font vengono risolti in base alla specificità del selettore di scope e alla corrispondenza con gli scope genitori.
Rendering
Shikiphp\Render\HtmlRenderer costruisce esattamente lo stesso albero HAST pre.shiki > code > span.line > span di Shiki, quindi lo serializza. La modalità con un solo tema scrive colori semplici; quella con due temi scrive le variabili CSS --shiki-light / --shiki-dark.
Convalidato rispetto a Shiki
Shiki stesso è il riferimento oracle, fissato a shiki 3.23.0 con il suo motore regex JavaScript. La directory scenarios/ contiene 214 fixture, ciascuna con un input, un linguaggio e un tema; nel complesso coprono 167 delle 253 grammatiche incluse, lang: 'ansi' e tutti i 65 temi. bin/test-regression esegue l’evidenziazione di ciascuna con entrambi i motori e verifica che HTML sia identico dopo aver rimosso gli spazi iniziali e finali. Opzioni e transformer vengono verificati con test di parità rispetto all’output acquisito dallo stesso Shiki e @shikijs/transformers 4.2.0. Il requisito inderogabile è che l’output dell’evidenziazione non peggiori rispetto a Shiki.