Arquitectura
shikiphp convierte código fuente en HTML resaltado con temas, mediante gramáticas TextMate y temas VS Code, sin extensiones de PHP aparte de json/mbstring, sin entorno de ejecución de Node ni enlace nativo de Oniguruma. Esta página resume el proceso; el contrato normativo completo está en ARCHITECTURE.md del repositorio.
El proceso
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>
Todo funciona en el espacio de desplazamientos de unidades de código UTF-16, el mismo que el del motor de expresiones regulares JS y el tokenizador de Shiki. Para ASCII, equivale a los desplazamientos en bytes.
El entorno de ejecución de expresiones regulares
En la base está Shikiphp\Regex, un motor completo de expresiones regulares ECMAScript puro-PHP incluido desde inline0/phasis (un motor JavaScript puro-PHP). Analiza un patrón de RegExp de JS con sus indicadores y ejecuta búsquedas hacia delante con la semántica de Oniguruma onig_search.
Conversión de Oniguruma → JS
Las gramáticas TextMate están escritas en Oniguruma. Shikiphp\Oniguruma\PatternConverter
es una adaptación de PHP de
oniguruma-to-es: convierte un patrón Oniguruma en una cadena de origen y de indicadores compatible con RegExp de JS que acepta el motor de expresiones regulares. Esta es la misma ruta Oniguruma → RegExp que utiliza el motor de expresiones regulares de Shiki JavaScript, lo que permite que shikiphp coincida con el resultado de ese motor desplazamiento por desplazamiento. El conversor admite las construcciones habituales de las gramáticas: clases POSIX, cuantificadores posesivos y grupos atómicos, indicadores en línea, grupos con nombre y referencias inversas, propiedades Unicode, entre otras.
El analizador y su ruta rápida de PCRE
Shikiphp\Oniguruma\OnigScanner refleja el analizador de vscode-oniguruma: se crea
una vez por regla a partir de una lista de patrones y busca la coincidencia más a la izquierda a partir de una posición inicial.
El comparador que recorre el árbol es correcto, pero lento, así que el analizador tiene una ruta rápida de PCRE condicionada a la equivalencia con dos niveles. Un PcreTranslator reescribe
el patrón de origen JS del conversor como un patrón PCRE nativo solo cuando puede demostrar que el resultado:
- Coincidencia completa. Cuando la traducción se comporta igual que el comparador,
incluidas todas las capturas, el patrón se ejecuta directamente en PCRE. Se excluyen de este nivel los grupos con nombre,
las referencias inversas,
\p{},\b,\G, la emulación de grupos atómicos y las capturas cuya semántica de JavaScript difiere de la de PCRE (dentro de un grupo cuantificado o una aserción hacia atrás). - Solo posición. Cuando solo podrían diferir las capturas, una segunda traducción
admite esas construcciones (
\by\Bcomo aserciones de anticipación y retroceso ASCII exactas, grupos atómicos como(?>…)nativo,\p{…}si PCRE lo compila). PCRE solo encuentra dónde está la siguiente coincidencia; el comparador vuelve a ejecutarse en ese desplazamiento para generar las capturas reales. Las referencias inversas y\Gtambién se excluyen aquí.
Todo lo demás se ejecuta únicamente en el comparador. Para las gramáticas incluidas en Shiki 3.23.0, alrededor del 39 % de los patrones distintos de 14,234 se ejecuta completamente en PCRE, cerca del 57 % usa el nivel de posición y alrededor del 4 % permanece en el comparador. Una herramienta de desarrollo del repositorio (bin/.oracle-tools/pcre-equivalence.php) verifica esta clasificación comparando la ruta rápida con el comparador —índice, final y, para el primer nivel, cada intervalo de captura— en las entradas de los escenarios y en un conjunto de cadenas sintéticas, sin permitir divergencias.
Gramática y tema
- Gramática (
Shikiphp\Grammar) es una adaptación fiel devscode-textmate: unRegistryresuelve gramáticas por nombre de ámbito (incluidas referencias ainclude, lenguajes incrustados e inyecciones), y unTokenizermantiene las pilas de reglas y ámbitos línea por línea. - Tema (
Shikiphp\Theme) adapta la correspondencia de temas devscode-textmate: los colores de primer plano y los estilos de fuente se resuelven según la especificidad del selector de ámbito y la correspondencia con los ámbitos superiores.
Renderizado
Shikiphp\Render\HtmlRenderer construye el árbol HAST exacto que genera pre.shiki > code > span.line > span Shiki y luego lo serializa. El modo de tema único escribe colores sencillos; el modo de dos temas escribe variables CSS --shiki-light/--shiki-dark.
Validado frente a Shiki
Shiki es la referencia y está fijado a shiki 3.23.0 con su motor de expresiones regulares JavaScript. El directorio scenarios/ contiene 214 casos de prueba, cada uno con una entrada, un idioma y un tema; en conjunto, abarcan 167 de las 253 gramáticas incluidas, lang: 'ansi' y los 65 temas. bin/test-regression resalta cada caso con ambos motores y comprueba que la cadena HTML sea idéntica tras quitar los espacios en blanco iniciales y finales. Las opciones y los transformadores se comprueban con pruebas de paridad frente a resultados capturados de las mismas versiones de Shiki y @shikijs/transformers 4.2.0.
La regla innegociable es que el resultado del resaltado no debe empeorar respecto a Shiki.