为何使用FParsec解析列表分隔符时未消耗字符?
问题解析与解决方案
为什么解析会失败?
核心问题出在你定义的isSeparator解析器上:你用了manySatisfy (fun c -> c = ' '),而manySatisfy的特性是匹配零个或多个满足条件的字符——哪怕当前位置没有空格,它也会成功返回空字符串,不会触发失败。
结合sepBy woz isSeparator的工作逻辑来看:
- 解析器先成功匹配
(z),接着isSeparator匹配了中间的空格,然后成功匹配(w),此时光标移到输入末尾。 - 这时候
sepBy会尝试重复执行「分隔符 + 元素」的逻辑:isSeparator首先尝试匹配,因为它允许零个空格,所以直接成功返回空字符串。 - 接下来
sepBy会要求匹配下一个woz,但此时已经没有输入了,解析器找不到(w)或(z),于是抛出失败错误。
简单说:你的分隔符解析器允许“空分隔符”,导致sepBy在解析完所有元素后,还固执地想要再找一个元素,最终失败。
针对“一个或多个空格分隔”的解决方案
根据你的需求(元素之间必须有一个或多个空格分隔),这里有几个靠谱的处理方式:
1. 用many1Satisfy替代manySatisfy
many1Satisfy要求至少匹配一个满足条件的字符,完美解决“允许零个空格”的问题:
let isSeparator = many1Satisfy (fun c -> c = ' ') let xs = sepBy woz isSeparator
现在isSeparator必须匹配至少一个空格才会成功,当解析到输入末尾时,它会直接失败,sepBy就会停止尝试,返回已经解析好的元素列表。
2. 使用FParsec内置的spaces1解析器
FParsec已经为我们封装了匹配一个或多个空白字符(包括空格、制表符、换行等)的解析器spaces1,如果你的场景只需要匹配空格,也可以用skipMany1 (pchar ' ')(跳过匹配,不返回字符串,更高效):
// 通用空白字符版本 let isSeparator = spaces1 // 严格只匹配空格的版本 // let isSeparator = skipMany1 (pchar ' ') let xs = sepBy woz isSeparator
3. 额外:确保列表至少有一个元素
你的需求是“一个或多个元素组成的列表”,而sepBy允许解析空列表。如果要严格符合需求,应该用sepBy1替代sepBy,它要求至少匹配一个元素:
let xs = sepBy1 woz isSeparator
测试验证
修改后再运行你的测试案例:
test xs "(z) (w)" // 会成功返回 [(z); (w)],位置停在输入末尾
内容的提问来源于stack exchange,提问作者Sean Kearon
相关产品推荐
相关产品推荐

