Tree-sitter 依靠语言语法库解析对应语言的文本。
在 Emacs 中,语言语法库由一个符号表示。
例如,C 语言语法库对应符号 c,
c 可作为 language 参数传递给 tree-sitter 函数。
Tree-sitter 语言语法库以动态库形式分发。 要在 Emacs 中使用某语言语法库,需确保系统已安装对应动态库。 Emacs 按以下顺序在多个位置查找语言语法库:
treesit-extra-load-path 指定的目录列表中查找;
user-emacs-directory (see 初始化文件) 指定目录下的
tree-sitter 子目录中查找;
在上述每个目录中,Emacs 会查找后缀名为变量
dynamic-library-suffixes 指定后缀的文件。
若 Emacs 无法找到该库或加载出错,会触发
treesit-load-language-error 错误。
该错误信号的数据可能为以下之一:
(not-found error-msg …)表示 Emacs 未找到该语言语法库。
(symbol-error error-msg)表示 Emacs 在库中未找到所有语言语法库都应导出的预期函数。
(version-mismatch error-msg)表示语言语法库版本与 tree-sitter 库版本不兼容。
在以上所有情况中,error-msg 可能会提供失败的详细信息。
如果 language 对应的语言语法库存在且可加载,
该函数返回非 nil。
若 detail 为非 nil,语言可用时返回 (t . nil),
不可用时返回 (nil . data)。
data 为 treesit-load-language-error 的信号数据。
按照惯例,language 对应动态库的文件名为
libtree-sitter-language.ext,
其中 ext 为系统专属的动态库后缀。
同样按照惯例,该库提供的函数名为 tree_sitter_language。
若某语言语法库未遵循此惯例,可在变量
treesit-load-name-override-list 中添加一项:
(language library-base-name function-name)
其中 library-base-name 为动态库文件名的基础名
(通常为 libtree-sitter-language),
function-name 为库提供的函数
(通常为 tree_sitter_language)。例如:
(cool-lang "libtree-sitter-coool" "tree_sitter_cooool")
适用于那些不愿遵守惯例的 “特殊(cool)” 语言。
该函数返回 tree-sitter 库支持的语言语法库应用二进制接口
(ABI) 版本。默认返回库支持的最新 ABI 版本;
若 min-compatible 为非 nil,则返回库仍兼容的最旧 ABI 版本。
语言语法库必须基于 tree-sitter 库支持的新旧版本之间的 ABI 版本编译,
否则 Emacs 无法加载。
该函数返回 Emacs 为 language 加载的语法库的 ABI 版本。
若 language 不可用,函数返回 nil。
语法树是解析器生成的结果。在语法树中,每个节点代表一段文本, 节点间以父子关系相连。例如,若源代码文本为:
1 + 2
其语法树可表示为:
+--------------+
| root "1 + 2" |
+--------------+
|
+--------------------------------+
| expression "1 + 2" |
+--------------------------------+
| | |
+------------+ +--------------+ +------------+
| number "1" | | operator "+" | | number "2" |
+------------+ +--------------+ +------------+
也可以用 S 表达式表示:
(root (expression (number) (operator) (number)))
root、expression、number、operator
这类名称指定了节点的 类型(type)。但并非语法树中所有节点都有类型。
无类型的节点称为 匿名节点(anonymous nodes),有类型的节点称为 具名节点(named nodes)。
匿名节点是固定拼写的标记,包括括号 ‘]’ 等标点符号
以及 return 等关键字。
为便于分析语法树,许多语言语法会为子节点分配 字段名(field names)。
例如,function_definition 节点可能包含 declarator
与 body:
(function_definition declarator: (declaration) body: (compound_statement))
为帮助理解语言语法并调试使用语法树的 Lisp 程序, Emacs 提供 “浏览(explore)” 模式,可实时显示当前缓冲区源代码的语法树。 Emacs 还附带 “查看模式(inspect mode)”,在模式行中显示光标处节点的信息。
该模式会弹出一个窗口,显示当前缓冲区源代码的语法树。 在源代码缓冲区中选中文本时,语法树显示中对应的节点会高亮; 点击语法树中的节点时,源代码缓冲区中对应文本会高亮。
该次要模式在模式行上显示 起始于光标处的节点。 例如,模式行可显示:
parent field: (node (child (...)))
其中 node、child 等为起始于光标处的节点, parent 为 node 的父节点。node 以粗体显示。 field-name 为 node 及其子节点的字段名。
若没有节点起始于光标处(即光标位于某节点中间), 模式行会显示包含光标位置的最早节点及其直接父节点。
该次要模式不会自行创建解析器,
它使用 (treesit-parser-list) (see 使用 Tree-sitter 解析器)
中的第一个解析器。
语言语法库作者会定义编程语言的 语法规则(grammar), 决定解析器如何从程序文本构建具体语法树。 要高效使用语法树,需要查阅对应的 语法文件(grammar file)。
语法文件通常位于语言语法项目仓库中的 grammar.js。 各语言语法库的主页链接可在 tree-sitter 主页上找到。
语法定义使用 JavaScript 编写。
例如,匹配 function_definition 节点的规则可能如下:
function_definition: $ => seq(
$.declaration_specifiers,
field('declarator', $.declaration),
field('body', $.compound_statement)
)
规则由接收单个参数 $ 的函数表示,
$ 代表整个语法。函数本身由其他函数构建:
seq 函数将多个子规则按顺序组合;
field 函数为子节点标注字段名。
若将上述定义写成 巴科斯-诺尔范式(Backus-Naur Form) (BNF) 语法,形式为:
function_definition := <declaration_specifiers> <declaration> <compound_statement>
解析器返回的节点则形如:
(function_definition (declaration_specifier) declarator: (declaration) body: (compound_statement))
以下是语法定义中常见的函数列表, 每个函数接收其他规则作为参数并返回新规则。
seq(rule1, rule2, …)依次匹配各规则。
choice(rule1, rule2, …)匹配参数中的任意一条规则。
repeat(rule)匹配 rule 零次或多次, 类似于正则表达式中的 ‘*’ 运算符。
repeat1(rule)匹配 rule 一次或多次, 类似于正则表达式中的 ‘+’ 运算符。
optional(rule)匹配 rule 零次或一次, 类似于正则表达式中的 ‘?’ 运算符。
field(name, rule)为 rule 匹配的子节点分配字段名 name。
alias(rule, alias)使 rule 匹配的节点在解析器生成的语法树中显示为 alias。例如:
alias(preprocessor_call_exp, call_expression)
会使所有匹配 preprocessor_call_exp 的节点显示为 call_expression。
以下是阅读语言语法时相对次要的语法函数。
token(rule)标记 rule 生成单个叶节点。 即不会生成带有多个独立子节点的父节点,而是合并为单个叶节点。 See 获取节点。
token.immediate(rule)通常语法规则会忽略前置空白符; 该函数使 rule 仅在无前置空白时匹配。
prec(n, rule)为 rule 设置 n 级优先级。
prec.left([n,] rule)标记 rule 为左结合,可指定优先级 n。
prec.right([n,] rule)标记 rule 为右结合,可指定优先级 n。
prec.dynamic(n, rule)功能类似 prec,但优先级在运行时生效。
tree-sitter 项目文档中有 更多关于编写语法规则的内容, 尤其建议阅读 “The Grammar DSL” 一节。