为何我的Parsec词法分析器仅对整数存在空格敏感性?
问题:Parsec解析器无法处理无空格分隔的
=>与整数 我编写了如下解析器代码:
module Parser2 where import Text.Parsec import Text.Parsec.String (Parser) import Text.Parsec.Language (emptyDef) import qualified Text.Parsec.Expr as Ex import qualified Text.Parsec.Token as Tok data Expr = Map [(Expr, Expr)] | Int Integer | Str String deriving (Eq, Ord, Show) lexer :: Tok.TokenParser () lexer = Tok.makeTokenParser style where ops = ["=>"] names = [] style = emptyDef { Tok.commentLine = "#" , Tok.reservedOpNames = ops , Tok.reservedNames = names } integer :: Parser Integer integer = Tok.integer lexer commaSep :: Parser a -> Parser [a] commaSep = Tok.commaSep lexer reserved :: String -> Parser () reserved = Tok.reserved lexer int :: Parser Expr int = Int <$> integer maplit :: Parser Expr maplit = do reserved "{" c <- commaSep $ do k <- expr reserved "=>" v <- expr return (k, v) reserved "}" return $ Map c expr :: Parser Expr expr = Ex.buildExpressionParser [] factor stringLit :: Parser Expr stringLit = Str <$> Tok.stringLiteral lexer factor :: Parser Expr factor = try stringLit <|> try maplit <|> try int contents :: Parser a -> Parser a contents p = do Tok.whiteSpace lexer r <- p eof return r parseExpr :: String -> Either ParseError Expr parseExpr = parse (contents expr) "<stdin>"
该解析器可正确解析整数前后带空格的映射,但无法解析整数前无空格的情况,而字符串不受此影响:
ghci> parseExpr "{\"a\"=> 1,\"b\"=> 2}" Right (Map [(Str "a",Int 1),(Str "b",Int 2)]) ghci> parseExpr "{\"b\"=>\"c\"}" Right (Map [(Str "b",Str "c")]) ghci> parseExpr "{\"a\"=>1}" Left "<stdin>" (line 1, column 8): unexpected '1' expecting end of "=>"
我意识到这可能与makeTokenParser默认设置有关,但不清楚具体原因,也未找到合适调试方法,恳请帮助。
原因分析
核心问题是错误使用了reserved函数匹配操作符=>:
Tok.reserved是用于匹配保留字的,它要求匹配的字符串后面必须跟着一个词法边界(空白、括号、引号等非标识符字符)。- 你把
"=>"放到了reservedOpNames列表中,但用reserved而非reservedOp来匹配它,导致解析=>1时,reserved "=>"匹配完符号后,发现后续的1属于标识符(整数词法单元),不满足词法边界要求,直接报错。 - 字符串不受影响是因为引号本身就是词法边界,
"=>"后接引号时,reserved会认为符合边界要求,因此能继续解析。
解决方案
把匹配"=>"的代码从reserved "=>"改成Tok.reservedOp lexer "=>"——reservedOp是专门用于匹配操作符的,没有词法边界的限制:
maplit :: Parser Expr maplit = do reserved "{" c <- commaSep $ do k <- expr Tok.reservedOp lexer "=>" -- 替换为reservedOp v <- expr return (k, v) reserved "}" return $ Map c
另外补充:用reserved匹配{和}也不合适,它们属于标点符号,更推荐用Tok.symbol lexer "{"或Tok.braces处理,但这不是当前问题的核心。修改后,parseExpr "{\"a\"=>1}"即可正常解析。
调试建议
- 使用
Parsec的traceParser函数跟踪解析流程,比如修改expr为:
能直观看到每一步的解析尝试。expr = traceParser "expr" (Ex.buildExpressionParser [] factor) - 明确
Text.Parsec.Token中核心函数的差异:reserved: 匹配保留字,要求前后有词法边界reservedOp: 匹配操作符,无边界要求symbol: 匹配任意符号,自动处理前后空白
内容的提问来源于stack exchange,提问作者Tanse
相关产品推荐
相关产品推荐

