如何将`a -> Parser b`转换为`Parser (a -> b)`?Parsec解析器问题
解决方案与思路调整
核心问题分析
你遇到的本质矛盾是:buildExpressionParser要求操作符解析器返回纯函数(如Parser (a -> a -> a)),但你需要在Parser monad内执行带副作用的校验逻辑(通过parserFail抛出错误)。sequence报错是因为函数类型(->) a并非Traversable实例,无法被sequence遍历转换。
以下是几种可行的解决思路:
方案1:手动实现表达式解析(替代buildExpressionParser)
放弃依赖buildExpressionParser,手写递归下降解析器,直接在解析流程中嵌入校验逻辑。这种方式能完全掌控操作符与操作数的组合过程,自然支持Parser monad内的校验:
-- 自定义支持Parser操作符的chainl1 chainl1' :: Parser a -> Parser (a -> a -> Parser a) -> Parser a chainl1' p op = do x <- p rest x where rest x = do f <- op y <- p z <- f x y rest z <|> return x -- 解析加法操作符,内嵌所有权校验 parseAddOp :: Parser (Located (Algebra Located) -> Located (Algebra Located) -> Parser (Located (Algebra Located))) parseAddOp = do opPos <- getPosition _ <- symbol "+" return $ \left right -> do if checkOwnership left right then return $ combineAlgebra left right "+" opPos else parserFail $ "位置" ++ show opPos ++ ":操作数" ++ show left ++ "与" ++ show right ++ "所有权不匹配" -- 表达式解析入口 parseExpr :: Parser (Located (Algebra Located)) parseExpr = parseTerm `chainl1'` parseAddOp parseTerm = parseFactor `chainl1'` parseMulOp -- ... 其他优先级的解析逻辑
方案2:先解析AST,后做校验
保留buildExpressionParser的便捷性,先解析出包含位置、所有权信息的AST,再遍历AST执行校验,校验失败时通过parserFail抛出带位置的错误:
-- 定义AST结构,包含操作符位置、左右操作数 data ExprAST = OpNode (Located String) (Located (Algebra Located)) (Located (Algebra Located)) | AtomNode (Located (Algebra Located)) -- 用buildExpressionParser解析AST parseAST :: Parser ExprAST parseAST = buildExpressionParser operators parseAtom where operators = [ [Infix (do pos <- getPosition; _ <- symbol "+"; return (\x y -> OpNode (Located pos "+") x y)) AssocLeft] -- 其他操作符定义... ] parseAtom = AtomNode <$> parseAlgebraAtom -- 遍历AST执行校验 validateAST :: ExprAST -> Parser ExprAST validateAST (OpNode op left right) = do unless (checkOwnership left right) $ parserFail $ "位置" ++ show (getLoc op) ++ ":" ++ show left ++ "与" ++ show right ++ "所有权校验失败" OpNode op <$> validateAST left <*> validateAST right validateAST (AtomNode atom) = return (AtomNode atom) -- 最终解析器:先解析再校验 parseAndValidate :: Parser ExprAST parseAndValidate = parseAST >>= validateAST
方案3:避免转换,调整校验时机
如果所有权校验逻辑仅与单个操作数相关(而非操作符组合),可以将校验逻辑直接嵌入操作数的解析器中,提前在解析原子节点时抛出错误,无需等待操作符组合:
parseAlgebraAtom :: Parser (Located (Algebra Located)) parseAlgebraAtom = do atom <- parseRawAtom if checkAtomOwnership atom then return atom else parserFail $ "位置" ++ show (getLoc atom) ++ ":原子操作数所有权无效"
内容的提问来源于stack exchange,提问作者ShapeOfMatter
相关产品推荐
相关产品推荐

