home *** CD-ROM | disk | FTP | other *** search
- /**
- ** process -- Decide what to do with an input character, and do it.
- **
- ** Process() attempts to treat the actual calculator logic as
- ** a Mealy-machine DFA (outputs associated with transitions).
- ** The calculator really resides in those outputs; the state only
- ** serves to determine how the input characters are treated.
- ** The EXPONENT state (added 90.01.01) actually makes it a pushdown
- ** automaton, but a fixed-push one that could be converted back to
- ** a DFA if desired. (The single recursive call is more efficient.)
- **
- ** The GETNUM state is the workhorse; this state builds up input
- ** numbers. A non-number terminates number entry and either causes
- ** some calculator action, or moves to the GETFUNC state or one of
- ** the Memory states. The PRECISION and BASE states are special cases
- ** that interrupt number entry without terminating it.
- **
- ** EXPONENT state is really a variant of the GETNUM state, but it
- ** functions like the PRECISION and BASE states in using metanumbers
- ** --- here, they represent the power-of-10 exponent. The calculator
- ** gets out of this state by going through state GETNUM and using
- ** process() recursively to process the character.
- **
- ** The <ESCAPE> character is treated specially; whatever the state,
- ** it either turns off the Help window or forces the DFA into the
- ** GETNUM state. An <ESCAPE> followed by a "Clear X" effectively
- ** re-initializes the DFA (although not the calculator).
- **
- ** If "init" is detected in GETFUNC, process() returns an INITialize
- ** flag to the main() function. A Quit character (in GETNUM)
- ** causes process() to return a QUIT flag. A <CTRL>-x character yields
- ** an ABORT flag, which means "Quit but leave the windows visible".
- **
- ** 90.05.28 v3.0
- ** matherr() moved to a separate file. Matherr() can't catch
- ** arithmetic (divide-by-0) errors; signal(SIG_FPE) used as well.
- ** Add_ok() and other tests for arith. errors are gone (except
- ** one use of mul_ok()) since signal() works.
- ** More constants added (see ftns.c).
- ** Linear regression/interpolation (see ftns.c).
- ** Ftn keys handled specially (outside any state) and moved to main().
- ** Nullary functions put into a lookup table, like the unary ones.
- ** Lotsa code rearrangement in general.
- ** 90.05.02 v2.3 - fix Register 0 bug, hex digit bug.
- ** The hex-digit fix involves storing legal digit-characters in
- ** an array and checking it; this results in dispensing with the
- ** isnum() testing in favor of a value() function that returns
- ** a character's digit value or -1 if it isn't a digit. (The
- ** meta-numbers use isdigit() since they're restricted to Base 10.)
- **
- ** 90.01.01
- **/
- #include <stdlib.h>
- #include <string.h>
- #include <ctype.h>
- #include <math.h>
- #include <float.h>
- #include "rpn.h"
- #include "display.h" /** for display-stack width vs. precision **/
- #include "rpnio.h" /** for special-key definitions **/
- #include "ftns.h"
- #include "debug.h"
-
-
- /** Possible states for the calculator automaton... **/
- typedef enum {
- GETNUM, GETFUNC, PRECISION, BASE, EXPONENT,
- STORE, STOREP, STOREM, STORET, STORED, RECALL
- } input_states ;
-
- static input_states state; /** This one is a biggie :-) **/
- static int meta_num; /** workspace for memory addresses, etc. **/
- static int meta_neg; /** sign of meta_num (for exponents) **/
- static int wholenum; /** "Is this a whole digit or decimal digit?" **/
- static double num_divider;/** keeps track of decimal place **/
- /** when entering non-whole digits **/
- static char *functend; /** current position in 'thisfunct' string **/
-
- static int value(int); /* test & return a key's digit-value */
- static int try_metanum(int);
- static int good_mem(int);
- static void do_funct(char *);
- static void clear_vars(void);
- #define flush_thisfunct() (*(functend = thisfunct) = '\0')
- #define safe_len() (functend < (thisfunct + NAME_LEN-1))
-
-
- /* / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / */
-
- static void do_ftn_2(char *fn)
- {
- if (savefile)
- close_savefile();
- else {
- open_savefile(fn);
- strncpy(filename, fn, 12);
- }
- }
-
- /* / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / */
- /*
- | Append the supplied string to `thisfunct' at wherever functend points.
- */
- static void append_functend(char *src)
- {
- while ( '\0' != (*functend = *src++) )
- functend++;
- }
-
- /*
- | Append `ch' to `thisfunct' at wherever functend points.
- */
- static void cat_functend(int ch)
- {
- DBG_FPRINTF((errfile,"cat_functend ch: %c/%d; %p/%p\n"
- "thisfunct: %s, length %d; ",
- ch,ch, thisfunct, functend, thisfunct,strlen(thisfunct)));
-
- *(functend++) = ch;
- *functend = '\0';
-
- DBG_FPRINTF((errfile,"%p/%p, thisfunct: %s, length %d\n",
- thisfunct, functend, thisfunct,strlen(thisfunct)));
- }
-
- /* / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / */
-
- int process(int ch)
- {
- double temp; /* Intermediate & temp. values */
- long double ltemp;
- int val; /* digit-value */
-
- /*
- | First check some keys whose meanings are state-invariant but
- | which affect (or are affected by) the DFA state...
- */
-
- if (ch == 0x1b) { /* <ESCAPE> */
- if (help_flag >= 0) {
- help_flag = 3;
- toggle_help();
- } else {
- /** Most of the clear_state() function, except **/
- /** that stacklift & number entry are unchanged **/
- clear_vars();
- }
-
-
- } else switch (state) {
- /*-------------------------------------------------------*\
- | PROGRAMMING WARNING: |
- | Process() should return *immediately* after this switch |
- | statement; many of the cases perform their own return! |
- \*-------------------------------------------------------*/
-
- case GETNUM:
-
- DBG_FPRINTF((errfile,"GETNUM: %p/%p, xbuf: %s, xreg: %g\n",
- xbuf,xbp,xbuf,xreg));
-
- if ( (val = value(ch)) >= 0 ) {
- if (newnum) {
- if (stacklift) {
- push();
- }
- if (wholenum) {
- xreg = val;
- } else {
- num_ct = 1;
- num_divider = 1.0 / (double)base;
- xreg = val * num_divider;
- }
- if (negative) {
- xreg = -xreg;
- }
- stacklift = newnum = FALSE;
- } else {
- if (wholenum) {
- xreg = (xreg * (double)base) +
- ( negative ? -val : val );
- } else {
- ++num_ct;
- num_divider /= (double)base;
- temp = val * num_divider;
- if (negative)
- xreg -= temp;
- else
- xreg += temp;
- }
- }
-
- } else { /** not a number... **/
- switch (ch) {
- case '.': /**...GETNUM state **/
- wholenum = FALSE;
- if (newnum) {
- if (stacklift)
- push();
- xreg = 0.0;
- stacklift = newnum = FALSE;
- }
- break;
-
- case 'E': /**...GETNUM state **/
- append_functend("exponent ");
- if (newnum) {
- if (stacklift)
- push();
- xreg = 1.0;
- stacklift = newnum = FALSE;
- }
- meta_neg = meta_num = 0;
- state = EXPONENT;
- break;
-
- case DEL: /** Del on numeric keypad **/
- case '\b':
- if (!newnum) { /** kill last digit... **/
- if (wholenum) {
- xreg = (double)(floor(xreg / (double)base));
- } else {
- num_divider *= (double)base;
- temp = (double)(floor(xreg / num_divider));
- xreg = temp * num_divider;
- --num_ct;
- if (0 == num_ct)
- wholenum = TRUE;
- }
- break;
- }
- /** else FALLTHROUGH to Clear-X... **/
- case 'C':
- xreg = 0.0;
- clear_state("Clr X");
- stacklift = FALSE; /** Next number overwrites X reg. **/
- break;
-
- case '+': /**...GETNUM state **/
- temp = yreg + xreg;
- pop();
- xreg = temp;
- clear_state("+");
- break;
- case '-':
- temp = yreg - xreg;
- pop();
- xreg = temp;
- clear_state("-");
- break;
- case '*':
- temp = yreg * xreg;
- pop();
- xreg = temp;
- clear_state("*");
- break;
- case '/':
- temp = yreg / xreg;
- pop();
- xreg = temp;
- clear_state("/");
- break;
- case '%':
- temp = 0.01 * xreg;
- temp = yreg * temp;
- shift_lastx();
- xreg = temp;
- clear_state("%");
- break;
- case '^':
- power();
- break;
-
- case 'X': /**...GETNUM state **/
- temp = yreg;
- yreg = xreg;
- xreg = temp;
- clear_state("X \x1D Y");
- break;
-
- case 'I':
- shift_lastx();
- if (xreg == 0.0) {
- prterr("Inverse", "of zero");
- } else {
- xreg = (double)1.0 / xreg;
- }
- clear_state("Inverse( X )");
- break;
-
- case '!':
- shift_lastx();
- xreg = fact(xreg);
- clear_state("factorial");
- break;
-
- case 'L': /**...GETNUM state **/
- push();
- xreg = lastx;
- clear_state("Last X");
- break;
-
- case 'N':
- case '\'':
- xreg = -xreg;
- negative = !negative;
- strcpy(lastfunct, (negative ? "Negate (-)" : "Negate (+)"));
- if (newnum)
- stacklift = TRUE;
- flush_thisfunct();
- break;
-
- case ']': /** quick form of 'sum+' Summing function **/
- sumplus();
- break;
- case '[': /** quick form of 'sum-' Un-Summing function **/
- summinus();
- break;
-
- case 0x04: /** <ctrl>-D **/
- trig_mode = DEGREES;
- clear_state("Degrees mode");
- break;
- case 0x12: /** <ctrl>-R **/
- trig_mode = RADIANS;
- clear_state("Radians mode");
- break;
-
- case '\r':
- case ' ':
- push();
- clear_state("Enter");
- stacklift = FALSE; /** Next number overwrites X reg. **/
- break;
-
- case UARR: /** UpArrow on numeric keypad **/
- ru();
- break;
- case DARR: /** DownArrow on numeric keypad **/
- rd();
- break;
-
- case 'R': /** quick form of 'rcl' **/
- append_functend("Recall ");
- meta_neg = meta_num = 0;
- state = RECALL;
- break;
- case 'S': /** quick form of 'sto' **/
- append_functend("Store ");
- meta_neg = meta_num = 0;
- state = STORE;
- break;
-
- case 'P':
- append_functend("Prec. ");
- meta_neg = meta_num = 0;
- state = PRECISION;
- break;
-
- case 'B': /**...GETNUM state **/
- append_functend("Base ");
- meta_neg = meta_num = 0;
- state = BASE;
- break;
-
- case FTN_2:
- do_ftn_2(default_save);
- break;
-
- default:
- cat_functend(ch);
- state = GETFUNC;
- }
- }
- break; /** ...from GETNUM state **/
-
- case EXPONENT:
- if (try_metanum(ch) == TRUE) {
- break;
- }
- if (ch == '\'' || ch == 'N') { /** change sign of exponent **/
- meta_neg = !meta_neg;
- *(thisfunct+9) = (meta_neg ? '-' : ' ');
- break;
- }
- /*
- | ...else finish entering the number, and recursively process
- | this character as a number-entry terminator...
- */
- if (meta_neg)
- meta_num = -meta_num;
- xreg *= p10((double)meta_num);
- state = GETNUM;
- return process(ch); /** return DIRECTLY from here **/
-
-
- case GETFUNC:
- if (ch == '\r') {
- if (strcmp(thisfunct,"init") == 0) {
-
- return INIT_VAL; /** Re-initialize **/
-
- } else {
- do_funct(thisfunct);
- }
-
- } else if (ch == DEL || ch == '\b') { /* Delete or Backspace? */
- if (functend != thisfunct) {
- *(--functend) = '\0'; /* Backspace & delete a char. */
- }
- if (functend == thisfunct) {
- state = GETNUM;
- }
- } else if (ch == FTN_2) { /** Save results **/
- do_ftn_2(thisfunct);
- clear_state(thisfunct);
- }
- else {
- if (safe_len()) {
- cat_functend(ch);
- } else {
- prterr("ftn name", "too long");
- }
- if (strcmp(thisfunct,"rcl") == 0) {
- meta_neg = meta_num = 0;
- state = RECALL;
- } else if (strcmp(thisfunct,"sto") == 0) {
- meta_neg = meta_num = 0;
- state = STORE;
- }
- }
- break; /** ...from GETFUNC **/
-
- case RECALL:
- if (ch == 's') {
- if (meta_num == 0) {
- xreg = memory[11];
- yreg = memory[12];
- append_functend("sums");
- clear_state(thisfunct);
- } else {
- prterr("mem-addr", "invalid char");
- clear_state(lastfunct);
- }
- break;
- }
- if (try_metanum(ch) == TRUE) {
- break;
- }
- if (good_mem(ch) == TRUE) {
- push();
- xreg = memory[meta_num];
- clear_state(thisfunct);
- }
- break;
-
- case STORE:
- if (ch == '+' || ch == '-' ||ch == '*' || ch == '/') {
- cat_functend(ch);
- state = ((ch == '+') ? STOREP :
- ((ch == '-') ? STOREM :
- ((ch == '*') ? STORET : STORED)));
- break;
- }
- if (try_metanum(ch) == TRUE) {
- break;
- }
- if (good_mem(ch) == TRUE) {
- memory[meta_num] = xreg;
- clear_state(thisfunct);
- }
- break;
-
- case STOREP:
- if (try_metanum(ch) == TRUE) {
- break;
- }
- if (good_mem(ch) == TRUE) {
- ltemp = memory[meta_num] + xreg;
- memory[meta_num] = ltemp;
- clear_state(thisfunct);
- }
- break;
-
- case STOREM:
- if (try_metanum(ch) == TRUE) {
- break;
- }
- if (good_mem(ch) == TRUE) {
- ltemp = memory[meta_num] - xreg;
- memory[meta_num] = ltemp;
- clear_state(thisfunct);
- }
- break;
-
- case STORET:
- if (try_metanum(ch) == TRUE) {
- break;
- }
- if (good_mem(ch) == TRUE) {
- ltemp = memory[meta_num] * xreg;
- memory[meta_num] = ltemp;
- clear_state(thisfunct);
- }
- break;
-
- case STORED:
- if (try_metanum(ch) == TRUE) {
- break;
- }
- if (good_mem(ch) == TRUE) {
- ltemp = memory[meta_num] / xreg;
- memory[meta_num] = ltemp;
- clear_state(thisfunct);
- }
- break;
-
- /*
- | Precision and Base are specified in "metanumbers" which are
- | always entered as base 10, so "ch" is checked against 0..9
- | These two states are combined for efficiency, because they
- | are so similar --- they are also alike in being "display-control"
- | states rather than normal calculator-operation states.
- */
- case PRECISION:
- case BASE:
- if (try_metanum(ch) == TRUE) {
- break; /** Finish the case here **/
- }
- /** ...else not a digit, so finish off state **/
- if (ch == '\r') {
-
- if (state == BASE) {
- if (meta_num <= MAX_BASE) {
- base = meta_num;
- show_base(0);
- } else {
- prterr("base", "too large");
- show_base(1);
- }
- } else { /** ...must be PRECISION state **/
- pre = min((STK_WIDTH - STK_MARKS), meta_num);
- }
-
- } else {
- prterr((state == BASE ? "base" : "precision"), "invalid char");
- stack_popped = 0;
- }
- flush_thisfunct();
- state = GETNUM;
- break;
-
- default: /** We should never get here! **/
- base = 10;
- show_base(1);
- xreg = (double)state;
- prterr("machine", "unknown state");
- return ABORT_VAL;
- }
-
- return OK_VAL;
- }
-
- /**\/\/\/\/\/\ end of process() /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\**/
-
-
- /*---------------------------------------------------------------------*\
- | do_funct() is a series of tests, trying to match the function name. |
- | if any test succeeds, the function is performed and do_funct() |
- | immediately returns (thus no "else" clauses). If all tests fail, |
- | the default code at the end is performed. |
- \* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
- static void do_funct(char *name)
- {
- int addr;
- f_ptr p; /** for unary functions **/
- vf_ptr vp; /** for void/null-ary functions **/
-
- /*
- | According to the manual, pi doesn't save Last X; so none of
- | these constants do. (fixed in v1.1; add'l constants v3.0)
- */
- for (addr = 0; addr < NUM_CONSTS; ++addr) {
- if (0 == strcmp(name, constants[addr].name)) {
- push();
- xreg = constants[addr].val;
- clear_state(name);
- return;
- }
- }
-
- if (strcmp(name,"clrstk") == 0) {
- xreg = yreg = zreg = treg = 0.0;
- clear_state(name);
- return;
- }
- if (strcmp(name,"clrblk") == 0) {
- clrreg((int)yreg, (int)xreg);
- clear_state(name);
- return;
- }
- if (strcmp(name,"clrreg") == 0) {
- clrreg(0, MEMSIZE-1);
- clear_state(name);
- return;
- }
- if (strcmp(name,"clrsum") == 0) {
- clrreg(10, 19);
- memory[19] = memory[18] = ONE; /** geometric means start at 1 **/
- clear_state(name);
- return;
- }
- if (strcmp(name,"clrlin") == 0) {
- clrreg(B0, COV);
- clear_state(name);
- return;
- }
-
- /*-----------------------------------------------------*/
-
- if ((vp = funct_0(name)) != (vf_ptr)NULL) {
- shift_lastx();
- (*vp)(); /** These functions clear_state() themselves **/
- return;
- }
-
- if ((p = funct_1(name,I_TRIG)) != (f_ptr)NULL) {
- shift_lastx();
- xreg = (*p)(xreg);
- if ((trig_mode == DEGREES) && !math_error)
- xreg *= RAD_TO_DEG;
- clear_state(name);
- return;
- }
-
- if ((p = funct_1(name,TRIG)) != (f_ptr)NULL) {
- shift_lastx();
- if (trig_mode == DEGREES)
- xreg *= DEG_TO_RAD;
- xreg = (*p)(xreg);
- if (math_error && (trig_mode == DEGREES))
- xreg *= RAD_TO_DEG;
- clear_state(name);
- return;
- }
-
- if ((p = funct_1(name,UNARY)) != (f_ptr)NULL) {
- shift_lastx();
- xreg = (*p)(xreg);
- clear_state(name);
- return;
- }
-
- /*
- | ...ELSE...
- */
- strncat(thisfunct, " unknown", NAME_LEN - 1 - strlen(thisfunct));
- clear_state(thisfunct);
- return;
- }
-
- /* / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / */
-
- /**
- ** Try_metanum() tests whether a character is a legitimate meta-number,
- ** processes it if it is, and reports the test result back to the caller.
- ** Meta-numbers are used to specify display precision and base, exponents,
- ** and memory addresses. They are always in base 10.
- **/
- static int try_metanum(int ch)
- {
- if (isdigit(ch)) {
- meta_num = meta_num * 10 + (ch - '0');
- if (safe_len()) {
- DBG_FPRINTF((errfile,"meta_num: %d, ch: %c/%d\n",meta_num,ch,ch));
- cat_functend(ch);
- } else {
- prterr("metanumber", "too long");
- }
- return TRUE;
- }
- return FALSE;
- }
-
- /* / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / */
- /**
- ** Good_mem() sets up the memory address if ch is 'i' or '\r'.
- ** If ch or the address is invalid it emits an error message.
- ** Then it reports the outcome to its caller.
- **
- ** v2.3 - (Mike Mueller?) reports that the "out of range" error message
- ** gets corrupted. So we try some hacks to fix this.
- ** v3.0 ... the hacks reportedly work. yippee.
- **/
- const char oor_msg[] = "out of range";
- const char ic_msg[] = "invalid char";
-
- static int good_mem(int ch)
- {
- char *errptr;
-
- switch (ch) {
- case 'i':
- meta_num = memory[0];
- cat_functend(ch);
- /* FALLTHROUGH... */
- case '\r':
- if (meta_num >= 0 && meta_num < MEMSIZE) {
-
- return TRUE;
- }
- errptr = (char *)oor_msg;
- break;
- default:
- errptr = (char *)ic_msg;
- }
- prterr("mem-addr", errptr);
- clear_state(lastfunct);
- return FALSE;
- }
-
- /* / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / */
-
- static int value(int c)
- {
- int v;
- for (v = 0; v < base; ++v) {
- if (c == digits[v])
- return v;
- }
- return -1;
- }
-
- /**\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\**/
-
- void pop(void)
- {
- shift_lastx();
- xreg = yreg;
- yreg = zreg;
- zreg = treg;
- stack_popped = 1;
- }
-
- void push(void)
- {
- treg = zreg;
- zreg = yreg;
- yreg = xreg;
- }
- /**\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\**/
-
- static void clear_vars(void)
- {
- flush_thisfunct();
- state = GETNUM;
- math_error = stack_popped = 0;
- }
-
- /**\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\**/
-
- void clear_state(char *label)
- {
- if (label != lastfunct)
- strncpy(lastfunct, label, NAME_LEN-1);
- stacklift = newnum = wholenum = TRUE;
- negative = FALSE;
- num_ct = 0;
- num_divider = 1.0;
- clear_vars();
- if (savefile)
- write_save = TRUE; /* Flag save-file output for display() */
- }
-
- /**\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\**/
-
- /*
- | called by main() to initialize the calculator machinery
- */
- void init_machine(void)
- {
- notation = 0;
- pre = 3;
- base = 10;
- trig_mode = DEGREES;
- negative = FALSE;
- _fpreset(); /** reset the math chip **/
- clear_state("------");
- }
-
- /**\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\**/