0

I would like to ask for help for an exercise to do a calculator which recognizes the English words and numbers in Python but now using PLY (Python Lex-Yacc)

The numbers and the operators can be given in two forms written as a string using English words, "plus" = "+", "two" = 2, "hundred twelve" = 112, etc.

An example could be these entries:

"twenty five divided by 5" or "25 / 5" or "twenty five divided by five"

the result should be the same, a number 5 (not a string).

" -3 times 4" will give -12

Division by 0 will give "Error" " 34 divided by 0" will give "Error"

This should work for several basic operators "-","+","x" and "/" (minus, plus, times and divided by) either if I type the mathematical symbols or I type in text or mixed.

Here are some parts of my code:

# ------- Calculator tokenizing rules

tokens = (
    'NAME','NUMBER', 'times', 'divided_by', 'plus', 'minus'
)

literals = ['=','+','-','*','/', '(',')']

t_ignore = " \t"

t_plus    = r'\+'
t_minus   = r'-'
t_times   = r'\*'
t_divided_by  = r'/'
t_NAME    = r'[a-zA-Z_][a-zA-Z0-9_]*'

  precedence = (
    ('left','+','-'),
    ('left','plus','minus'),
    ('left','times','divided_by'),
    ('left','*','/'),
    ('right','UMINUS'),
)

#Changed here the assigment def p_statement_assign(p): 'statement : expression times divided_by plus minus expression' variables[p[1]] = p[3] p[0] = None

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

def p_expression_binop(p):
    '''expression : expression '+' expression
                  | expression 'plus' expression
                  | expression '-' expression
                  | expression 'minus' expression
                  | expression '*' expression
                  | expression 'times' expression
                  | expression 'divided_by' expression
                  | expression '/' 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]

Are my tokens having a bad definition? How I can tell that the number can be introduce in English letter or with numbers?

The expression (p[2] == '+' : p[0] = p[1] + p[3]) has to have a single character. Why is not valid to write in this form p[2] == 'plus' : p[0] = p[1] + p[3] ?


I have added the code suggested by sfk, but I have still the problem to recognize the numbers and operators entered as text, in english words.

Generating LALR tables
WARNING: 12 shift/reduce conflicts
 Enter your input: calc > one + two
Undefined name 'one'
Undefined name 'two'
P1 is :  0
 Enter your input: calc > 1+2
P1 is :  3
3
 Enter your input: calc > 1 plus 2
Syntax error at 'plus'
P1 is :  2
2

Do you have any idea about what I am doing wrong?

vvvvv
  • 25,404
  • 19
  • 49
  • 81
orubiop
  • 44
  • 1
  • 9
  • What do you mean by "...not valid to write in this form"? Technically, in Python, the expression `if p[2] == 'plus' : p[0] = p[1] + p[3]` is proper syntax. You haven't indicated exactly what error you're seeing. – lurker Sep 14 '13 at 14:03
  • Sorry, I did not see this commemt before, the error was it is expected a single character, '+' and not 'plus' but plus (without quotations is fine). I have still not managed to get it working with text. Any help will be appreacited. – orubiop Sep 17 '13 at 12:49

1 Answers1

1

First, add token definition for english words

t_plustext    = r'plus'

Add those new tokens to tokens

tokens = (
    'NAME','NUMBER', 'times', 'divided_by', 'plus', 'minus', 'plustext', ....
)

Finally, use those new token in you grammar this way :

def p_expression_binop(p):
    '''expression : expression '+' expression
                  | expression plustext expression
    '''

UPDATE : here is a working subset of the grammar

#!/usr/bin/python

from __future__ import print_function

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

# ------- Calculator tokenizing rules

tokens = (
    'NUMBER', 'times', 'divided_by', 'plus', 'minus', 'plustext',
    'one', 'two', 'three',
)

literals = ['=','+','-','*','/', '(',')']

t_ignore = " \t\n"

t_plustext    = r'plus'
t_plus    = r'\+'
t_minus   = r'-'
t_times   = r'\*'
t_divided_by  = r'/'
t_one = 'one'
t_two = 'two'
t_three = 'three'

def t_NUMBER(t):
    r'\d+'
    try:
        t.value = int(t.value)
    except ValueError:
        print("Integer value too large %d", t.value)
        t.value = 0
    return t

precedence = (
    ('left','+','-','plustext'),
    ('left','times','divided_by'),
    ('left','*','/'),
)


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

def p_expression_binop(p):
    '''expression : expression '+' expression
                  | expression plustext expression
                  | expression '-' expression
                  | expression '*' expression
                  | expression '/' 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]
    elif p[2] == 'plus': p[0] = p[1] + p[3]

def p_statement_lit(p):
    '''expression : NUMBER
          | TXTNUMBER
    '''
    p[0] = p[1]

def p_txtnumber(p):
    '''TXTNUMBER : one
         | two
         | three
    '''
    p[0] = w2n(p[1])

def w2n(s):
    if s == 'one': return 1
    elif s == 'two': return 2
    elif s == 'three': return 3
    assert(False)
    # See http://stackoverflow.com/questions/493174/is-there-a-way-to-convert-number-words-to-integers-python for a complete implementation

def process(data):
    lex.lex()
        yacc.yacc()
        #yacc.parse(data, debug=1, tracking=True)
        yacc.parse(data)

if __name__ == "__main__":
        data = open(sys.argv[1]).read()
        process(data)
sfk
  • 709
  • 5
  • 14
  • Thanks, for the reply, but it seems does not work. Could you be more specific, or tell me an example for the addtion for example with an input text as "1 plus two" will give the answer 3 – orubiop Sep 14 '13 at 19:59
  • Another question, is my def_pstament_assignp(p) incorrect, I have seen you do not use it #Changed here the assigment def p_statement_assign(p): 'statement : expression' variables[p[1]] = p[3] p[0] = None print(p[1]) #Adding print def p_statement_expr(p): 'statement : expression' p[0] = p[1] print(p[1]) – orubiop Sep 15 '13 at 12:35
  • Is the assignment part of your grammar ? as I understand it, the aim was to print the result, not to assign values to var. Could you plase clarify ? – sfk Sep 15 '13 at 13:33
  • Yes, the result should be printed in the stdout, not to get assigment, I am now confused If I need this declaration. I do not understand why the words 'plus' or ' minus' are not working. PLease check the last edit I did to my original question. – orubiop Sep 15 '13 at 13:43
  • As "plus" is supposed to be a token, you need to define it as such in **lex** : ie you need something like this `t_plustext = r'plus'` (note that "+" and "plus" will be 2 different tokens). Then, in **yacc** you must use `plustext`and not `'plustext'` (the `'x'`is only valid for single char) – sfk Sep 15 '13 at 13:59
  • Ok, I have now added, I think I had it before, but ' def p_expression_binop(p): '''expression : expression '+' expression | expression '-' expression | expression plus expression | expression plus_text expression ''' if p[2] == '+' : p[0] = p[1] + p[3] elif p[2] == plus: p[0] = p[1] + p[3] elif p[2] == plus_text: 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] Is this what you mean? – orubiop Sep 15 '13 at 15:10
  • Would you like to give a look to my entire code ? Or dicuss it by email. Thanks. – orubiop Sep 15 '13 at 16:57
  • Here is the link valid for 1 Day. http://pastebin.com/iaNx1a5c. I would like to have your help in completing the code and suggestions. – orubiop Sep 20 '13 at 12:26
  • Here is an updated version. You were almost there :) http://pastebin.com/ybd3tkPj – sfk Sep 20 '13 at 18:35