Source file rpn.icn
#
# A simple reverse-polish calculator (works with numeric and string operations)
#
#   Accepts numeric and string operands, operations, and function calls.
#   Strings that "look like" variables can be used as variables.
#
#   Note: given an overloaded operator/function, this
#          always calls the largest (one with the most arguments).
#          However, can fool by using 'nil' as arguments to ignore.
#          So, for example:
#<pre>
#               nil "abcdef" *
#</pre>
#          outputs '6'.
#   Note: cannot handle functions with more than 3 arguments.
#   Note: command line arguments are handled first

global mem          # simulates the calculator's memory...
global stack        #    and the rpn stack...

invocable all

procedure main(params)
   local token, opcode, argl, v

   stack := []
   mem   := table()
   mem["pi"] := &pi
   mem["e"]  := &e

   exit :=: Exit
   every token := gettoken(params) do {

      if opcode := proc(token, 3|2|1) then {

         case token of {

            ":=" : {    # special case assignment...
               if mem[var(v := Pop(stack))\1] := deref(Pop(stack)) then
                  push(stack,v) & Write(deref(v))
               else
                  write("\t\t", "assignment not done!")
               }

           ":=:": {    # ...and swap
               if mem[var(v := Pop(stack))] :=: mem[var(Pop(stack))] then
                  push(stack,v) & Write(deref(v))
               else
                  write("\t\t", "assignment not done!")
               }

            default: { # others are easy (simple operations on rvalues)
               argl := []
               every 1 to abs(args(opcode)) do {
                  arg := Pop(stack) | break next
                  # ignore "nil" arguments (used to override the
                  #   'apply opcode with most arguments' rule).
                  push(argl, deref("nil" ~== arg))
                  }
               # Recompute in case arg list has shrunk
               opcode := proc(token, *argl)
               &error := -1  # trap runtime errors during invocation
               every push(stack, Write(opcode!argl))
               if &error < -1 then {
                   write("\t\tRuntime error on ",token,": ",&errortext)
                   }
               &error := 0
               }
            }

         }

      else push(stack, tokType(token)) |
           write("\t\t", token,"\t: Invalid entry")
      }

end

# generate the tokens in the input stream
#
procedure gettoken(params)
   static NS := ~' \t'
   every (!\params | |read()) ? {
       while tab(upto(NS)) do {
           if ="\"" then suspend "\""||tab(upto("\"")+1) \ 1 # " (sigh)
           else suspend tab(many(NS)) \ 1
           }
       }
end

# Identify a token as a value or a variable
#
procedure tokType(s)
   s ? return (numeric(tab(0)) | s)
end

# If stack is empty, write an error message and fail
#
procedure Pop(stack)
    suspend pop(stack)
    write(&errout,"\t\t\t: Stack empty")
end

# return s only if it's a legal variable name...
#
procedure var(s)
   static firstch := &letters++"_", restch := firstch++&digits
   return s ? 1(tab(any(firstch))||(tab(many(restch))|""), pos(0))
end

# Dereference 's'.
#
procedure deref(s)
   return numeric(s) | (string(s) ? 2(="\"",tab(-1),="\"")) | \mem[s] | s
end

# Produce printable form of a value.
#
procedure printable(x)
   return (numeric|string|image)(x)
end

# Display a value
#
procedure Write(v)
   write("\t-> ",printable(v))
   return v
end

# Quit the calculator
#
procedure Exit()
   Exit(0)
end

# Some useful functions to allow in addition to the usual Icon/Unicon ones...

# Duplicate the value on top of the stack
#
procedure dup()
   push(stack, stack[1])
end

# Convert a value on the stack to a string
#
procedure tostring()
   stack[1] ?:= if not ="\"" then "\""||tab(0)||"\"" # " (keep beautifier happy)
end

# Display calculator stack
#
procedure showstack()
   writes("\t\tStack is:")
   every writes(" ",printable(!stack))
   write()
end

# Display calculator memory
#
procedure showmem()
   write("\t\tMemory contents:")
   every pair := !sort(mem) do
      write("\t\t\t",right(pair[1],10)," -> ",right(printable(pair[2]),20," ."))
end

# Flip top two on stack
#
procedure flip()
   stack[1] :=: stack[2]
end

# Pop off stack top
#
procedure popstack()
   Pop(stack)
end

# Turn on tracing
#
procedure traceon()
   &trace := -1
end

# Turn off tracing
#
procedure traceoff()
   &trace := 0
end

# Display a help message
#
procedure help()
   write("RPN - a reverse polish notation calculator")
   write()
   write("\tAccepts most Icon operators and functions.")
   write()
   write("\tSpecial operations are:")
   write()
   write("\t\tdup       -- duplicate the top of stack")
   write("\t\ttostring  -- force string value (not a variable)")
   write("\t\tshowmem   -- display calculator memory")
   write("\t\tshowstack -- display calculator stack")
   write("\t\tflip      -- flip top two stack elements")
   write("\t\tpopstack  -- pop top stack element")
   write("\t\ttraceon   -- turn tracing on")
   write("\t\ttraceoff  -- turn tracing off")
   write()
end

This page produced by UniDoc on 2021/04/15 @ 23:59:43.