Python PLY Yacc - Разбор деления комплексных чисел

Я реализую калькулятор на Python, чтобы иметь возможность вычислять как действительные, так и комплексные числа.

У меня есть лексер / парсер, использующий PLY, и я создаю свой собственный класс для комплексных чисел, добровольно игнорируя тот факт, что Python уже имеет встроенный тип для комплексных чисел.

Парсер работает нормально, кроме этого случая:

42i/2i

Мой парсер интерпретирует этот случай так:

42i/2i = (42*i)/(2*i) = 21

Как вы можете видеть выше, каждое комплексное число рассматривается как блок, действительная часть которого неотделима от мнимой части. Но математическая истина иная. Как вы, возможно, знаете, к этому случаю следует относиться следующим образом:

42i/2i = 42*i/2*i = -21

Я должен адаптировать свои правила парсера, чтобы получить правильный результат, но я не могу понять, как это сделать. Вот минимальный воспроизводимый пример:

import ply.lex as lex
import ply.yacc as yacc

tokens = (
    'NUMBER',
    'DIVIDE',
    'IMAGINE',
)

########################## LEXER ##########################

t_DIVIDE    = r'\/'

def t_NUMBER(t):
    r'\d+(\.\d+)?'
    t.value = int(t.value)
    return t

def t_IMAGINE(t):
    r'i'
    t.value = Complex(0, 1)
    return t

def t_error(t):
    print("Illegal character '%s'" % t.value[0])
    t.lexer.skip(1)

########################## PARSER #########################

def p_expression_binop(t):
    '''expression : expression DIVIDE expression'''
    t[0] = t[1] / t[3]
    print(t[0])

def p_expression_number(t):
    '''expression : NUMBER
                  | IMAGINE'''
    t[0] = t[1]

def p_expression_imaginary(t):
    '''expression : NUMBER IMAGINE'''
    t[0] = t[1] * t[2]

def p_error(t):
    print("Syntax error!")

###################### COMPLEX CLASS ######################

class Complex(object):
    def __init__(self, real, imag=0):
        self.real = real
        self.imag = imag

    def __str__(self):
        string = ''
        if self.real != 0:
            if self.real % 1 == 0 : self.real = int(self.real)
            string += str(self.real)
        if self.imag != 0:
            if self.imag % 1 == 0 : self.imag = int(self.imag)
            if self.real != 0:
                string += ' + ' if self.imag > 0 else ' - '
            else:
                string += '' if self.imag > 0 else '-'
            if abs(self.imag) != 1:
                string += str(abs(self.imag)) + 'i'
            else:
                string += 'i'
        return string or '0'

    def __mul__(self, other):
        return Complex(self.real * other.real - self.imag * other.imag,
                       self.imag * other.real + self.real * other.imag)

    def __rmul__(self, other):
        return self.__mul__(other)

    def __truediv__(self, other):
        if isinstance(other, (float,int)):
            other = Complex(other)
        s1, s2, o1, o2 = self.real, self.imag, other.real, other.imag
        r = float(o1 ** 2 + o2 ** 2)
        return Complex((s1 * o1 + s2 * o2) / r, ( s2 * o1 - s1 * o2) / r)

    def __rtruediv__(self, other):
        if isinstance(other, (float,int)):
            other = Complex(other)
        return other.__truediv__(-self)

########################## MAIN ##########################

lexer = lex.lex() 
while True:
    try:
        s = raw_input('> ')
    except:
        s = input('> ')
    if s:
        parser = yacc.yacc()
        parser.parse(s)

Любая помощь ? Большое спасибо !


person cuzureau    schedule 20.03.2020    source источник
comment
Почему вы считаете необходимым включить p_power_all? И разве это не предупреждает вас о конфликте? Также: рассмотрите возможность предоставления минимального воспроизводимого примера. Это действительно упрощает предоставление хороших ответов. (Но обратите внимание на слово минимальный.)   -  person rici    schedule 20.03.2020
comment
Спасибо за интерес @rici и извиняюсь за задержку. Я обновил свой вопрос в соответствии с инструкциями, описанными в вашей ссылке.   -  person cuzureau    schedule 23.03.2020
comment
Это полезно, спасибо.   -  person rici    schedule 23.03.2020


Ответы (1)


В вашей программе не хватает одного:

precedence = (
    ('left', 'DIVIDE'),
)

Без этого Ply генерирует конфликт сдвига-уменьшения при генерации синтаксического анализатора из-за производственного

expression : expression DIVIDE expression

Я упоминаю это только потому, что проблема заключается в приоритете операторов, хотя рассматриваемый оператор невидим: оператор неявного умножения, воплощенный в продукте:

expression : NUMBER IMAGINE

Вторая продукция не вызывает никаких конфликтов синтаксического анализа, даже без объявления приоритета, но это потому, что она недостаточно общая, чтобы на самом деле анализировать полезные выражения. Рассмотрим, например, ваш пример

42i/4i

Как вы говорите, ваш синтаксический анализатор интерпретирует это как

[expression: [expression: 42 i]
             /
             [expression: 4 i] ]

Но вы хотите, чтобы это интерпретировалось как:

[expression: [expression: [expression: 42 i]
                          /
                          [expression: 4]
              i]
]

Но очевидно, что самый внешний expression не может быть сгенерирован из вашей грамматики, потому что ваша грамматика требует, чтобы левый оператор неявного умножения был NUMBER, а в этом синтаксическом анализе это явно expression.

Прежде чем мы просто добавим производство

expression : expression IMAGINE

мы, вероятно, должны попытаться представить все возможные варианты использования. И один из них должен сразу прийти в голову: 4i2 (без подробных сведений о том, какой оператор вы выбираете для представления возведения в степень).

Неправильная грамматика слитного числа и воображаемого будет видеть это как квадрат 4i (то есть -16), но общепринятый синтаксический анализ в четыре раза больше квадрата i (то есть -4). Это соответствовало бы синтаксическому анализу

[expression: [expression: 4]
             [expression: [expression: i]
                          POWER
                          [expression: 2]]]

в котором правый аргумент неявного умножения - это не воображаемое, а выражение.

Итак, ваша первая задача - решить, каким будет общее неявное умножение в вашем языке. Все ли следующие действительны? (Некоторые требуют, чтобы ваш лексер отбрасывал пробелы)

4i
i4
(4*5)i
4(7+i)
(4+i)(7-i)
i i
4 i
4 7

Возможно, вы с подозрением отнесетесь к паре последних, но смею предположить, что большинство из них не вызовут никакого удивления. Позже мы можем посмотреть, как отклонить случай двух последовательных чисел, если это необходимо, но кажется, что наиболее разумное правило неявного умножения, по крайней мере, похоже на наиболее общее:

expression : expression expression

и, кроме того, он имеет тот же приоритет и ассоциативность, что и деление или явное умножение.

Но это оставляет нам проблему: как вы можете поместить оператор в таблицу приоритетов, когда оператора нет? И короткий ответ: вы не можете. Есть некоторые уловки, которые более или менее работают, но, безусловно, самая простая и удобочитаемая альтернатива - написать грамматику так, чтобы приоритет был явным.

Я не буду больше вдаваться в этот стиль, потому что он был забит до смерти в другом месте, и у вас не будет проблем с поиском примеров по всему Интернету (большинство из которых используют нетерминалы, называемые term и factor, которые я здесь не использую, потому что Я думаю, что их значения достаточно неясны, чтобы многие люди понимали их задом наперед). Я просто напишу грамматику в стиле PLY с функциями действий.

И еще одно замечание о функциях действий: Ply позволяет объединить две постановки с одним действием. Это удобно, но не означает, что вам следует делать такие вещи:

def p_additive(p):
    """ additive : additive '+' multiplicative
        additive : additive '-' multiplicative
        multiplicative : multiplicative '*' value
        multiplicative : multiplicative '/' value
        ...
    """
    if p[2] == '+' then:
        p[0] = p[1] + p[3]
    else if p[2] == '-' then:
        p[0] = p[1] - p[3]}
    else if p[2] == '*' then:
        p[0] = p[1] * p[3]}
    else if p[2] == '/' then:
        p[0] = p[1] / p[3]}

Чтобы увидеть, насколько это глупо, рассмотрим процесс, с помощью которого парсер попал туда. Во-первых, синтаксический анализатор точно определил, какая продукция была сопоставлена. Предположим, что производство было expression : additive '-' multiplicative. Затем он ищет соответствие между производством и функциями и находит указанную выше функцию (которая является точно такой же функцией, которую он нашел бы, если бы в производстве использовался любой другой бинарный оператор). Таким образом, он вызывает эту функцию, и в этот момент информация о том, какая продукция соответствует требованиям, фактически теряется.

Это означает, что первое, что должна сделать функция, - это выяснить, какое производство было сокращено, что уже было выяснено. И он продолжает делать это, сравнивая операторов по одному с теми, о которых он знает, что является пустой тратой циклов.

Надеюсь, этого достаточно, чтобы объяснить, почему в следующей грамматике не используются подобные функции. Если вы пишете функцию действия синтаксического анализатора Ply и обнаруживаете, что используете оператор if, чтобы проверить, какое из произведений было сопоставлено, вам следует немедленно подумать об использовании вместо этого двух (или более) различных функций действий. (Если действия имеют общие черты, подумайте о том, чтобы выделить их в подфункции. Вероятно, результат будет более читабельным и более удобным в обслуживании.)

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

import ply.lex as lex
import ply.yacc as yacc

### Lexer

# This lexer uses literal character tokens wherever possible. They're
# easier, faster, and more readable.
# (https://www.dabeaz.com/ply/ply.html#ply_nn11)

literals = '()+-*/^i'
t_ignore = ' \t'
tokens = ( 'NUMBER', )

def t_NUMBER(t):
    "(?:\d+(?:\.\d*)?|\.\d+)(?:[Ee][+-]?\d+)?"
    t.value = float(t.value)
    return t

def t_error(t):
    print("Illegal character '%s'" % t.value[0])
    t.lexer.skip(1)

### Parser

# Use this function for any unit production which just passes
# its value through.
def p_unit(p): 
    """ expression : additive
        additive : multiplicative
        multiplicative : exponential
        exponential : value
        value : NUMBER
    """
    p[0] = p[1]

def p_plus(p):
    """ additive : additive '+' multiplicative """
    p[0] = p[1] + p[3]

def p_minus(p):
    """ additive : additive '-' multiplicative """
    p[0] = p[1] - p[3]

# Compare this production with the next one.
def p_implicit_times(p):
    """ multiplicative : multiplicative exponential """
    p[0] = p[1] * p[2]


def p_times(p):
    """ multiplicative : multiplicative '*' exponential """
    p[0] = p[1] * p[3]

# TODO: catch errors
def p_divide(p):
    """ multiplicative : multiplicative '/' exponential """
    p[0] = p[1] / p[3]

# This one is right-associative.
# TODO: catch errors
def p_power(p):
    """ exponential : value '^' exponential """
    p[0] = p[1] ** p[3]

def p_i(p):
    """ value : 'i' """
    p[0] = 1J   # Python's way of writing 0+1i

def p_paren(p):
    """ value : '(' expression ')' """
    p[0] = p[2]

def p_error(t):
    print("Syntax error at " + str(t))

### Very simple driver

import sys
lexer = lex.lex()
parser = yacc.yacc()
while True:
    try:
        print(parser.parse(input('> ')))
    except EOFError:
        break
    except:
        print(sys.exc_info()[1])

Было бы полезно взглянуть на грамматику, которую я извлек из parser.out:

Rule 1     expression -> additive
Rule 2     additive -> multiplicative
Rule 3     multiplicative -> exponential
Rule 4     exponential -> value
Rule 5     value -> NUMBER
Rule 6     additive -> additive + multiplicative
Rule 7     additive -> additive - multiplicative
Rule 8     multiplicative -> multiplicative exponential
Rule 9     multiplicative -> multiplicative * exponential
Rule 10    multiplicative -> multiplicative / exponential
Rule 11    exponential -> value ^ exponential
Rule 12    value -> i
Rule 13    value -> ( expression )

Хорошо, теперь давайте рассмотрим один важный случай, когда нам действительно нужно изменить грамматику для неявного умножения, чтобы избежать конфликта. Проблема в безобидном на вид выражении 4 - 2. Теперь предположим, что в нашей грамматике реализован унарный минус (чего нет в грамматике выше). В этом случае будет два способа синтаксического анализа выражения: как вычитание и как неявное произведение двух подвыражений 4 и -2.

Очевидно, что вторая интерпретация неверна, и это означает, что мы не можем позволить унарному выражению быть вторым аргументом неявного умножения.

К счастью, мы уже отказались от идеи использовать объявления приоритета для устранения неоднозначности нашей грамматики. Если бы мы пытались выяснить, как изменить

expression : expression expression

так что второе выражение не может начинаться с унарного оператора минус, у нас будут большие проблемы.

Еще одна удача: в стандартной алгебраической нотации оператор возведения в степень является правоассоциативным, а также более плотно привязан к левой части, чем унарный минус, так что -24 оценивается как -16 (-(24)), а не 16 ((-2)4).

Когда мы сложим все вместе, модификация, разрешающая унарный минус, оказывается почти тривиальной. Мы вставляем унарный минус в иерархию приоритета, которой он логически принадлежит, на том же уровне, что и возведение в степень. [См. Примечание 1] Но нам нужно сделать одно исключение, чтобы неявное произведение умножения не могло иметь унарное минус-выражение в качестве своего правого аргумента. Для этого нам нужно разделить уровень на две части, например:

Rule 1     expression -> additive
Rule 2     additive -> multiplicative
Rule 3     multiplicative -> unary
Rule 4     unary -> exponential
Rule 5     exponential -> value
Rule 6     value -> NUMBER
Rule 7     additive -> additive + multiplicative
Rule 8     additive -> additive - multiplicative
Rule 9     multiplicative -> multiplicative exponential
Rule 10    multiplicative -> multiplicative * unary
Rule 11    multiplicative -> multiplicative / unary
Rule 12    unary -> - unary
Rule 13    exponential -> value ^ unary
Rule 14    value -> i
Rule 15    value -> ( expression )

Примечания

  1. Вы найдете много грамматик, которые настаивают на отдельных уровнях приоритета, но если вы немного задумаетесь, вы увидите, что, поскольку возведение в степень является правоассоциативным, оно может иметь общий уровень приоритета с унарным минусом. Если это неясно, это, вероятно, дополнительное свидетельство того факта, что объявления приоритета имеют тенденцию создавать путаницу, за исключением очень простых случаев использования.
person rici    schedule 23.03.2020
comment
Большое спасибо @rici! Это действительно хорошо объяснено и просто для понимания. Здесь я многому научился! Я уже думал об унарных минусах и приоритетах в своем коде, но я не показал их в минимальном примере. То, как вы кодировали свои постановки без использования приоритетов, действительно является для меня новаторским. На всякий случай, когда вы напишете это: [expression: [expression: [expression: 42 i] / [expression: 4] i]] вместо этого не должно быть: [expression: [expression: [expression: 42] i / [expression: 4] i]] (это шестой отрывок кода в вашем ответе) - person cuzureau; 24.03.2020
comment
@cuzureau: [expression: 42 i] соответствует произведению expression : NUMBER IMAGINE из вашей исходной грамматики. В этом дереве синтаксического анализа нет сокращения с 42 до [expression: 42]. В окончательной грамматике будут единичные произведения как для 42, так и для i. - person rici; 24.03.2020
comment
Хорошо спасибо ! Я адаптировал логику к своему коду, и он работает без сбоев. Но у меня не может быть отрицательной силы, такой как: 2^-2, хотя я реализовал унарный минус, и он отлично работает с такими базовыми вещами, как: -3*5. У тебя есть подсказка? - person cuzureau; 24.03.2020
comment
@cuzu: Хорошо, я думаю, что это исправлено. Одна из проблем, когда вы учитесь и получаете готовое решение, заключается в том, что вы узнаете меньше, чем если бы вы узнали это сами. То, что вы усваиваете лучше всего, - это то, о чем вы больше всего думали. Поэтому я призываю вас воспользоваться моей ошибкой и попытаться обдумать ее самостоятельно и посмотреть, придете ли вы к тому же исправлению :-) (И кто знает. Может быть, я сделал другие ошибки. Надеюсь, что нет, но очевидно такое случается.) - person rici; 25.03.2020
comment
Я попробовал сам и ДА, увидел, что не все понял. Я знал, где разместить унарную в иерархии приоритетов, но остальное было неясно. Даже сейчас, мне кажется, у меня все еще возникают проблемы с тем, чтобы полностью понять, как работает синтаксический анализатор PLY, шаг за шагом (или я сумасшедший?). В нашем парсере, в этом примере: 2 + 3, будут вызываться следующие правила: 6,5,4,3,2, 7, 6,5,4,3,2, 1, верно? В любом случае, спасибо за вашу помощь, сэр! - person cuzureau; 25.03.2020
comment
@cuzureau: Мне кажется. Вы можете наблюдать за работой парсера, используя parser.parse(str, debug=True) вместо parser.parse(str). Это покажет вам каждый переход конечного автомата, как сдвиг, так и уменьшение. Я использовал эту функцию для создания этого списка сокращений для 2+3: [value -> NUMBER],[exponential -> value],[unary -> exponential],[multiplicative -> unary],[additive -> multiplicative],[value -> NUMBER],[exponential -> value],[unary -> exponential],[multiplicative -> unary],[additive -> additive + multiplicative],[expression -> additive] - person rici; 25.03.2020
comment
@cuzureau: Чтобы правильно интерпретировать вывод трассировки, вам понадобится конечный автомат из parser.out. - person rici; 25.03.2020
comment
@cuzureau: Важная особенность грамматики в том, что в ней нет магии. Он говорит именно то, что означает. Если там написано, например, multiplicative: multiplicative exponential, это означает, что multiplicative может быть multiplicative, за которым сразу следует exponential. Поскольку exponential не содержит продукции, которая включает предложение, начинающееся с -, эта продукция для multiplicative не может соответствовать 2-3. Вот и все. Больше ничего нет. Так что, если вы пытаетесь вписать это в какую-то сложную модель, вам, вероятно, не хватает простоты всего этого. - person rici; 25.03.2020
comment
@cuzureau: Если вам интересно, как парсер определяет, какая продукция применяется, я предлагаю вам не беспокоиться об этом. Напишите вашу грамматику так, чтобы применялась только одна продукция, и рассчитывайте на грамматику, чтобы найти эту продукцию :-) Это проблема только в том случае, если синтаксический анализатор не может ее понять; такое может случиться, и это частично описано в документации Bison о загадочных конфликтах. Но пока это не произойдет с вами, сосредоточьтесь на написании удобочитаемых однозначных грамматик. - person rici; 25.03.2020
comment
Большое спасибо за ВСЕ советы и за время, выделенное из вашего графика @rici! Вы очень отзывчивый и хороший наставник :-) - person cuzureau; 26.03.2020