Как правильно разобрать доступ к полю после вызова функции через библиотеку parser-combinators (makeExprParser)? - PullRequest
0 голосов
/ 18 февраля 2020

Я хочу разобрать выражения вроде этого: a().x. Это должно выглядеть как EAttrRef (EFuncCall (EVarRef "a") []) "x". К сожалению, мой анализатор выражений останавливается слишком рано, он только анализирует a() и затем останавливается.

1:4:
  |
1 | a().x
  |    ^
unexpected '.'
expecting end of input

Код:

pExpr :: Parser Expr
pExpr = lexeme p & dbg "pExpr" <?> "expression"
  where
    pTerm = try pVarRef <|> pELit
    p = makeExprParser pTerm exprTable
    exprTable = [[Postfix opIndexRef], [InfixL opAttrRef], [Postfix opFuncCall]]
    opAttrRef :: Parser (Expr -> Expr -> Expr)
    opAttrRef = do
      symbol "." & dbg "opAttrRef symbol \".\""
      return r
      where
        r x (EVarRef y) = EAttrRef x y
        r x y = error [qq|opAttrRef got unexpected right operand $y (left operand was $x)|]
    opFuncCall :: Parser (Expr -> Expr)
    opFuncCall = do
      symbol "("
      args <- sepBy pExpr (symbol ",")
      symbol ")" & dbg "opFuncCall symbol \")\""
      return $ \funcExpr -> EFuncCall funcExpr args
    opIndexRef = do
      symbol "["
      e <- pExpr
      symbol "]" & dbg "opIndexRef symbol \"]\""
      return $ \obj -> EIndexRef obj e

Отладочный вывод:

opAttrRef symbol "."> IN: "().x"
opAttrRef symbol "."> MATCH (EERR): <EMPTY>
opAttrRef symbol "."> ERROR:
opAttrRef symbol "."> offset=1:
opAttrRef symbol "."> unexpected '('
opAttrRef symbol "."> expecting '.'

pExpr> IN: ").x"
pExpr> MATCH (EERR): <EMPTY>
pExpr> ERROR:
pExpr> offset=2:
pExpr> unexpected ").x"
pExpr> expecting "false", "null", "true", '"', '+', '-', '[', digit, identifier, or integer

opFuncCall symbol ")"> IN: ").x"
opFuncCall symbol ")"> MATCH (COK): ')'
opFuncCall symbol ")"> VALUE: ")"

pExpr> IN: "a().x"
pExpr> MATCH (COK): "a()"
pExpr> VALUE: EFuncCall (EVarRef "a") []

Мне кажется, что makeExprParser не вызывает opFuncCall второй раз (по сравнению с тем, как выглядит вывод отладочной информации доступа к индексу), но я понятия не имею, почему нет.

Он анализируется при уменьшении opAttrRef приоритет, но затем он создает неправильные деревья (например, правильный операнд x.a() будет a(), что неверно, оно должно быть a, а затем все мысли должны быть в вызове функции), поэтому я не могу использовать это (Я совершенно уверен, что текущий приоритет правильный, поскольку он основан на ссылке на этом языке).

1 Ответ

0 голосов
/ 18 февраля 2020

Ваш текущий синтаксический анализатор выражений выглядит следующим образом: BNF:

expr = funcOp ;
funcOp = attrOp , { "(" , expr, ")" } ;
attrOp = attrOp , "." , indexOp | indexOp ;
indexOp = term , { "[", expr, "]" } ;

Когда он завершит синтаксический анализ funcCall, он не будет go выполнять резервное копирование в таблице операторов и анализировать любые attrRef или indexRef.

Проблема с уменьшением приоритета opAttrRef заключается в том, что анализирует левую и правую части точки по отдельности, когда кажется, что вы хотите, чтобы парсер читал слева направо и может смешивать любой из funcCall, attrRef или indexRef. Поэтому, если вы хотите иметь возможность анализировать что-то вроде a[b](c).d(e)[f], я бы предложил изменить opAttrRef с инфикса на постфикс и сгладить таблицу операторов в:

exprTable = [[Postfix opIndexRef, PostFix opAttrRef, Postfix opFuncCall]]

На этом этапе анализатор становится:

expr = term , { indexRef | attrRef | funcCall } ;

Если вам нужно разрешить несколько операторов постфикса, вы можете переписать свой синтаксический анализатор выражений следующим образом:

p = liftM2 (foldl (flip ($))) pTerm (many (opAttrRef <|> opIndexRef <|> opFuncCall))

Анализатор p можно использовать в качестве анализатора терминов. для makeExprParser, если вы хотите добавить арифметику c, logi c и другие общие операторы.

...