我遇到了一个问题,我想用以下语法解析代码块
{
<stmt>;
<stmt>;
<stmt>;
<expr>
}
语句的格式可以为<expr>;
。这使Parsec以我不知道如何解决的方式跳闸。这可能只是我刚开始接触Haskell和Parsec库,但是我不知道在哪里寻找问题的解决方案。我写了一个例子来说明我的确切问题。
使用输入{ 5; 5; 5 }
,它在第三个5
上失败,因为它期望存在;
。我该如何解决?
import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Combinator
parseIdentifier = do
first <- letter
rest <- many $ letter <|> digit <|> char '_'
return $ first : rest
parseExpr = parseIdentifier <|> many1 digit
parseStmt = parseExpr <* char ';'
parseBlock = between
(char '{' >> spaces)
(spaces >> char '}')
(do
stmts <- try $ parseStmt `sepBy` spaces
parseExpr
)
readParser :: Parser String -> String -> String
readParser parser input = case parse parser "dusk" input of
Left err -> show err
Right val -> val
main = interact $ readParser parseBlock
答案 0 :(得分:1)
这类问题通常可以用sepBy
来解决,而不是manyTill
,棘手的一点是要保留manyTill
不消耗的输入,它必须使用try $ lookAhead
旁注:原因可以在
时,Parsec
的源代码中找到。 在内部,manyTill
使用<|>
,为什么try
生效,并且 应用单子绑定lookAhead
,>>=
>>
可以保留输入
因此,更正如下:
parseBlock = between
(char '{' >> spaces)
(spaces >> char '}')
(do
stmts <- manyTill (parseStmt <* spaces)
(try $ lookAhead (parseExpr >> space))
parseExpr
)
上面的解析器仅返回parseExpr
的输出,即5
,如果这是您的意图,则可以简化为:
manyTill (parseStmt <* spaces) (try $ lookAhead (parseExpr >> space)) >> parseExpr
如果您实际上还需要解析的语句字符串,它将变为:
(do
stmts <- manyTill (parseStmt <* spaces)
(try $ lookAhead (parseExpr >> space))
expr <- parseExpr
return (concat (stmts ++ [expr]))
)
返回555
答案 1 :(得分:0)
您的代码存在的问题是sepBy
对它的参数有一定的期望。如果分隔符成功解析,则不会导致元素解析器失败。
要解决此问题,我建议进行以下改进
parseBlock = between
(char '{' >> spaces)
(spaces >> char '}')
(do
stmts <- try $ many $ spaces *> parseStmt
spaces
parseExpr
)