Arquitetura
shikiphp transforma código-fonte em HTML destacados com tema usando gramáticas TextMate e temas VS Code, sem extensões PHP além de json/mbstring, sem runtime Node e sem vinculação nativa de Oniguruma. Esta página resume o pipeline; o contrato normativo completo está em ARCHITECTURE.md no repositório.
O 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>
Tudo opera no espaço de deslocamentos de unidades de código UTF-16 — o mesmo do mecanismo de regex JS e do tokenizador de Shiki. Para ASCII, ele equivale aos deslocamentos em bytes.
O runtime de regex
Na base está Shikiphp\Regex, um mecanismo completo de regex ECMAScript puro PHP, distribuído a partir de inline0/phasis (um mecanismo PHP JavaScript puro). Ele analisa uma origem de RegExp JS mais flags e executa buscas para a frente com a semântica de Oniguruma onig_search.
Conversão de Oniguruma → JS
As gramáticas TextMate são escritas em Oniguruma. Shikiphp\Oniguruma\PatternConverter é uma adaptação PHP de oniguruma-to-es: converte um padrão Oniguruma em uma origem compatível com RegExp JS e em uma string de flags aceita pelo mecanismo de regex. Este é o mesmo caminho Oniguruma → RegExp usado pelo mecanismo de regex de Shiki JavaScript, o que permite a shikiphp corresponder à saída desse mecanismo deslocamento por deslocamento. O conversor lida com as construções usadas por gramáticas reais: classes POSIX, quantificadores possessivos e grupos atômicos, flags em linha, grupos nomeados e referências retroativas, propriedades Unicode e muito mais.
O scanner e seu caminho rápido PCRE
Shikiphp\Oniguruma\OnigScanner espelha o scanner de vscode-oniguruma: é criado uma vez por regra a partir de uma lista de padrões e encontra a correspondência mais à esquerda na posição inicial ou depois dela.
O matcher que percorre a árvore está correto, mas é lento; por isso, o scanner tem um caminho rápido PCRE condicionado à equivalência, com duas camadas. Um PcreTranslator reescreve a origem JS do conversor como um padrão PCRE nativo somente quando pode provar o resultado:
- Correspondência completa. Quando a tradução se comporta de modo idêntico ao matcher, incluindo todas as capturas, o padrão é executado diretamente no PCRE. Grupos nomeados, referências retroativas,
\p{},\b,\G, emulação de grupos atômicos e capturas cujas semânticas de JavaScript diferem do PCRE (dentro de um grupo quantificado ou de um lookbehind) são rejeitados nesta camada. - Somente posição. Quando apenas as capturas podem diferir, uma segunda tradução permite essas construções (
\be\Bcomo lookarounds ASCII exatos, grupos atômicos como(?>…)nativo,\p{…}se o PCRE compilar). O PCRE então encontra somente a posição da próxima correspondência, e o matcher executa novamente nesse deslocamento para produzir as capturas reais. Referências retroativas e\Gtambém são rejeitadas aqui.
Todo o restante é executado somente pelo matcher. Para as gramáticas incluídas no pacote em Shiki 3.23.0, cerca de 39% dos 14,234 padrões distintos são executados integralmente no PCRE, cerca de 57% usam a camada de posição e cerca de 4% ficam no matcher. Uma ferramenta de desenvolvimento no repositório (bin/.oracle-tools/pcre-equivalence.php) verifica a classificação comparando o caminho rápido com o matcher — índice, fim e, na primeira camada, cada intervalo de captura — nos cenários de entrada e em um conjunto de strings sintéticas, sem permitir divergências.
Gramática e tema
- Gramática (
Shikiphp\Grammar) é uma adaptação fiel devscode-textmate: umRegistryresolve as gramáticas pelo nome do escopo (incluindo referênciasinclude, idiomas incorporados e injeções), e umTokenizermantém as pilhas de regras e de escopos linha por linha. - Tema (
Shikiphp\Theme) adapta a correspondência de temas devscode-textmate: as cores de primeiro plano e os estilos de fonte são resolvidos pela especificidade do seletor de escopo, com correspondência de escopos pai.
Renderização
Shikiphp\Render\HtmlRenderer cria exatamente a árvore HAST pre.shiki > code > span.line > span que Shiki cria e depois a serializa. No modo de tema único, grava cores simples; no modo de tema duplo, grava variáveis --shiki-light / --shiki-dark CSS.
Validado em relação a Shiki
O próprio Shiki é a referência, fixada na versão shiki 3.23.0 com seu mecanismo de regex JavaScript. O diretório scenarios/ contém 214 fixtures, cada uma com uma entrada em um idioma e tema; juntas, elas abrangem 167 das 253 gramáticas incluídas no pacote, lang: 'ansi' e os 65 temas. bin/test-regression destaca cada uma com ambos os mecanismos e verifica que a HTML é idêntica após remover espaços em branco no início e no fim. As opções e os transformadores são verificados por testes de paridade com saídas capturadas do mesmo Shiki e @shikijs/transformers 4.2.0. A regra inegociável é que a saída do destaque não pode regredir em relação a Shiki.