'===========================================================================
' Subject: RPN POSTFIX NOTATION EVAULATOR     Date: 01-28-97 (14:30)       
'  Author: The ABC Programmer                 Code: QB, QBasic, PDS        
'  Origin: voxel@freenet.edmonton.ab.ca     Packet: ALGOR.ABC
'===========================================================================
'=========================================================================
' Reverse Polish Notation (RPN) Evaluator Programmed by William Yu (01/97)
'
'            Infix Notation : 4 + (3 - 2) * 9 ^ 2
'   Equivalent Postfix (RPN): 4 3 2 - 9 2 ^ * +
'
' Please check ALGOR.ABC for the useful INFIX TO POSTFIX (RPN) CONVERTER.
' Both demonstrate the fundamentals and uses of stacks.
'=========================================================================

DECLARE FUNCTION EmptyStack% (Stack AS INTEGER)
DECLARE SUB POP (Stack AS INTEGER, Element AS DOUBLE)
DECLARE SUB GetNextToken (RPNExp AS STRING, Position AS INTEGER, Token AS ANY, TokenItem AS STRING)
DECLARE SUB PUSH (Stack AS INTEGER, Element AS DOUBLE)
DECLARE FUNCTION Evaluate# (RPNExp AS STRING)
DEFINT A-Z

CONST FALSE = 0
CONST TRUE = NOT FALSE

' Token item
CONST Operand = 1         ' TIP: Use descriptive identifiers };-)
CONST Operator = 2        ' So you don't have to waste time documenting.
CONST Bad = 3
CONST Finished = 4

'Error codes
CONST None = 0
CONST FewOperands = 1
CONST FewOperator = 2
CONST Invalid = 3
CONST NoResult = 4

CONST Max = 100      ' # of items (operands only) to fill stack until full

DIM RPNExp AS STRING
DIM SHARED Element(1 TO Max) AS DOUBLE
DIM SHARED ErrorCode

' One heck of an expression :)
' Needs
'WIDTH 80, 43
' to view all the steps
' End the expression with ; (semi-colon)

RPNExp = "+4.546 2.6734^ 5656 -438.390 /* 6234  -234 99*564- 23 432 457 234 *+/*-+;"

' Other examples to uncomment.  Create one!
'RPNExp = "4 6 +;"
'RPNExp = "-34.3 345 / 345 -345* 45 +-;"
'RPNExp = "45.345 -.345*;"
'RPNExp = "34 8 ^ 85 10 + *;"

CLS
PRINT "Expression is: "; RPNExp

Number# = Evaluate(RPNExp)
IF ErrorCode = None THEN
  PRINT "Expression Value is:"; Number#
ELSE
  PRINT : PRINT "*** BAD EXPRESSION : ";
  SELECT CASE ErrorCode
    CASE FewOperands
      PRINT "Too few operands. ***"
    CASE FewOperator
      PRINT "Too few operators. ***"
    CASE Invalid
      PRINT "Invalid input (bad character) ***"
    CASE NoResult
      PRINT "Nothing to evaluate! ***"
  END SELECT
END IF

FUNCTION EmptyStack (Stack AS INTEGER)
'----------------------------------------------
'  Simply checks if stack is empty
'----------------------------------------------

  EmptyStack = FALSE
  IF Stack = 0 THEN EmptyStack = TRUE

END FUNCTION

FUNCTION Evaluate# (RPNExp AS STRING)
'----------------------------------------------
'  Evaluate the RPN Expression
'----------------------------------------------

DIM TokenItem AS STRING
DIM Number AS DOUBLE, Number2 AS DOUBLE

   Position = 1      ' Parse string from position 1
   Stack = 0         ' Initialize Stack, ie. make it empty
   ErrorCode = None

   DO
     GetNextToken RPNExp, Position, Token, TokenItem
     PRINT "TOKEN = "; TokenItem;
     SELECT CASE Token
       CASE Operand
         Number = VAL(TokenItem)
         PRINT "  PUSH = "; Number
         PUSH Stack, Number
       CASE Operator
         POP Stack, Number
         IF EmptyStack(Stack) THEN ErrorCode = FewOperands: EXIT DO
         POP Stack, Number2
         PRINT "  POP ="; Number; " POP ="; Number2
         SELECT CASE TokenItem
           CASE "+"
             Number = Number + Number2
           CASE "-"
             Number = Number2 - Number
           CASE "*"
             Number = Number * Number2
           CASE "/"
             Number = Number2 / Number
           CASE "^"
             Number = Number2 ^ Number
         END SELECT
         PUSH Stack, Number
         PRINT " PUSH ="; Number
       CASE Finished
         IF EmptyStack(Stack) THEN
           ErrorCode = NoResult
         ELSE
           POP Stack, Number
           PRINT " POP ="; Number
         END IF
       CASE Bad
         ErrorCode = Invalid
         EXIT DO
     END SELECT
   LOOP UNTIL Token = Finished
   IF NOT EmptyStack(Stack) THEN ErrorCode = FewOperator

   Evaluate# = Number

END FUNCTION

SUB GetNextToken (RPNExp AS STRING, Position AS INTEGER, Token AS INTEGER, TokenItem AS STRING)
'------------------------------------------------------------------------
'  Parse Expression until a token (Operand/Operator/EndOfExpr) is found.
'------------------------------------------------------------------------

  Start = Position
  Done = FALSE
  Token = Finished
  Numbers$ = "0123456789."
  Signs$ = " +-*/^"

  DO
    Char$ = MID$(RPNExp, Position, 1)
    SELECT CASE Char$
      CASE "0" TO "9", "."
        Token = Operand
        Check$ = MID$(RPNExp, Position + 1, 1)
        Check = FALSE
        FOR I = 1 TO LEN(Signs$)
          IF Check$ = MID$(Signs$, I, 1) THEN Check = TRUE
        NEXT I
        IF Check THEN Done = TRUE
      CASE " "
        Start = Start + 1
      CASE "+", "-"
        Check$ = MID$(RPNExp, Position + 1, 1)
        Check = FALSE
        FOR I = 1 TO LEN(Numbers$)
          IF Check$ = MID$(Numbers$, I, 1) THEN Check = TRUE
        NEXT I
        IF Check THEN                   ' Check for strings like 123+123
          IF Token = Operand THEN       ' which is not valid.
            Token = Bad                 ' (Where + can be any operator)
            Done = TRUE
          ELSE                          ' Although you can have -123
            Token = Operand             ' so we continue to parse...
          END IF
        ELSE                            ' Else it's 123+ or just +
          Token = Operator              ' So we perform the operation.
          Done = TRUE                   ' (Again, + can be any operator)
        END IF
      CASE "*", "/", "^"
        Token = Operator
        Done = TRUE
      CASE ";"
        Done = TRUE
        Token = Finished
      CASE ELSE
        Token = Bad
        Done = TRUE
    END SELECT
    Position = Position + 1
  LOOP UNTIL Done OR Position > LEN(RPNExp)
 
  TokenItem = MID$(RPNExp, Start, Position - Start)

END SUB

SUB POP (Stack AS INTEGER, Element AS DOUBLE)
'----------------------------------------------
'  Return the top element on the stack.
'----------------------------------------------

IF Stack = 0 THEN
  PRINT " *** Attempt to pop from an empty stack ***"
ELSE
  Element = Element(Stack)
  Stack = Stack - 1
END IF

END SUB

SUB PUSH (Stack AS INTEGER, Element AS DOUBLE)
'----------------------------------------------
'  Fill stack with Element
'----------------------------------------------

Stack = Stack + 1
IF Stack > Max THEN
  PRINT " *** Attempt to push on a full stack ***"
ELSE
  Element(Stack) = Element
END IF

END SUB
