Pular para o conteúdo

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

Código
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 (\b e \B como 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 \G també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 de vscode-textmate: um Registry resolve as gramáticas pelo nome do escopo (incluindo referências include, idiomas incorporados e injeções), e um Tokenizer mantém as pilhas de regras e de escopos linha por linha.
  • Tema (Shikiphp\Theme) adapta a correspondência de temas de vscode-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.