
tree-sitter 模板语料库测试解析用 included ranges 精确解析模板语言片段【免费下载链接】tree-sitterAn incremental parsing system for programming tools项目地址: https://gitcode.com/gh_mirrors/tr/tree-sitter模板语言如 Ruby 的 ERB、PHP、HTML 内嵌脚本的源文件通常由“模板文本”与“可执行代码片段”交织而成语法解析器无法把整份文件当作单一语言的纯代码来处理。tree-sitter 在 test/fixtures/template_corpus/readme.md 中定义了名为Template Corpus模板语料库的测试机制它通过解析文件中的不连续区间disjoint ranges仅对% ... %定界符之间的代码片段建立语法树而忽略定界符之外的模板内容。读完本文你将掌握模板语料库文件的组织约定、它与普通语料库corpus及错误语料库error corpus的配合方式以及 tree-sitter 在 C/Rust 两层是如何用set_included_ranges完成“只看片段”的增量解析的。模板语料库是什么在 tree-sitter 的测试体系中语料库测试corpus tests是验证语法解析器行为的主要手段每个测试给出输入源码以及期望的语法树以 S-expression 形式表达。模板语料库是这个体系中的一个专门子集其核心定位由仓库内的说明文档原文明确给出This directory contains corpus tests that exercise parsing a set of disjoint ranges within a file. Each of these input files contains source code surrounded by the delimiters%and%. The content outside of these delimiters is meant to be ignored.也就是说模板语料库测试有两个关键特征解析不连续区间一次解析只覆盖文件中的多个互不相交的字节区间而不是整个文件。定界符约定所有模板代码片段都包裹在%与%之间区间之外的模板文本被明确忽略。这与 HTML/ERB 等“嵌入式模板语言”的实际解析需求一一对应div等宿主模板文本不参与目标语言的语法树只有% ... %内的 Ruby 代码才需要被解析。目前仓库中已有的模板语料库文件是 test/fixtures/template_corpus/ruby_templates.txt它用一套 ERB 风格的 Ruby 模板样例来验证这一机制。模板语料库文件的格式约定模板语料库文件的格式与普通语料库一致一个用分隔线标注标题的测试块上半部分是输入源码下半部分是期望的语法树输出中间用分隔线---隔开。以 ruby_templates.txt 为例 Templates with errors div % if notice.present? % p idnotice% notice %/p % end % div h1Foods/h1 div % link_to New food, new_food_path, class: block font-medium % ...文件名为ruby_templates.txt对应 Ruby 语言的模板场景测试标题为 “Templates with errors”说明这份语料刻意包含有语法错误的情况用来检验解析器在模板区间内生成ERROR节点的行为。测试块内同时出现了模板代码片段与% . render partial: ... %这种故意残缺的 Ruby 代码期望输出中对应的正是(ERROR)节点。模板语料库如何接入测试执行模板语料库并非独立运行而是与主语料库、错误语料库一起由test_language_corpus统一驱动。在 crates/cli/src/tests/corpus_test.rs 中可以看到完整的加载逻辑let main_tests parse_tests(corpus_dir).unwrap(); let error_tests parse_tests(error_corpus_file).unwrap_or_default(); let template_tests parse_tests(template_corpus_file).unwrap_or_default(); let mut tests flatten_tests(main_tests, EXAMPLE_INCLUDE.as_ref(), EXAMPLE_EXCLUDE.as_ref()); tests.extend(flatten_tests(error_tests, EXAMPLE_INCLUDE.as_ref(), EXAMPLE_EXCLUDE.as_ref())); tests.extend( flatten_tests(template_tests, EXAMPLE_INCLUDE.as_ref(), EXAMPLE_EXCLUDE.as_ref()) .into_iter() .map(|mut t| { t.template_delimiters Some((%, %)); t }), );这里有几个值得注意的实现细节三个目录/文件的路径分别来自 test/fixtures/error_corpus{language}_errors.txt与 test/fixtures/template_corpus{language}_templates.txt命名规则是“语言名 _errors/_templates”。从模板文件解析出的每个测试都会被打上template_delimiters Some((%, %))标记这意味着模板语料库测试与普通测试的唯一差别就体现在定界符上。测试执行时会对每个测试做两轮验证先做初始解析check_initial_parse再做一轮随机编辑后的增量重解析基于随机种子生成编辑序列编辑后复用旧树重解析最后撤销所有编辑再次重解析并比对期望输出完整链路可见 crates/cli/src/tests/corpus_test.rs。因此模板语料库不仅验证“区间解析是否正确”还验证“区间解析在增量编辑下是否保持一致”。执行这些测试的入口是test_corpus_for_*_language系列测试函数例如 crates/cli/src/tests/corpus_test.rs 中的test_corpus_for_ruby_language它调用test_language_corpus(ruby, ...)时会把{language}_templates.txt一并纳入。set_included_ranges区间解析的底层实现模板语料库“忽略定界符之外内容”的能力最终落在Parser::set_included_ranges这个 API 上。测试代码中的set_included_ranges辅助函数定义于 crates/cli/src/fuzz/corpus_test.rs展示了完整的转换逻辑pub fn set_included_ranges(parser: mut Parser, input: [u8], delimiters: Option(str, str)) { if let Some((start, end)) delimiters { let mut ranges Vec::new(); let mut ix 0; while ix input.len() { let Some(mut start_ix) input[ix..] .windows(2) .position(|win| win start.as_bytes()) else { break; }; start_ix ix start.len(); let end_ix input[start_ix..] .windows(2) .position(|win| win end.as_bytes()) .map_or(input.len(), |ix| start_ix ix); ix end_ix; ranges.push(Range { start_byte: start_ix, end_byte: end_ix, start_point: point_for_offset(input, start_ix), end_point: point_for_offset(input, end_ix), }); } parser.set_included_ranges(ranges).unwrap(); } else { parser.set_included_ranges([]).unwrap(); } }该函数的实际行为是扫描输入字节流用windows(2)逐对查找%的起始偏移再继续向后查找最近的%作为结束偏移从而枚举出所有模板片段区间每个区间同时给出start_byte/end_byte与换算出的行列坐标start_point/end_point通过 point_for_offset 对\n计数得到若某个%缺失如文件末尾未闭合则end_ix回退为input.len()即该区间延伸到文件结尾——这解释了为何模板语料库可以覆盖“未闭合模板”这类错误场景当没有定界符普通语料库测试时传入空区间数组等价于解析整个输入。在 C 核心库中这一 API 的最终落点是ts_parser_set_included_rangeslib/src/parser.c它会将区间配置转发给词法分析器ts_lexer_set_included_ranges之后ts_parser_parse在每次解析时都会依据这些区间裁剪输入流使语法分析器只能“看到”区间内的字节。这一点也可以从 crates/cli/src/tests/parser_test.rs 的test_parsing_with_multiple_included_ranges等测试中印证库本身支持同时传入多个区间并且返回的语法树可以通过included_ranges()查询实际生效的区间。模板语料库在增量解析验证中的作用模板语料库的意义并不局限于“能解析模板片段”。由于 tree-sitter 的核心卖点是增量解析测试框架刻意把模板测试放进与主语料库相同的随机编辑流程中先对原始输入做一次完整解析得到tree对该输入施加一系列随机编辑编辑次数由EDIT_COUNT决定种子为start_seed trial每次编辑后调用perform_edit更新tree与input设置好模板定界符后用parser.parse(input, Some(tree))基于旧树增量重解析得到tree2依次撤销所有编辑再基于tree2增量重解析得到tree3最终将tree3渲染出的 S-expression 与语料库期望输出比对同时用check_changed_ranges校验变更区间没有越界。完整逻辑见 crates/cli/src/tests/corpus_test.rs。这意味着模板语料库同时回归测试了三件事区间裁剪正确性set_included_ranges在经历多轮编辑、撤销后仍能精确找出所有% ... %区间增量重解析一致性旧树被编辑后新树与期望输出完全一致错误恢复对(ERROR)等错误节点的处理在区间解析模式下与整文件解析模式行为一致。此外同一份set_included_ranges与template_delimiters逻辑也被 crates/cli/src/fuzz.rs 的模糊测试fuzz testing复用说明“解析不连续区间”是语法解析器需要长期保持稳定的核心行为之一。如何运行与扩展模板语料库测试运行现有测试模板语料库测试随各语言的 corpus 测试一起执行例如 Ruby 的对应测试函数为test_corpus_for_ruby_languagecrates/cli/src/tests/corpus_test.rs可通过cargo test test_corpus_for_ruby_language运行。语言名与模板文件名的对应关系是language_templates.txt。添加新的模板语料库在 test/fixtures/template_corpus 目录下新增language_templates.txt采用“标题分隔线 输入 --- 期望语法树”的格式即可无需改动任何测试代码测试驱动会自动将其解析并打上%/%定界符标记定界符目前由 crates/cli/src/tests/corpus_test.rs 硬编码。编写模板样例的注意事项区间外内容会被忽略因此样例中可以放心书写宿主模板文本如 HTML 标签区间内代码应尽量覆盖正常语句、表达式、嵌套调用与刻意错误如 ruby_templates.txt 中的% . render ... %以验证错误恢复能力。小结模板语料库是 tree-sitter 测试体系中专门用于验证“不连续区间解析”的一环它以%与%为定界符界定模板片段通过set_included_ranges把宿主模板文本排除在语法树之外并与普通语料库、错误语料库一起经受随机编辑下的增量重解析验证。从 test/fixtures/template_corpus/readme.md 的约定到 crates/cli/src/fuzz/corpus_test.rs 的区间扫描再到 lib/src/parser.c 的 C 层实现这一机制完整覆盖了嵌入式模板语言解析、增量编辑与错误恢复三个核心场景是理解 tree-sitter 区间解析能力最直接的入口。【免费下载链接】tree-sitterAn incremental parsing system for programming tools项目地址: https://gitcode.com/gh_mirrors/tr/tree-sitter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考