Обобщающий пример целочисленного калькулятора из PLY в число с плавающей запятой

Я читаю первый пример из

https://github.com/dabeaz/ply

Это базовый калькулятор, позволяющий использовать только выражение, включающее '(',')','+','-','*','/', целые числа и присваивание (например, x=3), и выдавать оценку выражения (даже если его результат не является целым числом, например '3/4').

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

# -----------------------------------------------------------------------------
# calc.py
#
# A simple calculator with variables.
# -----------------------------------------------------------------------------

tokens = (
    'NAME','INTEGER', 'FLOAT',
    'PLUS','MINUS','TIMES','DIVIDE','EQUALS',
    'LPAREN','RPAREN',
    )

# Tokens

t_PLUS    = r'\+'
t_MINUS   = r'-'
t_TIMES   = r'\*'
t_DIVIDE  = r'/'
t_EQUALS  = r'='
t_LPAREN  = r'\('
t_RPAREN  = r'\)'
t_NAME    = r'[a-zA-Z_][a-zA-Z0-9_]*'

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

def t_FLOAT(t):
    r'/^(?!0\d)\d*(\.\d+)?$/mg'
    t.value = float(t.value)
    return t

# Ignored characters
t_ignore = " \t"

def t_newline(t):
    r'\n+'
    t.lexer.lineno += t.value.count("\n")

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

# Build the lexer
import ply.lex as lex
lex.lex()

# Precedence rules for the arithmetic operators
precedence = (
    ('left','PLUS','MINUS'),
    ('left','TIMES','DIVIDE'),
    ('right','UMINUS'),
    )

# dictionary of names (for storing variables)
names = { }

def p_statement_assign(p):
    'statement : NAME EQUALS expression'
    names[p[1]] = p[3]

def p_statement_expr(p):
    'statement : expression'
    print(p[1])

def p_expression_binop(p):
    '''expression : expression PLUS expression
                  | expression MINUS expression
                  | expression TIMES expression
                  | expression DIVIDE expression'''
    if p[2] == '+'  : p[0] = p[1] + p[3]
    elif p[2] == '-': p[0] = p[1] - p[3]
    elif p[2] == '*': p[0] = p[1] * p[3]
    elif p[2] == '/': p[0] = p[1] / p[3]

def p_expression_uminus(p):
    'expression : MINUS expression %prec UMINUS'
    p[0] = -p[2]

def p_expression_group(p):
    'expression : LPAREN expression RPAREN'
    p[0] = p[2]

def p_expression_integer(p):
    'expression : INTEGER'
    p[0] = p[1]

def p_expression_float(p):
    'expression : FLOAT'
    p[0] = p[1]

def p_expression_name(p):
    'expression : NAME'
    try:
        p[0] = names[p[1]]
    except LookupError:
        print("Undefined name '%s'" % p[1])
        p[0] = 0

def p_error(p):
    print("Syntax error at '%s'" % p.value)

import ply.yacc as yacc
yacc.yacc()

while True:
    try:
        s = input('calc > ')
    except EOFError:
        break
    yacc.parse(s)

у меня ошибка:

calc > 3.14+1
Illegal character '.'
Syntax error at '14'

person EricFlorentNoube    schedule 13.11.2017    source источник


Ответы (2)


ply анализирует элементы T_xxx в порядке объявления (используя отражение в вашем модуле). Здесь происходит следующее: T_INTEGER соответствует перед T_FLOAT. Таким образом, целочисленная часть вашего поплавка анализируется, а затем ply задыхается от точки.

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

Я упростил его до \d+\.\d+ (что не соответствует 1. или .9, так что это не лучший выбор), но вы можете позаимствовать лучший вариант, взятый из аналогичной проблемы: лексер PLY для чисел всегда возвращает значение типа double

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

def t_FLOAT(t):
    r'\d+\.\d+'
    # a better regex taking exponents into account:
    '[-+]?[0-9]+(\.([0-9]+)?([eE][-+]?[0-9]+)?|[eE][-+]?[0-9]+)'        
    t.value = float(t.value)
    return t

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

Как правило, для ply делайте это для всех шаблонов, которые длиннее/более специфичны, чем другие, чтобы избежать конфликтов.

person Jean-François Fabre    schedule 13.11.2017
comment
Это по-прежнему приводит к той же ошибке. Должен ли я, возможно, определить токен точки или? - person EricFlorentNoube; 13.11.2017
comment
теперь я проверил ваш код, и он работает, смотрите мое редактирование. - person Jean-François Fabre; 13.11.2017

У вас есть две проблемы в вашем файле lex. Во-первых, это порядок токенов, как объяснил Жан-Франсуа: более длинные токены должны быть определены первыми в lex (ссылка из ply doc.):

При построении главного регулярного выражения правила добавляются в следующем порядке:

  1. Все токены, определенные функциями, добавляются в том же порядке, в котором они появляются в файле лексера.
  2. Токены, определяемые строками, добавляются затем путем их сортировки в порядке уменьшения длины регулярного выражения (сначала добавляются более длинные выражения).

Но строка, определяющая токен, должна быть re совместимой строкой. Ваше определение FLOAT здесь ужасно нарушено. Если мы определим число с плавающей запятой как состоящее из ровно одной точки и необязательных цифр до или после точки, а не только точки, то приемлемым определением может быть:

r'(\d*\.\d+)|(\d+\.\d*)'

В частности, косая черта / не должна быть включена в строку...

person Serge Ballesta    schedule 13.11.2017
comment
Еще раз я терплю неудачу из-за регулярного выражения ... Большое спасибо! - person EricFlorentNoube; 13.11.2017