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

如何让嵌套的Megaparsec解析器按预期触发失败?

问题解答

1. fail调用未终止整个解析的原因

这个问题是MP.many组合子的默认行为和Megaparsec的错误类型设计共同导致的:

  • Megaparsec的错误分为两类:消耗输入的致命失败和未消耗输入的普通失败。fail调用产生的是未消耗输入的普通失败,这类失败会被<|>、many这类具备容错能力的组合子默认捕获。
  • MP.many p的执行逻辑是:反复运行解析器p,一旦p返回未消耗输入的失败就直接停止,返回之前已经解析到的所有结果,不会把内部的普通失败向上抛出为全局错误。

在你测试的ABC&D场景中,解析流程是这样的:

  1. 解析到&时,regular分支的MP.satisfy isAdmissibleChar不匹配,返回未消耗输入的失败
  2. 接着执行<|>右侧的fail分支,仍然返回未消耗输入的普通失败
  3. 整个escaped <|> regular解析器返回普通失败,触发MP.many停止执行,直接返回已经解析到的ABC,不会抛出全局错误。

2. 实现方案优化

你的整体解析思路是合理的,只是没有明确区分「合法终止条件(未转义的控制字符)」和「非法输入(不可接受字符)」,这是这类终止型字符串解析的常见误区。

这类场景的规范实现可以用MP.manyTill组合子,明确指定停止条件为未转义的控制终止符,同时把不可接受字符的错误设为消耗性的致命错误,避免被组合子吞掉,修改后的参考代码如下:

import qualified Data.Text as T
import qualified Text.Megaparsec as MP
import qualified Text.Megaparsec.Char as MC
import Data.List.NonEmpty as NE

pComponent :: Parser (Maybe Text)
pComponent = do
  t <- MP.manyTill (escaped <|> regular) stopCondition
  pure $ if null t then Nothing else Just (T.pack t)
 where
  -- 合法停止条件:前瞻到未转义的控制字符,或者到达输入末尾
  stopCondition = MP.lookAhead (MP.satisfy isControlChar) <|> MP.eof
  regular = do
    c <- MP.anySingle
    case () of
      _ | isAdmissibleChar c -> pure c
      _ | isControlChar c -> 
          -- 控制字符属于终止条件,返回未消耗失败触发manyTill停止
          MP.unexpected (MP.Label $ MP.fromList "control character")
      _ -> 
          -- 不可接受字符抛出致命错误,不会被组合子吞掉
          MP.failure (Just $ MP.Tokens $ NE.singleton c) mempty
  escaped = do
    _ <- MC.char escChar
    MP.satisfy isControlChar <?> "escaped control character"

这个实现的优势在于:

  • 停止条件显式声明,逻辑更清晰易维护
  • 不可接受字符会直接抛出全局错误,符合需求预期
  • 转义逻辑和原逻辑保持一致,仅允许转义控制字符

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.17 16:54:07