Parser Combinators
摘要
一篇技术博客,讲解如何用 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\]`
相似文章
快速简易的解析器组合子
关于在 Scheme (Gambit) 中构建解析器组合子的教程,解释了如何使用函数式编程技术编写快速且可读的解析器。
用于C语言的单头文件解析器组合子
CParseC 是一个基于 C99 的单头文件解析器组合子库,灵感来自 Haskell 的 Parsec,提供零拷贝解析、无隐藏内存分配以及 SIMD 优化的组合子。其目标是提供一种灵活、高性能的手写解析器和 lex/yacc 工具的替代方案。
使用数据导向设计工程化高性能解析器
本文介绍了如何通过关注内存布局和数据导向设计来设计高性能解析器,并以用Zig编写的Yuku JavaScript/TypeScript解析器为例。
解析,而非验证——在并不鼓励你这样做的语言中
一篇探讨在TypeScript中应用“解析,而非验证”原则的博客文章,展示了如何使用品牌类型(branded types)在解析后保留类型信息,尽管TypeScript的结构类型系统使得这种做法不如在Elm或Haskell等语言中那样自然。
受 Emacs rx 启发的可读正则表达式,适用于 JavaScript/TypeScript
本文介绍了一个受 Emacs 的 rx 宏启发的小型 JavaScript/TypeScript DSL,它让开发者以可读的、由命名形式构成的树状结构来编写正则表达式,而不是使用晦涩的字符串。文章讲解了其内部实现(原子、序列、备选、量词),并提供了速查表、对比示例以及包含完整源码的 Gist。