2

I am creating a postfix calculator that accepts arithmetic expressions and first pushes the operators to the stacl.

./pythonfilename 3 4 1 + - was used as my input. However, since no output is displayed, I tried to debug my program to see why my program is not taking in my argument. does not result in printing any output. I hit Ctrl+C to display the Traceback Call and it points out x = sys.stdin.readlines.

#!/usr/bin/python

import sys
import fileinput

class Stack:
    def __init__(self):
        self.items = []

    def isEmpty(self):
        return self.items == []

    def push(self,item):
        self.items.append(item)

    def pop(self):
        return self.items(pop)

    def peek(self):
        return self.items[len(self.items)-1]

    def size(self):
        return len(self.items)

    def is_number(line):
        try:
            float(line)
        except ValueError:
            return False        

def infixtoPostfix():
    initStack=Stack()
    x = sys.stdin.readlines() #read user input 
    for lines in x:#for lines in fileinput.input():
        for line in lines.strip().split(" "):
            if is_number(line):
                initStack.push(line)
                line = float(line)
            elif line =='+':
                firstNum = initStack.pop()
                secNum = initStack.pop()
                result = firstNum + secNum
                initStack.push(result)
                print initStack.peek()              

            elif line == '-':
                firstNum = initStack.pop()
                secNum = initStack.pop()
                result = firstNum - secNum
                initStack.push(result)
                print initStack.peek()

            elif line == '*':
                firstNum = initStack.pop()
                secNum = initStack.pop()
                result = firstNum * secNum
                initStack.push(result)
                print initStack.peek()
            elif line == "/":
                firstNum = initStack.pop()
                secNum = initStack.pop()
                result = firstNum / secNum
                initStack.push(result)
                print initStack.peek()
            elif line == "%":
                firstNum = initStack.pop()
                secNum = initStack.pop()
                result = firstNum % secNum
                initStack.push(result)
                print initStack.peek()

infixtoPostfix()
thebjorn
  • 26,297
  • 11
  • 96
  • 138
syavatkar
  • 85
  • 1
  • 1
  • 7

2 Answers2

3

The standard way to read from a pipe (cat ... | python myprog.py) is

import sys

for line in sys.stdin:
    print ">", line

line will here include the final '\n'

If instead you want to take arguments on the command line (python myprog.py 3 4 1 + -), you would use sys.argv[1:] (sys.argv[0] contains myprog.py).

To get a consistent lexing of the input you need to first check sys.argv and then split sys.stdin:

def lex_input():
    "Returns a list of tokens."
    tokens = []
    if len(sys.argv) > 1:
        tokens = sys.argv[1:]
    else:
        for line in sys.stdin:
            tokens += line.split()
    return tokens

then you just need to change your infixPostfix() function to use this token-array (instead of doing both the parsing and the evaluation in the same function).

ps: a more succinct way of writing the individual clauses would be:

elif token == '+':
    push(pop() + pop())

but it depends on what you're trying to accomplish..

Update: full'ish solution

Update2: with debug statements to visualize the stack (removed Stack class in favor of a regular list for brevity)

import sys

STACK = []
push = STACK.append
pop = STACK.pop

OPERATIONS = {
    '+': lambda b, a: a + b,
    '-': lambda b, a: a - b,
    '*': lambda b, a: b * a,
    '/': lambda b, a: b / a,
}

def infixtoPostfix(tokens):
    print '%-15s %5s %-15s' % ('STACK before', 'token', 'STACK after')
    print '-'*15, '-'*5, '-'*15

    for token in tokens:
        print '%15s %5r' % (STACK, token),

        if token not in OPERATIONS:
            push(int(token))
        else:
            push(OPERATIONS[token](pop(), pop()))

        print '%15s' % STACK

def lex_input():
    "Returns a list of tokens."
    tokens = []
    if len(sys.argv) > 1:
        tokens = sys.argv[1:]
    else:
        for line in sys.stdin:
            tokens += line.split()
    return tokens

if __name__ == "__main__":
    infixtoPostfix(lex_input())
    # well formed programs should leave a single value on the STACK
    print "\nResult is:", STACK[0]

testing:

(dev) go|c:\srv> python rpn.py 3 4 1 + -
STACK before    token STACK after
--------------- ----- ---------------
             []   '3'             [3]
            [3]   '4'          [3, 4]
         [3, 4]   '1'       [3, 4, 1]
      [3, 4, 1]   '+'          [3, 5]
         [3, 5]   '-'            [-2]

Result is: -2

(cat rpn.txt | python rpn.py will output the same thing if rpn.txt contains 3 4 1 + -).

If you try an rpn program with a syntax error, then the program will raise an exception, e.g.:

(dev) go|c:\srv> python rpn.py 3 4 + -
STACK before    token STACK after
--------------- ----- ---------------
             []   '3'             [3]
            [3]   '4'          [3, 4]
         [3, 4]   '+'             [7]
            [7]   '-'
Traceback (most recent call last):
  File "rpn.py", line 60, in <module>
    infixtoPostfix(lex_input())
  File "rpn.py", line 45, in infixtoPostfix
    push(OPERATIONS[token](pop(), pop()))
  File "rpn.py", line 26, in pop
    return STACK.pop()
IndexError: pop from empty list

In a real compiler that would be bad, since you don't want the end user to see details of your implementation. Instead you'd want to give them a diagnostic error message, with the exact place where your program found it.

In this case that isn't that difficult. I've omitted the debug statements to print the stack:

def infixtoPostfix(tokens):
    # make a copy of the input, for use in error handling
    input_tokens = tokens[:]  
    try:
        for i, token in enumerate(tokens):
            if token not in OPERATIONS:
                push(int(token))
            else:
                push(OPERATIONS[token](pop(), pop()))
    except IndexError:
        print 'Detected Syntax Error at token no.:', i + 1  # people count from 1..
        print ' '.join(input_tokens)
        print '%s%s' % ('-' * (1 + len(' '.join(input_tokens[:i]))), '^')
        push('SYNTAX ERROR')  # the top of the stack contains the result of the current operation..

a small change to the result printing is necessary, printing the last element in the list (STACK[-1]) which is the top of the stack instead of relying on the list/stack only having one element at the end:

if __name__ == "__main__":
    infixtoPostfix(lex_input())
    # well formed programs should leave a single value on the STACK
    print "\nResult is:", STACK[-1]

if we feed this version our program with a syntax error:

(dev) go|c:\srv> python rpn.py 34 4 + -
Detected Syntax Error at token no.: 4
34 4 + -
-------^

Result is: SYNTAX ERROR

we get a proper error message, with a little pointy 'graphic' indicating where the error was detected.

We could go further since we know that all of our operations take two elements on the stack, and give an even more detailed error message, e.g.:

Syntax Error at token "-":  Stack underflow
   The "-" operation requires two stack arguments and the stack 
   contained only one:

        Stack      token
        ---------- -----
        [37]        '-'

I'll leave the implementation of that as an exercise.

As you can see, even in this simple example there is more error handling code than evaluation code and that isn't too surprising when writing simple compilers.

thebjorn
  • 26,297
  • 11
  • 96
  • 138
  • I did not make the changes you suggested yet as I first tested cat test.txt | python something.py and I recieved an error saying pop is not defined. – syavatkar Oct 31 '15 at 02:57
  • Traceback (most recent call last): -bash: syntax error near unexpected token `most' File "something.py", line 75, in -bash: syntax error near unexpected token `newline' s$ infixtoPostfix() > File "something.py", line 43, in infixtoPostfix -bash: syntax error near unexpected token `File' firstNum = initStack.pop() -bash: syntax error near unexpected token `(' File "something.py", line 17, in pop – syavatkar Oct 31 '15 at 02:59
  • The ps was maybe a little too abstract.. It was meant as: "if you define push and pop as simple functions (since you're only working with one stack), then you can simplify the evaluation". It was just an aside to your question about tokenizing. – thebjorn Oct 31 '15 at 03:01
  • Looks like we were communicating on different levels, so I updated my answer with a full solution based on my comments. (I also made Stack a subclass of `list` since the `list` since much of the functionality is shared/similar). – thebjorn Oct 31 '15 at 03:23
  • Nice solution bjorn. But, I would still like to fix my code. I managed to fix the error for global name pop not defined by changing self.items.(pop) to self.items.pop() on line 17. However when I ran my code, I ended up with a different error for pop saying Indexerror: pop from empty list. Any idea what I might be doing wrong here? – syavatkar Oct 31 '15 at 03:49
  • You'll get a pop from empty list if you feed it a program with more operations than data, eg. `rpn.py 3 4 + +`. After each token the stack will be `[3]`, `[3, 4]`, `[7]`, and then the last `+` tries to pop 2 elements off the stack (the second pop will try to "pop from empty list"). – thebjorn Oct 31 '15 at 13:23
  • See my updated answer for how to debug/detect/report error conditions. I've also changed the semantics (check the `OPERATIONS` dict) a bit so `7 4 -` returns `3` (instead of `-3`) since that seems more common/correct :-) – thebjorn Oct 31 '15 at 14:21
0

./pythonfilename 3 4 1 + -

actually 3 4 1 + - are passed as argument not as input .

if you want to read from a file please use open('filename')

use ./pythonfilename '3 4 1 + -'

x = [sys.argv[1]]

in place of x = sys.stdin.readlines()

However then your code only process single input as argument

Saiful Azad
  • 1,823
  • 3
  • 17
  • 27
  • My apologies. Is this the correct method to pass the argument. I am actually trying to pass in the argument using cat – syavatkar Oct 31 '15 at 02:07
  • why you r not passing input file as program argument ? ./pythonfilename filename . This is the most common convention. – Saiful Azad Oct 31 '15 at 02:15
  • Yeah I want it to do both. If a filename is not passed it showed read the arguments passed in console – syavatkar Oct 31 '15 at 02:17
  • so use ./pythonfilename '3 4 1 + -' x = [sys.argv[1]] in place of x = sys.stdin.readlines() However then your code only process single input as argument – Saiful Azad Oct 31 '15 at 02:23