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

Reading list of statements and ending with a single expression, when statements can be expressions

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

{
    <stmt>;
    <stmt>;
    <stmt>;
    <expr>
}

语句的形式可以是<expr>;。这以一种我不知道如何修复的方式绊倒了秒差距。这可能只是我对 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

您的代码的问题是 sepBy 对其参数有一定的期望。如果分隔符成功解析,它不会期望元素解析器失败。

为了解决这个问题,我建议进行以下改进

parseBlock = between
    (char '{' >> spaces)
    (spaces >> char '}')
    (do
        stmts <- try $ many $ spaces *> parseStmt
        spaces
        parseExpr
    )

而不是sepBy,这类问题通常可以通过manyTill解决,棘手的一点是保持输入不被manyTill消耗,它必须使用 try $ lookAhead

Side note: the reason can be found in source code of Parsec. Internally, manyTill use <|>, so why try take effect, and lookAhead can retain the input when apply monad bind >>=, >>

因此,更正如下所示:

parseBlock = between
    (char '{' >> spaces)
    (spaces >> char '}')
    (do
        stmts <- manyTill (parseStmt <* spaces) 
                          (try $ lookAhead (parseExpr >> space))
        parseExpr
    )

上面的解析器只是 return 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]))
)

它return555