0

以下のコードでは、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 "*"

なぜこれが起こるのですか?演算子だけの周りの空白を解析する他の方法はありますか?

4

1 に答える 1

4

コードをテストすると、2 * 2 * 3正しく解析されますが、解析さ2 + 2れません。*のパーサーが一部の入力を消費し、その位置でバックトラッキングが有効になっていないため、解析が失敗します。そのため、他のパーサーを試すことはできません。

によって作成された式パーサーは、buildExpressionParser成功するまで各演算子を順番に解析しようとします。を解析2 + 2すると、次のことが起こります。

  • 最初2は と一致しnumberます。残りの入力は次のとおりです + 2(先頭のスペースに注意してください)。
  • パーサーgenParser '*' (*)が入力に適用されます。+スペースを消費しますが、文字と一致しません。
  • 一部の入力が によって消費されたため、他の中置演算子パーサーは自動的に失敗しgenParser '*' (*)ます。

これは、パーサーの重要な部分を でラップすることで修正できますtrychar sこれは afterが成功するまで入力を保存します。char s失敗した場合は、buildExpressionParserバックトラックして別の中置演算子を試すことができます。

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

このパーサーの欠点は、中置演算子の前の先頭の空白の前にバックトラックするため、空白を繰り返しスキャンすることです。通常、OP の最初のパーサーの例のように、一致が成功した後に空白を解析することをお勧めします。

于 2012-11-25T17:24:36.317 に答える