在上一篇译文中,我们已经跑通了 Monaco Editor 的项目,接下来我们来具体看下,如何配置自定义语言高亮。
Monaco Editor 通过自带的语法高亮库 Monarch 来支持配置自定义语言。通过它,即可使用 JSON 创建声明式语法支持高亮。我们可以通过 monarch 提供 playground 来在线调试我们的自定义语言。
语言定义基本上只是描述语言的各种属性的 JSON 值。
开始
Monaco 通过 setMonarchTokensProvider(languageId, monarchConfig) 函数来定义语言的高亮功能,它的参数有两个,一个是自定义语言设定的 ID,一个就是上述的 JSON 配置项了。
我们先来看个例子:
{tokenizer: {root:[[/\d+/,{token:"keyword"}],[/[a-z]+/,{token:"string"}]],}}
我们在 tokenizer 中定义了一个 root 属性,root 是 tokenizer 中的一个 state , 这就是我们用来编写解析规则(rule)的地方,在 rule 中,我们可以编写匹配文本的正则表达式,然后再给匹配到的文本设置一个执行动作的 action ,在 action 中,我们可以给匹配到的文本设置 token class 。
在我们的例子中,我们在 root 中设置了两个 rule ,分别用来匹配数字和字母,匹配成功后就接着执行对应的 action ,最后在 action 中,我们设置了匹配文本的 token class :keyword 和 string。最终效果如图:

Monarch 中内置了以下几种 token class:
identifier entity constructoroperators tag namespacekeyword info-token typestring warn-token predefinedstring.escape error-token invalidcomment debug-tokencomment.doc regexpconstant attributedelimiter .[curly,square,parenthesis,angle,array,bracket]number .[hex,octal,binary,float]variable .[name,value]meta .[content]
可以看到上面的高亮还有问题,大写的 TEST 没有被识别出来,这时,我们可以再给完善以下匹配字符串的 rule 正则表达式。
tokenizer: {root:[[/\d+/,{token:"keyword"}],[/[a-zA-Z]+/,{token:"string"}]],}
假如我们的语言是忽略大小写的,那么,我们可以直接添加一条 ignoreCase 属性。
{ignoreCase: true,tokenizer: {root:[[/\d+/, {token: "keyword"}],[/[a-z]+/, {token: "string"}]],}}
我们再来看下高亮效果:

Monarch 使用创建声明式 JSON
通过上述示例,我们可以看到大致的配置了,接下来具体看下 monarch 的相关用法。
该库允许你使用 JSON 值来指定高效的语法突出显示工具。该规范具有足够的表现力,可以指定具有复杂状态转换,动态花括号匹配,自动完成,其他语言嵌入等功能的高亮。
创建语言定义
语言定义基本上只是描述语言的各种属性的 JSON 值,一些默认的属性有:
ignoreCase :语言是否区分大小写?默认为 true 。
defaultToken :如果 token 生成器中没有匹配项,则返回默认 token。
brackets :token 生成器使用定义匹配的括号。每个括号定义是一个由3个元素或对象组成的数组,用于描述 open括号,close 括号和 token 类。默认定义是:
[[''','}','delimiter.curly'],['[',']','delimiter.square'],['(',')','delimiter.parenthesis'],['<','>','delimiter.angle']]
tokenizer : 必选项,这定义了标记化规则。
创建一个 tokenizer
tokenizer 属性描述了词法分析是如何进行的,以及如何将输入划分为 token 。每个标记都有一个 CSS 类名称,该名称用于在编辑器中呈现每个 token。标准 CSS token 类包括:
identifier entity constructoroperators tag namespacekeyword info-token typestring warn-token predefinedstring.escape error-token invalidcomment debug-tokencomment.doc regexpconstant attributedelimiter .[curly,square,parenthesis,angle,array,bracket]number .[hex,octal,binary,float]variable .[name,value]meta .[content]
状态
分词器由定义状态的对象组成。令牌生成器的初始状态是令牌生成器中定义的第一个状态。当令牌生成器处于特定状态时,将仅应用该状态中的规则。所有规则都按顺序匹配,并且当第一个匹配时,将使用其操作来确定令牌类。不会尝试进一步的规则。因此,以最高效的方式对规则进行排序很重要,即首先使用空格和标识符。
规则
每个状态都定义为一组规则,用于匹配输入。规则可以采用以下形式:
[regex, action][regex, action, next]可以简写为{ regex: regex, action: action{next: next} }{regex: regex, action: action }{ include: state }
当 regex 与当前输入匹配时,则 action 设置的令牌类作用于该输入。正则表达式 regex 可以是正则表达式(使用),也可以是表示正则表达式的字符串。如果以字符开头,则表达式仅在源代码行的开头匹配。请注意,当行尾已经到达时,不会调用令牌生成器,因此,空模式 /$/ 将永远不会匹配。
include 是为了更好地组织规则,并引入定义的所有规则 state。
Actions
actions 确定结果标记类。可以具有以下形式:
string[action1,...,actionN]{ token: tokenclass }@brackets或者@brackets.tokenclass
一个 action 对象可以包含更多影响词法分析器状态的字段。可以识别以下属性:
next : state,(字符串)如果已定义,则将当前状态压入令牌生成器堆栈并生成当前状态 state 。例如,这可以用于标记开始块注释:
['/ \\ *','comment','@ comment']
请注意这是以下的简写:
{ regex: '/\\*', action: { token: 'comment', next: '@comment' } }
有一些特殊状态可用于该 next 属性:
@pop:使令牌生成器堆栈返回到先前的状态。例如,这在用于看到结束 token 后从块注释 token 返回:
['\\*/', 'comment', '@pop']
@push: 推入当前状态并继续当前状态。在看到注释开始 token 时执行嵌套的块注释,即在 @comment 状态下,我们可以执行以下操作:
['/\\*', 'comment', '@push']
@popall: 从令牌生成器堆栈中推出所有内容,并返回到栈顶状态。可以在恢复期间使用它,以从深度嵌套级别“跳回”到初始状态。
log : 用于调试。登录 message 到浏览器中的控制台窗口(按F12进行查看)。
[/\d+/, { token: 'number', log: 'found number $0 in state $S0' } ]
cases
{ cases: { guard1: action1, ..., guardN: actionN } }
最后一种操作对象是 case 语句。case 对象包含一个对象,其中每个字段均用作条件选择。将每个 guard 应用于匹配的输入,并且一旦其中一个匹配,就会应用相应的 action 操作。注意,由于这些本身就是 action,因此 case 可以嵌套。使用 case 来提高效率:例如,我们匹配标识符,然后测试标识符是否可能是关键字或内置函数:
[/[a-z_\$][a-zA-Z0-9_\$]*/,{ cases: { '@typeKeywords': 'keyword.type', '@keywords': 'keyword', '@default': 'identifier' }}]
guard 可以包括:
- @keywords 该属性 keywords 必须提前在语言对象中定义,并且由字符串数组组成。如果输入匹配到任何字符串,则条件判断成功。
- @default 始终成功的匹配 “@” 或 “”
- @eos 如果匹配的输入已到达行尾
- regex 如果不是以@(或$)字符开头,则将其解释为对匹配输入进行测试的正则表达式。例如,这可以用于测试特定的输入,这是 Koka 语言的示例,
[/[a-z](\w|\-[a-zA-Z])*/,{ cases:{ '@keywords': {cases: { 'alias' : { token: 'keyword', next: '@alias-type' }, 'struct' : { token: 'keyword', next: '@struct-type' }, 'type|cotype|rectype': { token: 'keyword', next: '@type' }, 'module|as|import' : { token: 'keyword', next: '@module' }, '@default' : 'keyword' }}, '@builtins': 'predefined', '@default' : 'identifier' }}]
请注意可以使用嵌套 case 来提高效率。此外,该库可识别上述简单正则表达式,并有效地对其进行编译。
理解上述定义之后,已基本掌握配置自定义语言高亮的写法了,接下来我们来解读下官方示例
Monaco 官方示例
{// 为了插件尚未被 token 解析的内容,设置 defaultToken 为 invaliddefaultToken: 'invalid',// 关键字定义keywords: ['abstract', 'continue', 'for', 'new', 'switch', 'assert', 'goto', 'do','if', 'private', 'this', 'break', 'protected', 'throw', 'else', 'public','enum', 'return', 'catch', 'try', 'interface', 'static', 'class','finally', 'const', 'super', 'while', 'true', 'false'],// 类型定义typeKeywords: ['boolean', 'double', 'byte', 'int', 'short', 'char', 'void', 'long', 'float'],// 操作符定义operators: ['=', '>', '<', '!', '~', '?', ':', '==', '<=', '>=', '!=','&&', '||', '++', '--', '+', '-', '*', '/', '&', '|', '^', '%','<<', '>>', '>>>', '+=', '-=', '*=', '/=', '&=', '|=', '^=','%=', '<<=', '>>=', '>>>='],// 定义常见的正则表达式symbols: /[=><!~?:&|+\-*\/\^%]+/,// C# 样式字符串escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,// 语言的主要 token 生成器tokenizer: {root: [// 标识符与关键字[/[a-z_$][\w$]*/, { cases: { '@typeKeywords': 'keyword','@keywords': 'keyword','@default': 'identifier' } }],[/[A-Z][\w\$]*/, 'type.identifier' ], // to show class names nicely// 空格{ include: '@whitespace' },// 括号与运算符[/[{}()\[\]]/, '@brackets'],[/[<>](?!@symbols)/, '@brackets'],[/@symbols/, { cases: { '@operators': 'operator','@default' : '' } } ],// @ 注释.// 作为示例,我们在这些 token 上发出调试日志消息[/@\s*[a-zA-Z_\$][\w\$]*/, { token: 'annotation', log: 'annotation token: $0' }],// 各类数字定义[/\d*\.\d+([eE][\-+]?\d+)?/, 'number.float'],[/0[xX][0-9a-fA-F]+/, 'number.hex'],[/\d+/, 'number'],// 分隔符[/[;,.]/, 'delimiter'],// 字符串定义[/"([^"\\]|\\.)*$/, 'string.invalid' ], // non-teminated string[/"/, { token: 'string.quote', bracket: '@open', next: '@string' } ],[/'[^\\']'/, 'string'],[/(')(@escapes)(')/, ['string','string.escape','string']],[/'/, 'string.invalid']],// 自定义规则 - 备注comment: [[/[^\/*]+/, 'comment' ],[/\/\*/, 'comment', '@push' ], // nested comment["\\*/", 'comment', '@pop' ],[/[\/*]/, 'comment' ]],// 自定义规则 - 字符串string: [[/[^\\"]+/, 'string'],[/@escapes/, 'string.escape'],[/\\./, 'string.escape.invalid'],[/"/, { token: 'string.quote', bracket: '@close', next: '@pop' } ]],// 自定义规则 - 空格whitespace: [[/[ \t\r\n]+/, 'white'],[/\/\*/, 'comment', '@comment' ],[/\/\/.*$/, 'comment'],],},};
通过以上的学习与理解,一个自定义语言的配置我们已能配置出来了。
相关参考
monarch playgroud: https://microsoft.github.io/monaco-editor/monarch.html
