当语句可以是表达式时,读取语句列表并以单个表达式结尾

时间:2018-11-28 04:30:20

标签: haskell parsec

我遇到了一个问题,我想用以下语法解析代码块

{
    <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

2 个答案:

答案 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
    )