Skip to main content

语言指南

概述

编程语言代码由具有不同解析规则的部分组成:关键字像forif在字符串中没有意义,字符串可能包含反斜杠转义符号,例如\",除了注释的结尾,注释通常不包含任何有趣的东西。

Highlight.js 中,这些部分称为“modes”。

每种模式包括:

  • 起始条件
  • 结束条件
  • 包含的子模式列表
  • 词法规则和关键字
  • ...本地化,比如语言中的另一种语言

解析器的工作是寻找模式及其关键字。找到它们后,它将它们包装到标记中 并放入模式名称(“字符串”、“评论”、“数字”)或关键字组名称(“关键字”、“文字”、“内置”)作为跨度的类名。<span class="...">...</span>

语法

语言定义是描述语言默认解析模式的 JavaScript` 对象。此默认模式包含子模式,而子模式又包含其他子模式,从而有效地使语言定义成为模式树。

示例:

{
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)

由于解析引擎本身的问题而不受支持的事情:

  • 端匹配器的后视匹配