将整数计算器示例从PLY推广到浮点数

时间:2017-11-13 09:28:12

标签: python yacc lex 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'

2 个答案:

答案 0 :(得分:1)

按照声明的顺序解析T_xxx成员(使用模块上的反射)。这里发生的是T_INTEGERT_FLOAT之前匹配。因此解析了浮点数的整数部分,然后点上的ply扼流圈。

如果您的浮动正则表达式没有关闭(在我的第一个回答中完全错过了这一点,被明显错误的顺序蒙蔽了),这将直接起作用。

我已将其简化为\d+\.\d+(不匹配1..9所以不是最佳选择),但您可以借一个更好的选择来自类似的问题:PLY lexer for numbers always returns double

您必须在 T_FLOAT之前解析 {em}。只需交换两个声明即可:

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 的一般规则,对所有比其他模式更长/更具体的模式执行此操作以避免冲突。

答案 1 :(得分:1)

你的lex文件中有两个问题。首先是Jean-François解释的令牌顺序:较长的令牌必须首先在lex中定义(参考ply doc。):

  

构建主正则表达式时,将按以下顺序添加规则:

     
      
  1. 函数定义的所有标记的添加顺序与它们在词法分析器文件中的显示顺序相同
  2.   
  3. 接下来通过按正则表达式长度递减的顺序对字符串定义的标记进行排序(首先添加较长的表达式)。
  4.   

但是定义令牌的字符串应该是re兼容的字符串。你的FLOAT定义在这里被严重破坏了。如果我们将浮点数定义为只包含一个点,并且在点之前或之后的可选数字而不是单独的点,则可接受的定义可能是:

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

特别是,闪烁/不应包含在字符串中......