语言指南
概述
编程语言代码由具有不同解析规则的部分组成:关键字像for
或if
在字符串中没有意义,字符串可能包含反斜杠转义符号,例如\"
,除了注释的结尾,注释通常不包含任何有趣的东西。
在 Highlight.js
中,这些部分称为“modes”。
每种模式包括:
- 起始条件
- 结束条件
- 包含的子模式列表
- 词法规则和关键字
- ...本地化,比如语言中的另一种语言
解析器的工作是寻找模式及其关键字。找到它们后,它将它们包装到标记中 并放入模式名称(“字符串”、“评论”、“数字”)或关键字组名称(“关键字”、“文字”、“内置”)作为跨度的类名。<span class="...">...</span>
语法
语言定义是描述语言默认解析模式的 J
avaScript` 对象。此默认模式包含子模式,而子模式又包含其他子模式,从而有效地使语言定义成为模式树。
示例:
{
case_insensitive: true, // language is case-insensitive
keywords: 'for if while',
contains: [
{
scope: 'string',
begin: '"', end: '"'
},
hljs.COMMENT(
'/\\*', // begin
'\\*/', // end
{
contains: [
{
scope: 'doc', begin: '@\\w+'
}
]
}
)
]
}
通常默认模式占代码的大部分,并描述了所有语言关键字。一个值得注意的例外是 XML
,其中默认模式只是不包含任何关键字的用户文本,最有趣的解析发生在标签内。
关键词
在简单的情况下,语言关键字可以用字符串(空格分隔)或数组定义:
{
keywords: 'else for if while',
// or with an array
keywords: ['else', 'for', 'if', 'while']
}
一些语言有不同类型的“关键字”,语言规范可能不会这样称呼它们,但从语法高亮显示的角度来看,它们非常接近。这些是各种“文字”、“内置”、“符号”等。为了定义这样的关键字组,属性 keywords
变成了一个对象,它的每个属性都定义了自己的一组关键字:
{
keywords: {
keyword: 'else for if while',
literal: ['false','true','null'],
_relevance_only: 'one two three four'
}
}
组名成为生成标记中的类名,为不同类型的关键字启用不同的主题。任何以 a 开头的属性_都只会使用这些关键字来增加相关性,它们不会被高亮显示。
为了检测关键字,highlight.js
将处理过的代码块分成单独的单词——这个过程称为词法分析。默认情况下,“单词”与正则表达式匹配 \w+
,这适用于许多语言。不同的词法规则可以由magic $pattern
属性定义:
{
keywords: {
$pattern: /-[a-z]+/, // allow keywords to begin with dash
keyword: '-import -export'
}
}
旧的 lexemes
设置已被弃用,取而代之的是使用 keywords.$pattern
. 它们在功能上是相同的。
子模式
子模式在 contains
属性中列出:
{
keywords: '...',
contains: [
hljs.QUOTE_STRING_MODE,
hljs.C_LINE_COMMENT,
{ ... custom mode definition ... }
]
}
contains
模式可以通过使用特殊关键字 'self'
在数组中引用自身。这通常用于定义嵌套模式:
{
scope: 'object',
begin: /\{/, end: /\}/,
contains: [hljs.QUOTE_STRING_MODE, 'self']
}
self
不能用于 contains
语言的根级别。根级模式是特殊的,可能不是自引用的。
注释
要定义自定义注释,建议使用内置辅助函数 hljs.COMMENT
而不是直接描述模式,因为它还定义了一些默认子模式,可以改进语言检测并做其他好事。
该函数的参数是:
hljs.COMMENT(
begin, // begin regex
end, // end regex
extra // optional object with extra attributes to override defaults
// (for example {relevance: 0})
)
标记生成
模式通常会生成实际的高亮标记——具有由属性 <span>
定义的特定类名的元素:scope
{
contains: [
{
scope: 'string',
// ... other attributes
},
{
scope: 'number',
// ...
}
]
}
范围不需要是唯一的;具有相同范围的多个定义是很常见的。例如,许多语言对字符串、注释等有不同的语法……
有时模式仅被定义为支持特定的解析规则,并且在最终标记中不需要。一个经典的例子是字符串中的转义序列,允许它们包含结束引号。
{
scope: 'string',
begin: '"',
end: '"',
contains: [{begin: '\\\\.'}],
}
对于此类模式,scope
应省略该属性,以免它们生成过多的标记。
有关所有受支持范围名称的列表,请参阅 范围参考。
模式属性
其他有用的属性在 模式参考 中定义。
关联
Highlight.js
尝试自动检测代码片段的语言。启发式方法本质上很简单:它尝试高亮显示具有所有语言定义的片段,并且产生最特定模式和关键字的片段获胜。语言定义的工作是通过暗示模式的相对相关性(或不相关性)来帮助这种启发式方法。
这最好通过例子来说明。Python
有特殊类型的字符串,由引号前的前缀字母定义: r"..."
, u"..."
. 如果一个代码片段包含这样的字符串,那么它很可能是在 Python
中。所以这些字符串模式被赋予了高度相关性:
{
scope: 'string',
begin: 'r"',
end: '"',
relevance: 10
}
另一方面,普通单引号或双引号中的传统字符串并不特定于任何语言,将它们的相关性设为零以减少统计噪音是有意义的:
{
scope: 'string',
begin: '"',
end: '"',
relevance: 0
}
相关性的默认值始终为 1。设置显式值时,通常使用 10 或 0。0 表示不应将此匹配项考虑用于语言检测目的。0 应该用于可能在任何语言(基本数字、字符串等)中找到的非常常见的匹配项,或者会产生太多误报的内容。10 表示“这几乎可以保证是 XYZ 代码”。10 应谨慎使用。
关键字也会影响相关性。它们中的每一个通常具有 1 的相关性,但有一些独特的名称不太可能在其语言之外找到,即使以变量名称的形式也是如此。例如 reinterpret_cast
,代码中的某个位置是我们正在研究 C++ 的一个很好的指标。将此类关键字的相关性设置得更高一点是值得的。这是通过管道完成的:
{
keywords: 'for if reinterpret_cast|10'
}
非法符号
改进语言检测的另一种方法是为模式定义非法符号。例如,在 Python 中,类定义 ( ) 的第一行不能包含符号或换行符。这些符号的存在清楚地表明该语言不是 Python,解析器可以提前放弃这种尝试。class MyClass(object):{
使用单个正则表达式定义非法符号:
{
scope: 'class',
illegal: '[${]'
}
预定义模式和正则表达式
许多语言共享通用模式和正则表达式。
正则表达式
Highlight.js
的目标是支持我们支持的 JavaScript
运行时普遍支持的任何正则表达式功能。您正在使用真正的正则表达式,负责任地使用它们。也就是说,由于解析器的设计,有一些警告。这些将在下面讨论。
我们现在完全支持但我们并不总是支持的事情:
- begin的前瞻正则表达式匹配(#2135)
- end的前瞻正则表达式匹配(#2237)
- 非法的前瞻正则表达式匹配(#2135)
- 正则表达式匹配中的反向引用 (#1897)
技术上可行的事情,但我们不允许(因为 Safari 不支持):
- begin的后视匹配(#2135)
由于解析引擎本身的问题而不受支持的事情:
- 端匹配器的后视匹配