PLY parser - присвоение уравнений - PullRequest
0 голосов
/ 01 мая 2020

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

Способ, которым я назначаю переменные: x = 10 (в dict * ключом будет 1004 * и 10 значение)

Способ, которым я хочу иметь возможность назначить уравнение: fun(x) = x + 42 (в диктовке fun будет ключом, а кортеж ('x', 'x+10') будет значением).

Он работает с этой надписью fun|x| = x + 42 (обратите внимание на символ 'pipe' здесь). Но это не работает с круглыми скобками.

Как я могу заставить его работать правильно?

Вот мой код:

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

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

tokens = (
    'NAME',
    'NUMBER',
)

t_NAME      = r'[a-zA-Z]+'
literals    = '+=-*/|()'
t_ignore    = " \t"

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

def t_error(t):
    print('Illegal character \'{}\''.format(t.value[0]))
    t.lexer.skip(1)

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

functions = {}
variables = {}

def p_operations(p):
    """ statement : expression
    expression : fifth
    fifth : fourth
    fourth : third
    third : second
    second : first
    first : NUMBER
    """
    p[0] = p[1]

def p_plus(p):
    """ fifth : fifth '+' fourth """
    if isinstance(p[1], str) or isinstance(p[3], str):
        p[0] = str(p[1]) + p[2] + str(p[3])
    else:
        p[0] = p[1] + p[3]

def p_minus(p):
    """ fifth : fifth '-' fourth """
    if isinstance(p[1], str) or isinstance(p[3], str):
        p[0] = str(p[1]) + p[2] + str(p[3])
    else:
        p[0] = p[1] - p[3]

def p_implicit_times(p):
    """ fourth : fourth second """
    if isinstance(p[1], str) or isinstance(p[2], str):
        p[0] = str(p[1]) + str(p[2])
    else:
        p[0] = p[1] * p[2]

def p_times(p):
    """ fourth : fourth '*' third """
    if isinstance(p[1], str) or isinstance(p[3], str):
        p[0] = str(p[1]) + p[2] + str(p[3])
    else:
        p[0] = p[1] * p[3]

def p_divide(p):
    """ fourth : fourth '/' third """
    if isinstance(p[1], str) or isinstance(p[3], str):
        p[0] = str(p[1]) + p[2] + str(p[3])
    else:
        p[0] = p[1] / p[3]

def p_unary_minus(p):
    """ third : '-' third """
    if isinstance(p[2], str):
        p[0] = '-' + p[2]
    else:
        p[0] = -p[2]

def p_power(p):
    """ second : first '^' third """
    if isinstance(p[1], str) or isinstance(p[3], str):
        p[0] = str(p[1]) + p[2] + str(p[3])
    else:
        p[0] = p[1] ** p[3]

def p_block(p):
    """ first : '(' expression ')' """
    p[0] = p[2]


################################ PROBLEM HERE ############################

def p_statement_assign(p):
    ''' statement : NAME '=' expression '''
    variables[p[1]] = p[3]
    p[0] = p[3]

def p_function_assign(p):
    ''' statement : NAME '|' expression '|' '=' expression '''
    functions[p[1]] = (p[3], p[6])
    p[0] = functions[p[1]]

def p_variable_expr(p):
    ''' first : NAME '''
    try : 
        p[0] = variables[p[1]]
    except:
        p[0] = p[1]

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

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

lexer = lex.lex() 
parser = yacc.yacc()

while True:
    try:
        question = raw_input('>>> ')
    except:
        question = input('>>> ')

    answer = parser.parse(question)
    if answer is not None:
        print(answer)

1 Ответ

1 голос
/ 01 мая 2020

Вы разрешаете неявное умножение. Это означает, что f(x) может быть проанализировано как произведение, и в этом случае f должно быть уменьшено до fourth согласно правилу неявного умножения. Но если его нужно проанализировать как присвоение, его нужно оставить как NAME. Это конфликт с уменьшением сдвига, который можно легко увидеть в parser.out:

state 3

    (16) statement -> NAME . = expression
    (17) statement -> NAME . ( expression ) = expression
    (18) first -> NAME .

  ! shift/reduce conflict for ( resolved as shift

Здесь вы видите, что когда анализатор видит NAME, за которым следует (, он не знает следует ли уменьшить NAME до first (а затем до second и т. д. c. до fourth) в ожидании, что оператор является простым вычислением, или сместить (, тем самым взяв на себя обязательство рассматривать это как определение функции.

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

statement : NAME '(' NAME ')' '=' expression

, но вы заменили второй NAME с expression. Это позволит избежать конфликта сдвига-уменьшения до ) за счет принятия сомнительных определений функций (f(a+3) = 2a).

Можно сделать нечто подобное, чтобы избежать этого конфликта сдвига-уменьшения (но это очень ad ho c решение):

statement : fourth '(' expression ')' '=' expression

Это «работает» в том смысле, что оно принимает правильные выражения. Но он также молча (или несколько молча) принимает несколько других выражений:

Это хорошо:

>>> f(a) = a + 3
('a', 'a+3')

Но это странно:

>>> -f(a) = a + 3
('a', 'a+3')
>>> 3f(a) = a + 3
('a', 'a+3')
>>> 3f(a+2) = a + 3
('a+2', 'a+3')

В качестве альтернативы, вы можете игнорировать конфликт сдвиг-уменьшение, так как PLY по умолчанию выполнит сдвиг (как сказано в parser.out: «разрешен как сдвиг»). Это не позволит парсеру принять странные примеры, приведенные выше, но также неправильно проанализирует некоторые выражения, которые могут показаться разумными:

Эти выражения кажутся подходящими:

>>> f(a) = a + 3
('a', 'a+3')
>>> -f(a) = a + 3
Syntax error!
a+3
>>> 3f(a) = a + 3
Syntax error!
a+3

Но мы могли бы ожидать этого напечатать 105:

>>> a=7
7
>>> a(2a+1)
Syntax error!

Если вас это не беспокоит, вы можете прекратить читать сейчас.


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

statement : NAME '(' NAME ')' '=' expression

или, если вы хотите разрешить функции с несколькими аргументами:

statement : NAME '(' name_list ')' '=' expression
name_list : NAME
name_list : name_list ',' NAME

Но это не LR (1), а это будет очень трудно сделать LR (1). Оба предложенных выше синтаксиса - это LR (4), и, поскольку каждая грамматика LR (k) теоретически может быть механически преобразована в (очень раздутую и трудно читаемую) грамматику LR (1), решение должно существовать. Это не будет красиво, однако.

(Фактический синтаксис, который вы используете, с expression, не является LR (k) для любого k, потому что expression может быть произвольно длинной, а анализатор имеет чтобы увидеть за пределами списка аргументов, чтобы решить, следует ли уменьшить первую NAME.)

Поскольку грамматика однозначна, вы можете проанализировать ее с помощью GLR / GLL / Earley / et c , синтаксический анализатор, но PLY не производит их, и я не знаю генератора синтаксического анализатора Python, который это делает (хотя это не означает, что он не существует). Существуют различные генераторы синтаксического анализатора GLR для других языков.

Однако с PLY лучше всего использовать общий синтаксис, показанный выше, в качестве ad ho c решения, а затем решить проблему принятия неверного определения, выполнив проверку в действии semanti c.

Однако эта проверка будет немного сложнее, если вы не укусите пулю и не перейдете на анализатор, который выдает AST вместо выполнения немедленной оценки, как мы обсуждали несколько раз. Если значения semanti c являются AST, то будет просто проверить, что p[1] и p[3] являются простыми именами в функции действия для

statement : fourth '(' expression ')' '=' expression

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

...