为什么Parsec在buildExpressionParser表中的运算符之前不允许使用空格

时间:2012-11-25 16:59:12

标签: parsing haskell parsec

在下面的代码中,我可以使用Parsec在每个标记之后正确解析空格:

whitespace = skipMany (space <?> "")

number :: Parser Integer
number = result <?> "number"
  where
  result = do {
    ds <- many1 digit;
    whitespace;
    return (read ds)
  }

table = result
  where
  result = [
    [Infix (genParser '*' (*)) AssocLeft, 
     Infix (genParser '/' div) AssocLeft],
    [Infix (genParser '+' (+)) AssocLeft, 
     Infix (genParser '-' (-)) AssocLeft]]
  genParser s f = char s >> whitespace >> return f

factor = parenExpr <|> number <?> "parens or number"
  where
  parenExpr = do {
    char '(';
    x <- expr;
    char ')';
    whitespace;
    return x
  }

expr :: Parser Integer
expr = buildExpressionParser table factor <?> "expression"

但是,在尝试 解析运算符之前和后的 时,我遇到了解析错误:

whitespace = skipMany (space <?> "")

number :: Parser Integer
number = result <?> "number"
  where
  result = do {
    ds <- many1 digit;
    return (read ds)
  }

table = result
  where
  result = [
    [Infix (genParser '*' (*)) AssocLeft, 
     Infix (genParser '/' div) AssocLeft],
    [Infix (genParser '+' (+)) AssocLeft, 
     Infix (genParser '-' (-)) AssocLeft]]
  genParser s f = whitespace >> char s >> whitespace >> return f

factor = parenExpr <|> number <?> "parens or number"
  where
  parenExpr = do {
    char '(';
    x <- expr;
    char ')';
    return x
  }

expr :: Parser Integer
expr = buildExpressionParser table factor <?> "expression"

解析错误是:

$ ./parsec_example < <(echo "2 * 2 * 3")
"(stdin)" (line 2, column 1):
unexpected end of input
expecting "*"

为什么会这样?是否有其他方法来解析只是运算符周围的空白区域?

1 个答案:

答案 0 :(得分:5)

当我测试您的代码时,2 * 2 * 3正确解析,但2 + 2没有。解析失败,因为*的解析器消耗了一些输入,并且在该位置没有启用回溯,因此无法尝试其他解析器。

buildExpressionParser创建的表达式解析器尝试依次解析每个运算符,直到成功。解析2 + 2时,会发生以下情况:

  • 第一个2number匹配。输入的其余部分是 + 2(注意开头的空格)。
  • 解析器genParser '*' (*)应用于输入。它占用空间,但与+字符不匹配。
  • 其他中缀运算符解析器会自动失败,因为genParser '*' (*)消耗了一些输入。

您可以通过在try中包装解析器的关键部分来解决此问题。这将保存输入,直到char s成功。如果char s失败,则buildExpressionParser可以回溯并尝试其他中缀运算符。

genParser s f = try (whitespace >> char s) >> whitespace >> return f

这个解析器的缺点是,因为它在中缀运算符之前回溯到前导空格之前,它会重复扫描空格。通常在成功匹配后解析空格更好,比如OP的第一个解析器示例。