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

Van*_*uel 1 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"
Run Code Online (Sandbox Code Playgroud)

但是,在尝试解析运算符之前和之后的空格时,我得到一个解析错误:

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"
Run Code Online (Sandbox Code Playgroud)

解析错误是:

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

为什么会这样?有一些其他的方法来分析周围的白色空间只是经营者?

Hea*_*ink 5

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

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

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

您可以通过包装解析器的关键部分来解决此问题try.这将保存输入,直到char s成功为止.如果char s失败,那么buildExpressionParser可以回溯并尝试另一个中缀运算符.

genParser s f = try (whitespace >> char s) >> whitespace >> return f
Run Code Online (Sandbox Code Playgroud)

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

  • 请注意,OP的示例在输入的末尾有一个换行符(`echo`添加换行符,除非使用`-n`调用).`"2*2*3"`有效,但是"2*2*3 \n"`没有. (3认同)