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

Parsec中try使用误区排查:<-解析失败问题及修复方案

问题描述

我编写了一段基于Haskell Parsec库的解析代码,用于解析包含操作符和原始字符串的文本:

import Control.Applicative ((<|>))
import Text.Parsec (ParseError, endBy, sepBy, try)
import Text.Parsec.String (Parser)
import qualified Data.Char as Char
import qualified Text.Parsec as Parsec

data Operation = Lt | Gt deriving (Show)

data Value =
      Raw String
    | Op Operation
  deriving (Show)

sampleStr :: String
sampleStr =  unlines
  [ "#BEGIN#"
  , "x <- 3.14 + 2.72;"
  , "x < 10;"
  ]

gtParser :: Parser Value
gtParser = do
  Parsec.string "<"
  return $ Op Gt

ltParser :: Parser Value
ltParser = do
  Parsec.string ">"
  return $ Op Lt

opParser :: Parser Value
opParser = gtParser <|> ltParser

rawParser :: Parser Value
rawParser = do
  str <- Parsec.many1 $ Parsec.satisfy $ not . Char.isSpace
  return $ Raw str

valueParser :: Parser Value
valueParser = try opParser <|> rawParser

eolParser :: Parser Char
eolParser = try (Parsec.char ';' >> Parsec.endOfLine)
         <|> Parsec.endOfLine

lineParser :: Parser [Value]
lineParser = sepBy valueParser $ Parsec.many1 $ Parsec.char ' '

fileParser :: Parser [[Value]]
fileParser = endBy lineParser eolParser

parse :: String -> Either ParseError [[Value]]
parse = Parsec.parse fileParser "fail..."

main :: IO ()
main = print $ parse sampleStr

运行后解析样本字符串时出现错误:

Left "fail..." (line 2, column 4):
unexpected "-"
expecting " ", ";" or new-line

我原本认为使用try opParser后,当Parsec无法将<-解析为操作符时,应当回退并尝试rawParser,但实际解析失败了。请问我的理解误区是什么?该如何修复这个错误?

误区分析

你对try的作用理解有误:try opParser只会在opParser整体解析失败时触发回退,但这里opParser并没有失败——gtParser成功匹配了<字符,返回了Op Gt,Parsec认为这部分解析完成,继续处理后续的-字符。

而你的lineParser要求value之间用一个或多个空格分隔,此时-前面没有空格,不符合分隔规则,因此抛出了"expecting space"的错误,和try的回退逻辑无关。

修复方案

问题的核心是操作符解析器误将<从<-中单独匹配出来,导致后续字符无法被正确解析。我们需要修改操作符解析器,确保它只匹配独立的操作符(即操作符后面不能紧跟非空格字符,避免和类似<-的复合符号混淆),具体步骤如下:

  1. 导入notFollowedBy函数,用于检查后续字符不符合特定条件
  2. 修改gtParser和ltParser,在匹配操作符后添加notFollowedBy检查,确保操作符不是更长符号的一部分

修改后的代码如下:

import Control.Applicative ((<|>))
import Text.Parsec (ParseError, endBy, sepBy, try, notFollowedBy) -- 添加notFollowedBy
import Text.Parsec.String (Parser)
import qualified Data.Char as Char
import qualified Text.Parsec as Parsec

data Operation = Lt | Gt deriving (Show)

data Value =
      Raw String
    | Op Operation
  deriving (Show)

sampleStr :: String
sampleStr =  unlines
  [ "#BEGIN#"
  , "x <- 3.14 + 2.72;"
  , "x < 10;"
  ]

gtParser :: Parser Value
gtParser = do
  Parsec.string "<"
  -- 确保<后面不是非空格字符,避免匹配<-的前半部分
  notFollowedBy (Parsec.satisfy (not . Char.isSpace))
  return $ Op Gt

ltParser :: Parser Value
ltParser = do
  Parsec.string ">"
  -- 确保>后面不是非空格字符
  notFollowedBy (Parsec.satisfy (not . Char.isSpace))
  return $ Op Lt

opParser :: Parser Value
opParser = gtParser <|> ltParser

rawParser :: Parser Value
rawParser = do
  str <- Parsec.many1 $ Parsec.satisfy $ not . Char.isSpace
  return $ Raw str

valueParser :: Parser Value
valueParser = try opParser <|> rawParser

eolParser :: Parser Char
eolParser = try (Parsec.char ';' >> Parsec.endOfLine)
         <|> Parsec.endOfLine

lineParser :: Parser [Value]
lineParser = sepBy valueParser $ Parsec.many1 $ Parsec.char ' '

fileParser :: Parser [[Value]]
fileParser = endBy lineParser eolParser

parse :: String -> Either ParseError [[Value]]
parse = Parsec.parse fileParser "fail..."

main :: IO ()
main = print $ parse sampleStr

修改后,当遇到<-时,gtParser会因为<后面紧跟-(非空格字符)而失败,opParser整体失败,此时try触发回退,rawParser会将<-作为一个完整的Raw值解析;而遇到独立的<(如第三行的x < 10;)时,gtParser会成功匹配并返回Op Gt,符合预期。

运行修改后的代码,会得到正确的解析结果:

Right [[Raw "#BEGIN#"],[Raw "x",Raw "<-",Raw "3.14",Raw "+",Raw "2.72"],[Raw "x",Op Gt,Raw "10"]]

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 07:54:26