/* "rp.c" copyright 1993-2006 by Franklin Webber * This source code may be freely copied if it is not modified. It may * be modified if this copyright notice (the first 7 lines of the file) * is included unchanged at the beginning of every modified version. * The author requests that you send a copy of any modified version to him * at Franklin.Webber@computer.org . */ #define THE_VERSION "1T" #define THE_DATE "2006 May 1" /* This program creates a desk calculator in which arithmetic expressions * are entered in Reverse Polish order (i.e., like some Hewlett-Packard * calculators), hence the name "rp". * * To compile: * under DOS: "tcc rp.c" * under Win: "bcc32 rp.c" * under Unix: "gcc -DUNIX -o rp rp.c -lm" * * To execute: * "rp [-{precision}] [-h] [-v] [-n] [-c] [-o]" * where * [-{precision}] is the number of digits displayed after the decimal point * [-h] indicates horizontal display style (default if input from console) * [-v] indicates vertical display style (needs ANSI cursor control) * [-n] indicates null display style (default if input was redirected) * [-c] shows command set at startup * [-o] shows command-line options * * Commands: * Enter or ; or Space : push current entry onto the stack * Cntrl-p : pop stack * Cntrl-d : duplicate top stack element * Cntrl-x : exchange top two stack elements * Cntrl-f,b : cycle stack forward, backward * Cntrl-n : negate top stack element * Cntrl-i : invert top stack element * Cntrl-r : sqrt * Cntrl-e,l : exp, ln * Cntrl-a,s,c,t : asin, sin, cos, tan * e,p : e, pi * +,-,*,/ : operate on top two stack elements * <, > : increase, decrease precision * ? : display command set (not in null display style) * # : display top of stack (null display style only) * @ : display top as ASCII byte, then pop (null style) * Escape, EoF, Cntrl-z : exit the program * ^ can substitute for Cntrl (useful in scripts) */ #include #include #include #include #include #include #include #include #if defined (UNIX) #include #include struct termio ttystate, ttysave; #elif defined (__WIN32__) #include #include #elif defined (__MSDOS__) #include #else #error UNKNOWN PLATFORM #endif /* Define names for some ASCII chars. */ #define ESCAPE '\033' #define RUBOUT '\177' #define CNTRL_P '\020' #define CNTRL_D '\004' #define CNTRL_X '\030' #define CNTRL_F '\006' #define CNTRL_B '\002' #define CNTRL_N '\016' #define CNTRL_I '\011' #define CNTRL_R '\022' #define CNTRL_E '\005' #define CNTRL_L '\014' #define CNTRL_A '\001' #define CNTRL_S '\023' #define CNTRL_C '\003' #define CNTRL_T '\024' #define CNTRL_Z '\032' /* Define booleans. */ typedef int Bool; #define False 0 #define True 1 #define Or || #define And && #define Not ! /* Declare flags determined by the command line. */ Bool commandSet = False; Bool styleSpecified = False; Bool inputRedirected = False; Bool outputRedirected = False; /* Declare global error data. */ int errors = 0; int sigval = 0; /* Declare the floating point type used for all operands. * (rp doesn't really work for type "long double" because the math library * functions only work on type "double". And the loss of precision with * type "float" is very noticeable. So "double" is really the only choice.) */ typedef double Real; #define REAL_MAX DBL_MAX #define REAL_MIN DBL_MIN #define ZERO ((Real)0.0) #define ONE ((Real)1.0) char realFixed [] = "lf"; char realFloat [] = "lE"; /* Define some limits on operand values. * Trigonometric functions will not be applied to any Real whose * absolute value is larger than TRIG_MAX (ANSI C implementations * are supposed to handle such values but some don't). */ #define TRIG_MAX ((Real)1e+14) /* Declare the display style, which is a choice of different ways in * which the program can display the cells of the calculator's stack: * horizontal: displays the top few stack cells on a single line; * vertical: displays the stack from the bottom right of the screen upward; * null: displays only the top of the stack when commanded to. * For vertical style the program requires a monitor that recognizes ANSI * cursor control. In that case, ANSI.SYS must be installed when running DOS. */ typedef int Style; #define Horizontal 2 #define Vertical 1 #define Null 0 Style style = Horizontal; /* Define the screen coordinates of the command entry point, * which is one row below the stack display * (used only in vertical display style). * 2 <= COMMAND_ROW <= 24 * 1 <= COMMAND_COL <= 79-WIDTH * The first screen row and column are numbered 1. */ #define COMMAND_ROW 24 #define COMMAND_COL 58 /* Define the maximum number of stack elements visible. * (used only in horizontal display style). */ #define VISIBLE 3 /* Define the maximum stack depth, and * declare a stack of operands and its current depth. * 0 <= depth <= DEPTH. */ #define DEPTH 100 int depth = 0; Real stack [DEPTH]; /* Define the display width of a stack element. * The display width satisfies several constraints. First, the width * must be expressed using a fixed number of decimal digits so that it can be * packed into a fixed length format string. Two digits is the only good * choice so * 10 <= WIDTH < 100 * Second, the width must be large enough that the number of leading digits * in a fixed point number will always be positive for any precision. * See below for the analysis that implies * 12 <= WIDTH. * Third, the width must be large enough to display a floating point number * regardless of the precision. See below for the analysis that implies * 17 <= WIDTH. * Finally, the width must be small enough that in the horizontal display style * VISIBLE+1 elements can be displayed on one line with one space * between them. So * WIDTH < 80/(VISIBLE+1). * 4 elements per line means * WIDTH < 20. * 5 elements per line would be possible if WIDTH==15 but then, * according to the analysis below, precision <= 8. */ #define WIDTH 17 /* Declare variables that control the display of a stack element. * Precision is the number of digits after the decimal point. * Digits is the maximum number of digits before the decimal point. * In fixed point 2 bytes in addition to the precision and the digits must * be allowed in the display: one for the decimal point itself and * one for an optional sign, so * digits + precision + 2 == WIDTH. * In floating point up to 5 more bytes must be allowed for the 3-digit * exponent, its sign, and an 'E'; also digits==1, so * precision + 8 <= WIDTH. * The precision will be packed into a fixed length format string so * it must be expressed using exactly one decimal digit: * 0 <= precision <= 9. * Finally, there must be at least one leading digit * (exactly one when using floating point): * 0 < digits. * These constraints are used to limit the choice of WIDTH in the previous * paragraph, i.e., 17 <= WIDTH for floating point. Also, digits will be * derived from precision and WIDTH when displaying fixed point: * digits = WIDTH - precision - 2. */ int precision = 2; int digits; /* Define the conditions for underflow and overflow of the output; * when either condition is satisfied, * the output will use the floating point format instead of fixed point. * tooSmall == pow (10.0, -precision) * tooLarge == pow (10.0, digits) */ Real tooSmall; Real tooLarge; #define rabs(x) ((Real)fabs((double)x)) #define TOO_SMALL(x) (rabs(x) < tooSmall) #define TOO_LARGE(x) (rabs(x) >= tooLarge) /* Declare variables to hold the format strings used in I/O of stack elements. * The fixed point format should be "%.lf" for type double * and the floating point format should be "%.lE". * takes two digits and takes one, * for a total of 8 bytes, 7 printing and a final null, in the format string. * The input format should be simply "%lf" for type double. */ char fixedFormat [8]; char floatFormat [8]; char inputFormat [8]; /************************************************/ /* Implement a function to call when changing the precision. * This function computes digits, format strings, overflow and underflow. */ static void SetFormat (void) { if (precision > 9) precision = 9; if (precision < 0) precision = 0; digits = WIDTH - precision - 2; tooSmall = pow (10.0, -precision); tooLarge = pow (10.0, digits); if (style == Null) { sprintf (fixedFormat, "%%.%1d%-.2s", precision, realFixed); sprintf (floatFormat, "%%.%1d%-.2s", precision, realFloat); } else { sprintf (fixedFormat, "%%%2d.%1d%-.2s", WIDTH, precision, realFixed); sprintf (floatFormat, "%%%2d.%1d%-.2s", WIDTH, precision, realFloat); } sprintf (inputFormat, "%%%-.2s", realFixed); } /************************************************/ /* Implement cursor control operations. * Define the ANSI escape sequences necessary for vertical display style. */ #define ATTN "\033\133" #define NORMAL 0 #define REVERSE 7 static void PutCursorSave (void) { printf (ATTN); printf ("s"); } static void PutCursorRestore (void) { printf (ATTN); printf ("u"); } static void PutCursor (int row, int col) { printf (ATTN); printf ("%d;%df", row, col); } static void PutMode (int nat) { printf (ATTN); printf ("%dm", nat); } /************************************************/ /* Implement stack operations: Empty, Push, Pop, Cycle. */ static void PutError (void) { errors++; if (style != Null) putchar ('\a'); } static void EmptyStack (void) { int i; for (i=0; i0; i--) stack [i] = stack [i-1]; stack [0] = real; } static Real PopStack (void) { Real real = stack [0]; int i; if (depth > 0) depth--; else PutError (); for (i=0; i0; i--) stack [i] = stack [i-1]; stack [0] = real; } static void CycleStackBackward (void) { Real real; int i; if (depth < 2) return; real = stack [0]; for (i=0; i COMMAND_ROW-2) shown = COMMAND_ROW-2; PutCursor (COMMAND_ROW-shown-1, COMMAND_COL); /* clear an extra row in */ PutBlank (); /* case stack was popped */ for (i=shown-1; i>=0; i--) { PutCursor (COMMAND_ROW-i-1, COMMAND_COL); PutElement (stack [i]); /* write stack values */ } PutCursor (COMMAND_ROW, COMMAND_COL); /* clear an extra row */ PutBlank (); /* for entering new data */ PutCursor (COMMAND_ROW, COMMAND_COL); } static void PutStack (void) { switch (style) { case Horizontal: PutHorizontalStack (); break; case Vertical: PutVerticalStack (); break; } } static void PutCommandSet (void) { printf ("\"rp -o\" shows command-line options\n"); printf ("Enter or ; or Space to push number onto stack\n"); printf ("Cntrl- p(op), d(uplicate), (e)x(change)\n"); printf ("Cntrl- f(orward), b(ackward)\n"); printf ("Cntrl- n(egate), i(nvert), (sq)r(oot), e(xp), l(n)\n"); printf ("Cntrl- a(sin), s(in), c(os), t(an)\n"); printf ("e, p(i)\n"); printf ("+,-,*,/\n"); printf ("<, > adjusts precision\n"); printf ("? displays command set except in null display style\n"); printf ("# displays top of stack in null display style\n"); printf ("@ displays top as ASCII byte, then pops, in null display style\n"); printf ("^ can substitute for Cntrl (useful in scripts)\n"); printf ("Escape or EoF or Cntrl-z to exit\n"); } static void PutHelp (void) { int i; switch (style) { case Horizontal: putchar ('\r'); for (i=0; i<=VISIBLE; i++) PutBlank (); for (i=0; i 0 And (ch == 'E' Or (buffer [count-1] == 'E' And (ch == '+' Or ch == '-')))); } static int ToControl (int ch) { if ('A' <= ch And ch <= 'Z') return ch - 'A' + CNTRL_A; if ('a' <= ch And ch <= 'z') return ch - 'a' + CNTRL_A; return ch; } static int ToAscii (Real real) { if (real < 0.) return 0; if (real > 255.) return 255; return (int) floor ((double) real); } static void DoRubout (void) { if (count > 0) { count--; if (style != Null) { putchar ('\b'); putchar (' '); putchar ('\b'); } } } static void DoNumeric (int ch) { if (count < WIDTH-1) { buffer [count++] = ch; if (style != Null) putchar (ch); } } static void DoNumber (void) { Real value; if (count > 0) { buffer [count] = '\0'; count = 0; if (sscanf (buffer, inputFormat, &value) == 1) PushStack (value); else PutError (); } } static void DoNullary (int ch) { switch (ch) { case CNTRL_F: CycleStackForward (); break; case CNTRL_B: CycleStackBackward (); break; case 'e': PushStack ((Real) 2.7182818284590452354); break; case 'p': PushStack ((Real) 3.14159265358979323846); break; } } static void DoUnary (int ch) { Real s; if (depth < 1) return; s = stack [0]; switch (ch) { case CNTRL_P: (void) PopStack (); break; case CNTRL_D: PushStack (s); break; case CNTRL_N: stack [0] = -s; break; case CNTRL_I: if (s != ZERO) stack [0] = ONE / s; else PutError (); break; case CNTRL_R: if (s >= ZERO) stack [0] = (Real) sqrt ((double) s); else PutError (); break; case CNTRL_E: if (s <= (Real) log ((double) REAL_MAX)) stack [0] = (Real) exp ((double) s); else PutError (); break; case CNTRL_L: if (s > ZERO) stack [0] = (Real) log ((double) s); else PutError (); break; case CNTRL_A: if (rabs (s) <= ONE) stack [0] = (Real) asin ((double) s); else PutError (); break; case CNTRL_S: if (rabs (s) <= TRIG_MAX) stack [0] = (Real) sin ((double) s); else PutError (); break; case CNTRL_C: if (rabs (s) <= TRIG_MAX) stack [0] = (Real) cos ((double) s); else PutError (); break; case CNTRL_T: if (rabs (s) <= TRIG_MAX) stack [0] = (Real) tan ((double) s); else PutError (); break; case '#': if (style == Null) PutElement (s); break; case '@': if (style == Null) putchar (ToAscii (PopStack ())); break; } } static void DoBinary (int ch) { Real s,t; if (depth < 1) return; if (depth==1 And ch=='-') { /* special case: user probably expects */ PushStack (ZERO); /* stack [1] to be zero */ CycleStackForward (); /* even though he didn't enter it, */ } /* e.g. '5' then '-' yields -5 */ if (depth < 2) return; s = stack [0]; t = stack [1]; switch (ch) { case CNTRL_X: stack [0] = t; stack [1] = s; break; case '+': if ((s > ZERO And t > REAL_MAX - s) Or (s < ZERO And t < -REAL_MAX - s)) PutError (); else { (void) PopStack (); stack [0] = t + s; } break; case '-': if ((s > ZERO And t < -REAL_MAX + s) Or (s < ZERO And t > REAL_MAX + s)) PutError (); else { (void) PopStack (); stack [0] = t - s; } break; case '*': s = rabs (s); t = rabs (t); if ((s > ONE And t > REAL_MAX/s) Or (ZERO < s And s < ONE And ZERO < t And t < REAL_MIN/s)) PutError (); else { s = PopStack (); stack [0] *= s; } break; case '/': s = rabs (s); t = rabs (t); if ((s > ONE And ZERO < t And t < REAL_MIN*s) Or (ZERO < s And s < ONE And t > REAL_MAX*s) Or s == ZERO) PutError (); else { s = PopStack (); stack [0] /= s; } break; } } static void DoCommand (int ch) { switch (ch) { case '?': PutHelp (); break; case '<': precision++; SetFormat (); break; case '>': precision--; SetFormat (); break; case CNTRL_F: case CNTRL_B: case 'e': case 'p': DoNullary (ch); break; case CNTRL_P: case CNTRL_D: case CNTRL_N: case CNTRL_I: case CNTRL_R: case CNTRL_E: case CNTRL_L: case CNTRL_A: case CNTRL_S: case CNTRL_C: case CNTRL_T: case '#': case '@': DoUnary (ch); break; case CNTRL_X: case '+': case '-': case '*': case '/': DoBinary (ch); break; } } /* If the escape char or cntrl-z is typed or EOF is seen, * return False to quit the program. * If a backspace is typed, delete the most recent char in the buffer. * If a digit is typed, put it at the end of the buffer. * Any other char may be a command, so convert a non-empty buffer into * a number on the top of the stack and execute the command. * Convert ^ to Cntrl- before processing the char. */ static Bool DoChar (int ch) { static Bool cntrl = False; if (cntrl == True) ch = ToControl (ch); cntrl = (ch == '^'); if (ch == EOF Or ch == CNTRL_Z Or ch == ESCAPE) return False; if (ch == '\b' Or ch == RUBOUT) DoRubout (); else if (IsNumeric (ch)) DoNumeric (ch); else { DoNumber (); DoCommand (ch); PutStack (); } return True; } /************************************************/ /* Implement startup and shutdown. */ /* Define error return macros. */ #define EXIT_(err) { fprintf (stderr,"\n"); exit (err); } #define EXIT0_(str,err) { fprintf (stderr,str); EXIT_(err); } #define EXIT1_(str,a1,err) { fprintf (stderr,str,a1); EXIT_(err); } /* Give help on how to invoke the program. */ #define USAGE_EXIT_(str) { PutUsage (); EXIT0_(str,1) } static void PutUsage (void) { fprintf (stderr, "copyright 1993-2006 by Franklin Webber\n"); fprintf (stderr, "version %s of %s\n", THE_VERSION, THE_DATE); fprintf (stderr, "usage: rp [-{precision}] [-h] [-v] [-n] [-o] [-c]\n"); fprintf (stderr, "where\n"); fprintf (stderr, " [-{precision}] specifies number of digits "); fprintf (stderr, "shown after the decimal point\n"); fprintf (stderr, " [-h] indicates horizontal display style "); fprintf (stderr, "(default if input from console)\n"); fprintf (stderr, " [-v] indicates vertical display style "); fprintf (stderr, "(needs ANSI cursor control)\n"); fprintf (stderr, " [-n] indicates null display style "); fprintf (stderr, "(default if input was redirected)\n"); fprintf (stderr, " [-c] shows command set at startup\n"); fprintf (stderr, " [-o] shows command-line options\n"); } static void InitializeSwitch (char* str) { if (Not strcmp (str, "h")) { if (styleSpecified And style != Horizontal) USAGE_EXIT_(">> you may specify at most one style") style = Horizontal; styleSpecified = True; } else if (Not strcmp (str, "v")) { if (styleSpecified And style != Vertical) USAGE_EXIT_(">> you may specify at most one style") style = Vertical; styleSpecified = True; } else if (Not strcmp (str, "n")) { if (styleSpecified And style != Null) USAGE_EXIT_(">> you may specify at most one style") style = Null; styleSpecified = True; commandSet = False; } else if (Not strcmp (str, "c")) { if (style != Null) commandSet = True; } else if (Not strcmp (str, "o")) USAGE_EXIT_(" ") else if (sscanf (str, "%d", &precision) != 1) USAGE_EXIT_(">> you may specify only integer precision") } static void InitializeTerminal (void) { #if defined (UNIX) if (Not isatty (0)) { inputRedirected = True; if (Not styleSpecified) style = Null; return; } if (ioctl (0, TCGETA, &ttysave) < 0) EXIT0_("rp: cannot load terminal state", 2) ttystate = ttysave; ttystate.c_iflag &= ~IXON; ttystate.c_oflag &= ~OCRNL; ttystate.c_lflag &= ~ICANON; ttystate.c_lflag &= ~ISIG; ttystate.c_lflag &= ~ECHO; ttystate.c_cc [VMIN] = 1; ttystate.c_cc [VTIME] = 0; if (ioctl (0, TCSETA, &ttystate) < 0) EXIT0_("rp: cannot store terminal state", 2) #elif defined (__WIN32__) if (Not isatty (0)) { inputRedirected = True; if (Not styleSpecified) style = Null; setmode (fileno (stdin), O_BINARY); } if (Not isatty (1)) { outputRedirected = True; setmode (fileno (stdout), O_BINARY); } #else /* defined (__MSDOS__) */ union REGS before, after; before.x.ax = 0x4400; before.x.bx = 0; /* stdin */ int86 (0x21, &before, &after); if (after.x.flags & 1) EXIT0_("rp: cannot get device data", 2) if ((after.x.dx & 0x81) != 0x81) { inputRedirected = True; if (Not styleSpecified) style = Null; setmode (fileno (stdin), O_BINARY); } before.x.ax = 0x4400; before.x.bx = 1; /* stdout */ int86 (0x21, &before, &after); if (after.x.flags & 1) EXIT0_("rp: cannot get device data", 2) if ((after.x.dx & 0x82) != 0x82) { outputRedirected = True; setmode (fileno (stdout), O_BINARY); } #endif } static void FinalizeTerminal (void) { #if defined (UNIX) if (inputRedirected) return; if (ioctl (0, TCSETA, &ttysave) < 0) EXIT0_("rp: cannot restore terminal state", 2); #else /* defined (__WIN32__) || defined (__MSDOS__) */ if (inputRedirected) setmode (fileno (stdin), O_TEXT); if (outputRedirected) setmode (fileno (stdout), O_TEXT); #endif } /* This handler for floating point exceptions should never be called, * but if it is, it notes this error and reinitializes itself * for the next error. */ static void FPE_handler (int sig) { sigval = sig; PutError (); (void) signal (SIGFPE, &FPE_handler); } static void Initialize (int argc, char* argv []) { while (argc--> 1) if (argv [argc] [0] == '-') InitializeSwitch (argv [argc] + 1); else USAGE_EXIT_(">> you may specify only options beginning with '-'") InitializeTerminal (); SetFormat (); EmptyStack (); if (commandSet) PutCommandSet (); if (style == Vertical) { PutMode (REVERSE); PutCursorSave (); } PutStack (); (void) signal (SIGFPE, &FPE_handler); } static void Finalize (void) { (void) signal (SIGFPE, SIG_DFL); if (style == Vertical) { PutCursorRestore (); PutMode (NORMAL); } FinalizeTerminal (); printf ("\n"); } #ifdef UNIX #define getchr() getchar() #else /* assume Microsoft if not Unix */ #define getchr() (inputRedirected ? getchar () : getch ()) #endif /************************************************/ int main (int argc, char* argv []) { Initialize (argc, argv); while (DoChar (getchr ())); Finalize (); return 0; }