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的回退逻辑无关。
修复方案
问题的核心是操作符解析器误将<从<-中单独匹配出来,导致后续字符无法被正确解析。我们需要修改操作符解析器,确保它只匹配独立的操作符(即操作符后面不能紧跟非空格字符,避免和类似<-的复合符号混淆),具体步骤如下:
- 导入
notFollowedBy函数,用于检查后续字符不符合特定条件 - 修改
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
相关产品推荐
相关产品推荐

