读取语句列表并以单个表达式结尾,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
我 运行 遇到了一个问题,我想用以下语法解析一段代码
{
<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 whytry
take effect, andlookAhead
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