架构
shikiphp 使用 TextMate 语法和 VS Code 主题,将源代码转换为带主题样式的高亮 HTML,无需 PHP 扩展(仅需 json/mbstring),无需 Node 运行时,也无需原生 Oniguruma 绑定。本页概述处理流程;完整的规范性约定见仓库中的 ARCHITECTURE.md。
处理流程
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>
所有操作都使用 UTF-16 代码单元 偏移量,与 JS 正则表达式引擎和 Shiki 的分词器使用的偏移空间相同。对于 ASCII,这等同于字节偏移量。
正则表达式运行时
底层是 Shikiphp\Regex,一个完整的纯 PHP ECMAScript 正则表达式引擎,随附自 inline0/phasis(纯 PHP JavaScript 引擎)。它会解析 JS-RegExp 源代码及标志,并使用 Oniguruma onig_search 语义执行向前搜索。
Oniguruma → JS 转换
TextMate 语法以 Oniguruma 编写。Shikiphp\Oniguruma\PatternConverter
是 PHP 移植版本,基于
oniguruma-to-es:它会将
Oniguruma 模式转换为 JS 兼容 RegExp 引擎的源码和标志字符串。该引擎接受这些内容。这与 Oniguruma → RegExp 路径相同,也是 Shiki 的 JavaScript 正则表达式引擎所采用的路径,因此 shikiphp 能够逐偏移量匹配该引擎的输出。转换器
可处理语法中实际使用的构造:POSIX 类、占有量词和原子组、内联标志、命名组和反向引用、Unicode 属性等。
扫描器和 PCRE 快速路径
Shikiphp\Oniguruma\OnigScanner 仿照 vscode-oniguruma 的扫描器实现:每条规则根据一组模式构建一次,并查找起始位置处或之后最靠左的匹配项。
树遍历匹配器是正确的,但速度不快,因此扫描器提供了经等价性验证的 PCRE 快速路径,分为两个层级。PcreTranslator 只有在能证明结果成立时,才会将转换器生成的 JS 源代码重写为原生 PCRE 模式:
- 完整匹配。 当转换后的行为与匹配器完全相同(包括所有捕获组)时,直接使用 PCRE 执行模式。命名组、反向引用、
\p{}、\b、\G、原子组模拟,以及在 JavaScript 语义与 PCRE 不同的捕获组(位于量化组或后顾断言中),均不采用此层级。 - 仅定位。 如果可能不同的只有捕获结果,第二种转换会允许使用这些结构(
\b和\B作为精确的 ASCII 环视断言,原子组作为原生(?>…),\p{…}仅在 PCRE 编译成功时使用)。PCRE 仅负责定位下一处匹配,随后匹配器会在该偏移量重新运行,以生成实际捕获结果。反向引用和\G在此层级中也不予采用。
其他所有情况都仅由匹配器处理。对于位于 Shiki 3.23.0 的捆绑语法,大约 39% 的 14,234 个不同模式完全由 PCRE 执行,大约 57% 使用仅定位层级,约 4% 仅由匹配器处理。仓库中的开发工具 bin/.oracle-tools/pcre-equivalence.php 会检查分类结果,将快速路径与匹配器进行比较;它使用场景输入和一组综合字符串,核对索引、结束位置,以及第一层级中的所有捕获范围,并要求差异为零。
语法和主题
- 语法(
Shikiphp\Grammar)是对vscode-textmate的忠实移植:Registry按作用域名称解析语法(包括include引用、嵌入语言和注入),而Tokenizer会逐行维护规则栈和作用域栈。 - 主题(
Shikiphp\Theme)移植了vscode-textmate的主题匹配方式:前景色和字体样式会根据作用域选择器的特异性及父作用域匹配结果进行解析。
渲染
Shikiphp\Render\HtmlRenderer 会构建与 Shiki 相同的 pre.shiki > code > span.line > span HAST 树,然后将其序列化。单主题模式会写入纯颜色;双主题模式会写入 --shiki-light / --shiki-dark CSS 变量。
已根据 Shiki 验证
Shiki 本身就是基准,固定使用其 shiki 3.23.0 版本及其 JavaScript 正则表达式引擎。scenarios/ 目录包含 214 个测试样例,每个样例都是一种语言和一种主题下的一个输入;它们共同覆盖 253 种捆绑语法中的 167 种、lang: 'ansi' 以及全部 65 种主题。bin/test-regression 会使用两种引擎分别对每个样例进行高亮,并断言去除首尾空格后 HTML 完全相同。选项和转换器则通过一致性测试,与同一 Shiki 和 @shikijs/transformers 4.2.0 捕获的输出进行核对。不可妥协的原则是,高亮输出不得偏离 Shiki。