解析是将非结构化输入(如原始文本)转换为结构化数据(如对象或列表)的过程。虽然正则表达式(regex)常用于简单任务,但它们在处理复杂结构时往往变得难以管理。相反,像 tree-sitter 这样的重型库对于许多应用来说可能过于繁重。解析器组合子提供了一种函数式替代方案,允许开发人员通过组合小型、专门的函数来构建复杂的解析逻辑。

这张图片说明了正则表达式在处理 HTML 等复杂结构时变得难以管理的情况。
这种方法的核心是“部分解析器”。部分解析器是一个以字符串作为输入,并返回包含两个元素的元组的函数:它成功提取的数据片段以及字符串的剩余部分。例如,一个旨在识别整数的解析器将扫描字符串的开头,提取数字,并将字符串的其余部分传递给链中的下一个函数。通过“链式调用”这些函数——即一个函数的输出成为下一个函数的输入——开发人员可以逐步消耗整个字符串。这个过程在概念上类似于函数式编程中的“unfold”,它使用中间状态将一件事拆分为许多件事,而“fold”(或 reduce)则是将许多事情合并为一件。
这种方法的真正力量在于“组合子”——一种将解析器作为参数并返回一个新的、更复杂的解析器的函数。例如,序列组合子可以接收三个独立的解析器,并将它们封装成一个按顺序执行它们的单一函数。这允许进行高层抽象,例如一个专门寻找问候语后接数字的解析器。此外,映射组合子可以在解析过程中本身将原始提取的数据转换为特定的对象(如 JSON 风格的字典)。虽然与使用独立语法文件的解析器生成器相比,这种方法有时会导致更多的“解包”代码,但它提供了一种高度模块化且可读的方式,从简单、可测试的单元构建复杂逻辑。
这种手动方法的一个实际限制是类型管理的复杂性,尤其是在像 TypeScript 这样的语言中,其中序列的变长类型可能难以表达。此外,虽然组合子高度灵活,但它们要求开发人员手动定义操作序列,这可能不如从正式语法中自动推导解析逻辑的生成器高效。然而,对于许多折中任务,像构建模块一样组合解析器的能力为处理结构化数据提供了一条清晰且易于维护的路径。