############################################################################
#
#	Name:	calc.icn
#
#	Title:	Desk calculator
#
#	Author:	Ralph E. Griswold
#
#	Date:	February 22, 1990
#
############################################################################
#
#  This is a simple Polish "desk calculator".  It accepts as values Icon
#  integers, reals, csets, and strings (as they would appear in an Icon
#  program). Other lines of input are interpreted as operations. These
#  may be Icon operators, functions, or the special instructions listed
#  below.
#
#  In the case of operator symbols, such as +, that correspond to both unary
#  and binary operations, the binary one is used.  Thus, the unary operation
#  is not available.
#
#  In case of Icon functions like write() that take an arbitrary number of
#  arguments, one argument is used.
#
#  The special instructions are:
#
#	clear	remove all values from the calculator's stack
#	dump	write out the contents of the stack
#	print	print the top value on the stack, but do not remove it
#	quit	exit the calculator
#
#  Example: the input lines
#
#	"abc"
#	3
#	repl
#	print
#
#  prints "abcabcabc" and leaves this the only value on the stack.
#
#  Failure and most errors are detected, but in these case, arguments are
#  consumed and not restored to the stack.
#
############################################################################

global stack

procedure main()
   local line, p, n, arglist

   stack := []

   while line := read() do {
      push(stack,value(line)) | {	# if it's a value, push it
         case line of {	# else check special operations
            "clear":   {stack := []; next}
            "dump":    {every write(image(!stack)); next}
            "print":   {write(image(stack[1])); next}
            "quit":    exit()
            }      
         if p := proc(line,3 | 2 | 1) then {	# check for procedure
            n := abs(args(p))
            arglist := []
            every 1 to n do
               push(arglist,pop(stack)) | {
                  write(&errout,"*** not enough arguments ***")
                  break next
                  }
            &error := 1	# anticipate possible error
            push(stack,p!arglist) | {
               if &error = 0 then {
                  write(&errout,"*** error performing ",line)
                  }
               else write(&errout,"*** failure performing ",line)
               }
            }
         else write(&errout,"*** invalid input: ",line)
         }
      }
end

#  Check input to see if it's a value
#
procedure value(s)
   local n

   if n := numeric(s) then return n
   else {
      s ? {
         if ="\"" & s := tab(-1) & ="\"" then return escape(s)
         else if ="'" & s := tab(-1) & ="'" then return cset(escape(s))
         else fail
         }
      }
end

#  Handling escape sequences is no fun
#
procedure escape(s)
   local ns, c

   ns := ""
   s ? {
      while ns ||:= tab(upto('\\')) do {
         move(1)
         ns ||:= case c := map(move(1 | 0)) of {	# can be either case
            "b":  "\b"
            "d":  "\d"
            "e":  "\e"
            "f":  "\f"
            "l":  "\n"
            "n":  "\n"
            "r":  "\r"
            "t":  "\t"
            "v":  "\v"
            "'":  "'"
            "\"":  "\""
            "x":  hexcode()
            "^":  ctrlcode()
            !"01234567":  octcode()
            default:  c
            }
         }
      ns ||:= tab(0)
      }
   return ns
end

procedure hexcode()
   local i, s
   static cdigs
   initial cdigs := ~'0123456789ABCDEFabcdef'
   
   move(i := 2 | 1) ? s := tab(upto(cdigs) | 0)
   move(*s - i)
   return char("16r" || s)
end

procedure octcode()
   local i, s
   static cdigs
   initial cdigs := ~'01234567'
   
   move(-1)
   move(i := 3 | 2 | 1) ? s := tab(upto(cdigs) | 0)
   move(*s - i)
   if s > 377 then {	# back off if too large
      s := s[1:3]
      move(-1)
      }
   return char("8r" || s)
end

procedure ctrlcode(s)
   return char(upto(map(move(1)),&lcase))
end

