home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Simtel MSDOS - Coast to Coast
/
simteldosarchivecoasttocoast2.iso
/
calculat
/
rpn30src.zip
/
PROCESS.C
< prev
next >
Wrap
Text File
|
1990-05-30
|
23KB
|
792 lines
/**
** 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("------");
}
/**\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\**/