Разбор правоассоциативного оператора (показатели степени)

1

Я пишу lexer/parser/interpreter для своего собственного языка, и пока все работает. Я слежу за примерами в блоге Руслана Спивака (ссылка Github на каждую статью).

Я хотел бы расширить свою грамматику языка за пределы того, что написано в статьях, чтобы включить больше операторов, таких как сравнения (<, >= и т.д.), А также экспоненты (** или ^ на моем языке). У меня есть эта грамматика:

expression        : exponent ((ADD | SUB) exponent)*
exponent          : term ((POWER) term)*
# this one is right-associative (powers **)

term              : comparison ((MUL | DIV) comparison)*
comparison        : factor ((EQUAl   | L_EQUAL | LESS
                             N_EQUAL | G_EQUAL | GREATER) factor)*
# these are all binary operations



factor            : NUM | STR        | variable
                        | ADD factor | SUB factor
                        | LPAREN expr RPAREN
# different types of 'base' types like integers
# also contains parenthesised expressions which are evalutaed first

Что касается маркеров разбора, я использую тот же метод, что и в блоге Руслана. Вот тот, который будет анализировать строку exponent, которая обрабатывает сложение и вычитание, несмотря на свое имя, поскольку грамматика говорит, что выражения анализируются как exponent_expr (+ / -) exponent_expr

def exponent(self):
    node = self.term()
    while self.current_token.type in (ADD, SUB):
        token = self.current_token

        if token.type == ADD:
            self.consume_token(ADD)
        elif token.type == SUB:
            self.consume_token(SUB)

        node = BinaryOperation(left_node=node,
                               operator=token,
                               right_node=self.term())

    return node

Теперь это разбирает лево-ассоциативные токены просто отлично (поскольку поток токенов поступает слева направо, естественно), но я зациклился на том, как анализировать право-ассоциативные показатели. Посмотрите на этот ожидаемый вход/выход для справки:

>>> 2 ** 3 ** 2
# should be parsed as...
>>> 2 ** (3 ** 2)
# which is...
>>> 2 ** 9
# which returns...
512

# Mine, at the moment, parses it as...
>>> (2 ** 3) ** 2
# which is...
>>> 8 ** 2
# which returns...
64

Чтобы решить эту проблему, я попытался переключить левый и правый узлы конструктора BinaryOperation() чтобы сделать текущий узел справа, а новый узел - левым, но это просто делает анализ 2**5 равным 5**2 что дает мне 25 вместо ожидаемый 32.

Любые подходы, которые я мог бы попробовать?

  • 0
    Эта грамматика дает странно высокий приоритет операторам сравнения.
  • 0
    Да, я только что понял это. Я по ошибке положил это в нижней части, так как я думал, что это был более высокий приоритет, тогда как на самом деле верхняя строка имеет самый низкий приоритет
Теги:
parsing
token
right-to-left

1 ответ

0
Лучший ответ

Тот факт, что ваша функция exponent фактически анализирует expression должна быть красным флагом. Фактически, вам нужна функция expression которая анализирует выражения и функцию exponent которая анализирует экспоненции.

Вы также смешали приоритеты возведения в степень и умножения (и других операций), потому что 2 * x ** 4 не означает (2 * x) ** 4 (что было бы 16x⁴), а скорее 2 * (x ** 4). Точно так же x * 3 < 17 не означает x * (3 < 17), как ваша грамматика будет анализировать его.

Обычно приоритеты для арифметики выглядят примерно так:

 comparison     <, <=, ==, ... ( lowest precedence)
 additive       +, -
 multiplicative *, /, %
 unary          +, -
 exponentiation **
 atoms          numbers, variables, parenthesized expressions, etc.

(Если бы у вас были постфиксные операторы, такие как вызовы функций, они проходили бы между экспоненцированием и атомами.)

После того как вы переработали свою грамматику в этой форме, анализатор экспонентов будет выглядеть примерно так:

def exponent(self):
    node = self.term()
    while self.current_token.type is POWER:
        self.consume_token(ADD)
        node = BinaryOperation(left_node=node,
                               operator=token,
                               right_node=self.exponent())
    return node

Рекурсивный вызов в конце дает правильную ассоциативность. В этом случае рекурсия приемлема, поскольку левый операнд и оператор уже были использованы. Таким образом, рекурсивный вызов не может создать бесконечный цикл.

  • 0
    Спасибо за этот подробный ответ. Очень признателен. Я только что понял, что теперь я читаю свою грамматику несколько назад, поскольку выражение верхней строки на самом деле имеет самый низкий приоритет, и я ошибочно вставил правила сравнения и экспоненты в противоположные стороны вокруг аддитивных и мультипликативных правил.
  • 0
    Ваша грамматика неверна. term : comparison ((MUL | DIV) comparison)* приводит к синтаксическому анализу x * (3 < 17) , что необычно . Это производство буквально говорит о том, что термин - это последовательность продуктов сравнений.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню