Parser Combinators

Hacker News Top 工具

摘要

一篇技术博客,讲解如何用 TypeScript 中的函数式 Parser Combinator 模式(如 pSeq、pMap)组合小解析器来解析复杂文本,例如把 ping 输出转换为结构化 JSON 对象。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/10/18 10:54

# 解析器组合子 来源:https://t3.at/blog/2026-10-17-parser-combinators/ `"Hello, world\!"` `\{ greeting: "Hello", who: "world" \}` 字符串 `"Hello, world\!"` 解析为一个对象,其中 `greeting` 为 `"Hello"`,`who` 为 `"world"`。 `type Parser = \(input: string\) =\> \[T, string\]` `\["pIntegernumber10, "string 20 30"\]` T 类型的 Parser 是一个函数,它接受一个输入字符串,返回一个由 T 和剩余字符串组成的二元组。例如,`pInteger` 将 `"10 20 30"` 在 `"10"` 之后截断,并返回数字 10 和剩余的字符串 `" 20 30"`。 `pHello` `\["Hello", " 100"\]` `pIgnoreWhitespace` `\[" null", "100"\]` `pInteger` `\["100", ""\]` `"Hello 100"` 依次被 `pHello`、`pIgnoreWhitespace`、`pInteger` 解析,每个解析器都处理前一个解析器留下的剩余字符串:`pHello` 返回 `"Hello"` 和 `" 100"`;`pIgnoreWhitespace` 返回 `" "` 和 `"100"`;`pInteger` 返回 `"100"` 和 `""`。 `pHello100` `\["Hello 100\["Hello", null, 100\]", ""\]` `"Hello 100"` 被 `pHello100` 解析,`pHello100` 返回 `"Hello 100"` 和 `""`。 `pSeq\( pHello, pIgnoreWhitespace, pInteger \)` `Parser<\[ string, null, number \]\>` `pSeq` 接收 `pHello`、`pIgnoreWhitespace`、`pInteger`,生成一个元组 `[string, null, number]` 类型的 Parser。 --- `const pHello100 = pSeq\(\.\.\.\)` `const result = pHello100\("Hello 100"\)` `pHello100` `\["Hello 100\["Hello", null, 100\]", ""\]` `"Hello 100"` 被 `pHello100` 解析,`pHello100` 返回 `"Hello 100"` 和 `""`。 `pGreeting` `\["Hello 100\["Hello", null, 100\]\{ greeting: "Hello", who: 100 \}pMap", ""\]` `"Hello 100"` 被 `pGreeting` 解析,`pGreeting` 返回 `"Hello 100"` 和 `""`。 `pSeq\(pNumber, pSemicolon\)` `\["10;\[10, ";"\]", "20;30;"\]` `pSeq\(pNumber, pSemicolon\)` `\["20;\[20, ";"\]", "30;"\]` `pSeq\(pNumber, pSemicolon\)` `\["30;\[30, ";"\]", ""\]` `"10;20;30;"` 依次被三次 `pSeq\(pNumber, pSemicolon\)` 解析,每次处理前一次留下的剩余字符串:第一次返回 `"10;"` 和 `"20;30;"`;第二次返回 `"20;"` 和 `"30;"`;第三次返回 `"30;"` 和 `""`。 `PING 1\.1\.1\.1 \(1\.1\.1\.1\) 56\(84\) bytes of data\. 64 bytes from 1\.1\.1\.1: icmp\_seq=1 ttl=52 time=389 ms 64 bytes from 1\.1\.1\.1: icmp\_seq=2 ttl=52 time=68\.7 ms 64 bytes from 1\.1\.1\.1: icmp\_seq=3 ttl=52 time=47\.1 ms 64 bytes from 1\.1\.1\.1: icmp\_seq=4 ttl=52 time=48\.6 ms 64 bytes from 1\.1\.1\.1: icmp\_seq=5 ttl=52 time=85\.8 ms 64 bytes from 1\.1\.1\.1: icmp\_seq=6 ttl=52 time=84\.8 ms 64 bytes from 1\.1\.1\.1: icmp\_seq=7 ttl=52 time=87\.4 ms \-\-\- 1\.1\.1\.1 ping statistics \-\-\- 8 packets transmitted, 7 received, 12\.5% packet loss, time 7008ms rtt min/avg/max/mdev = 47\.129/115\.880/388\.750/112\.515 ms` `\{ header: \{ targetIp: "1\.1\.1\.1", resolvedIp: "1\.1\.1\.1", icmpPayloadSize: 56, totalPacketSize: 84 \}, packets: \[ \{ byteCount: 64, from: "1\.1\.1\.1", seq: 1, ttl: 52, time: 389 \}, \{ byteCount: 64, from: "1\.1\.1\.1", seq: 2, ttl: 52, time: 68\.7 \}, \{ byteCount: 64, from: "1\.1\.1\.1", seq: 3, ttl: 52, time: 47\.1 \}, \{ byteCount: 64, from: "1\.1\.1\.1", seq: 4, ttl: 52, time: 48\.6 \}, \{ byteCount: 64, from: "1\.1\.1\.1", seq: 5, ttl: 52, time: 85\.8 \}, \{ byteCount: 64, from: "1\.1\.1\.1", seq: 6, ttl: 52, time: 84\.8 \}, \{ byteCount: 64, from: "1\.1\.1\.1", seq: 7, ttl: 52, time: 87\.4 \}, \], statistics: \{ packetsTransmitted: 8, packetsReceived: 7, packetLoss: 12\.5, time: 7008, rtt: \{ min: 47\.129, avg: 115\.88, max: 388\.75, mdev: 112\.515 \}, \}, \}` **折叠(Fold)** `const result = list\.reduce\(\(acc, n\) =\> acc \+ n\)` `\["Hello", " ", 100\] → "Hello 100"` --- **展开(Unfold)** `const result = pSeq\(\.\.\.\)\("Hello 100"\)` `"Hello 100" → \["Hello", " ", 100\]`

相似文章

快速简易的解析器组合子

Lobsters Hottest

关于在 Scheme (Gambit) 中构建解析器组合子的教程,解释了如何使用函数式编程技术编写快速且可读的解析器。

用于C语言的单头文件解析器组合子

Hacker News Top

CParseC 是一个基于 C99 的单头文件解析器组合子库,灵感来自 Haskell 的 Parsec,提供零拷贝解析、无隐藏内存分配以及 SIMD 优化的组合子。其目标是提供一种灵活、高性能的手写解析器和 lex/yacc 工具的替代方案。

解析,而非验证——在并不鼓励你这样做的语言中

Hacker News Top

一篇探讨在TypeScript中应用“解析,而非验证”原则的博客文章,展示了如何使用品牌类型(branded types)在解析后保留类型信息,尽管TypeScript的结构类型系统使得这种做法不如在Elm或Haskell等语言中那样自然。

受 Emacs rx 启发的可读正则表达式,适用于 JavaScript/TypeScript

Lobsters Hottest

本文介绍了一个受 Emacs 的 rx 宏启发的小型 JavaScript/TypeScript DSL,它让开发者以可读的、由命名形式构成的树状结构来编写正则表达式,而不是使用晦涩的字符串。文章讲解了其内部实现(原子、序列、备选、量词),并提供了速查表、对比示例以及包含完整源码的 Gist。