You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Haskell Parsec解析'}'前存在换行时报错的修复方法

Parsec解析花括号列表末尾换行报错修复

问题描述

需要用Parsec解析包裹在花括号内、以单词and分隔的元素列表,规则允许花括号、and的前后存在任意空格或换行,初始实现的解析器代码如下:

testBodyP :: Parser [Tester.Condition]
testBodyP =
  Parsec.between
    (Parsec.char '{' <* Parsec.spaces)
    (Parsec.spaces *> Parsec.char '}')
    (Parsec.sepBy
       condtionP
       (Parsec.spaces *> Parsec.string "and" <* Parsec.spaces))

使用该解析器解析最后一个元素与}之间存在换行的脚本时,会抛出unexpected "}" expecting space or "and"错误,待解析的异常样例如下:

("breaks", "echo -n does this test work") should {
        contain("byeeeee") and
        contain("nope")
}

而解析最后一个元素与}直接相连、无间隔空白的脚本时可以正常运行,正常样例如下:

("breaks", "echo -n does this test work") should {
        contain("byeeeee") and
        contain("nope")}

注:condtionP为自定义的条件解析器,逻辑依赖其他业务模块,此处不展开。

根因分析

报错本质是对Parsec回溯规则的理解偏差:Parsec中只有当解析器失败时没有消费任何输入,上层组合子才会将其判定为分支不匹配,转而尝试后续解析逻辑;如果解析器失败前已经消费了部分输入,会直接抛出解析错误,不会自动回滚已消费的内容。
针对这个场景的具体执行逻辑:

  1. 当最后一个元素和}直接相连时,sepBy解析完最后一个condtionP后,会尝试运行分隔符解析器。分隔符开头的Parsec.spaces碰到},没有可消费的空白字符,直接以零消费状态失败,sepBy会判定列表已结束,正常返回已解析的结果,后续between的结束解析器可以正常消费空白、匹配}。
  2. 当最后一个元素和}之间存在换行、空格时,分隔符开头的Parsec.spaces会先把所有间隔空白全部消费掉,再尝试匹配字符串"and"时碰到},此时分隔符解析失败,但已经消费了前面的空白,不属于零消费失败,sepBy直接抛出解析错误。

修复方案

有两种成熟的修复方式,可根据代码习惯选择:

方案1:给分隔符添加try回溯

用Parsec.try包裹分隔符解析器,让分隔符匹配失败时自动回滚所有已消费的输入,保证失败时不占用输入流,sepBy即可正常识别列表结束:

testBodyP :: Parser [Tester.Condition]
testBodyP =
  Parsec.between
    (Parsec.char '{' <* Parsec.spaces)
    (Parsec.spaces *> Parsec.char '}')
    (Parsec.sepBy
       condtionP
       (Parsec.try $ Parsec.spaces *> Parsec.string "and" <* Parsec.spaces))

这种方式改动最小,只需要给分隔符加一层try即可,适合不想调整原有解析结构的场景。

方案2:调整空白消费位置,消除不必要的回溯

把空白消费逻辑拆分到元素解析的后置位置,用sepEndBy替代sepBy,从根源上避免分隔符解析器在失败前消费输入:

testBodyP :: Parser [Tester.Condition]
testBodyP =
  Parsec.between
    (Parsec.char '{' <* Parsec.spaces)
    (Parsec.char '}')
    (Parsec.sepEndBy
       (condtionP <* Parsec.spaces)
       (Parsec.string "and" <* Parsec.spaces))

这种写法不需要额外的try回溯:每个条件解析完成后立刻消费后续所有空白,接下来要么匹配到and继续解析下一个元素,要么直接碰到},此时分隔符解析器(直接匹配"and")碰到}时零消费失败,sepEndBy正常结束,最后between直接匹配已暴露在输入流头部的}即可,性能更好,逻辑也更直观。


内容的提问来源于stack exchange,提问作者RHL120

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 07:27:38