Я пишу 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
.
Любые подходы, которые я мог бы попробовать?
Тот факт, что ваша функция 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
Рекурсивный вызов в конце дает правильную ассоциативность. В этом случае рекурсия приемлема, поскольку левый операнд и оператор уже были использованы. Таким образом, рекурсивный вызов не может создать бесконечный цикл.
term : comparison ((MUL | DIV) comparison)*
приводит к синтаксическому анализу x * (3 < 17)
, что необычно . Это производство буквально говорит о том, что термин - это последовательность продуктов сравнений.