/************************************************************************
 *									*
 *      A Simple Calculator for Desqview				*
 *									*
 *    Written By: Phillip A. Kaufman                     November, 1987	*
 *									*
 *    Copyright 1987, 1988 Phillip A. Kaufman. All rights, except those	*
 *    specifically granted herein are reserved by the author. The right	*
 *    to copy and distribute this material is granted without fee for	*
 *    any and all non-commercial use. This material specifically may	*
 *    not be distributed or sold for a fee nor incorporated in whole or	*
 *    in part into any other product that is distributed or sold for a	*
 *    fee without specific permission of the author. To obtain special	*
 *    permission or to report any difficulties with this material	*
 *    contact:								*
 *                      Phillip A. Kaufman				*
 *                      19987 Moran Lane				*
 *                      Saratoga, CA 95070				*
 *									*
 *    THIS MATERIAL IS DISTRIBUTES "as is" WITHOUT ANY EXPRESSED OR	*
 *    IMPLIED WARRANTY OR LIABILITY FOR DIRECT, INDIRECT OR		*
 *    CONSEQUENTIAL DAMAGES.						*
 ************************************************************************
 *      Inputs:								*
 *              Decimal Digits; 0 to 9					*
 *              Floating representation (+/-xxx.xxx) in decimal		*
 *                      mode only.					*
 *              Hex digits;     A to F REPRESENTED BY Function keys	*
 *              Ops;            + - * /  There is full chaining of ops	*
 *              HEX and Binary Ops: And, Or, Xor  			*
 *				No chaining of logical ops		*
 *              Change Sign #						*
 *              Memory; MClear, MRecall, M+, M-				*
 *              Modes;          Hex, Decimal, Binary			*
 *                      (hex and binary are integer only)		*
 *              Clear Entry or Accum;           C			*
 *              EXIT;           ESC					*
 ************************************************************************
 *                       CALCULATOR SCREEN IMAGE                        *
 *                                                                      *
 *                  +------------------------------+                    *
 *                0 |                              |                    *
 *                1 |    MEMORY      +-----------+ |                    *
 *                2 | 1234567890123  1234567890123 |                    *
 *                3 |                +-----------+ |                    *
 *                4 |                              |                    *
 *                5 | MCLEAR    M-   DEC  HEX  BIN |                    *
 *                6 |                              |                    *
 *                7 | MRECALL   M+   AND  OR   XOR |                    *
 *                8 |                              |                    *
 *                9 | A(F5)   B(F6)    7  8  9   / |                    *
 *               10 |                              |                    *
 *               11 | C(F7)   D(F8)    4  5  6   * |                    *
 *               12 |                              |                    *
 *               13 | E(F9)   F(F10)   1  2  3   - |                    *
 *               14 |                              |                    *
 *               15 | CLEAR   +/-(#)   0  .  =   + |                    *
 *               16 |                              |                    *
 *                  +------------------------------+                    *
 *                             11111111112222222222                     *
 *                   012345678901234567890123456789                     *
 *                                                                      *
 *                                                                      *
 ************************************************************************/

#include	<dos.h>
#include 	<stdio.h>
#include	<setjmp.h>
#include	<float.h>
#include	<signal.h>

	/* colors may be changed to suit user */
#define FG      0x07            /* forground color, unselected info     */
#define BG      0x01            /* background color, main image         */
#define HC      0x0a            /* highlight color, selectable keys     */
#define WF      0x00            /* answer window FG color               */
#define WB      0x07            /* answer window BG color               */
#define SC      0x0f            /* selected info color                  */
#define EC	0x06		/* error color				*/

#define CASE    case
#define DEFAULT default
#define OK	1
#define BAD	0
#define TRUE	1
#define FALSE	0
#define YES	1
#define	NO	0

#define ROWS    17		/* for convenience - do not change 	*/
#define COLS    30
#define ROW2    ROWS*2
#define COL2    COLS*2

#define attr(f,b) (char)( (((b)&0x0F) << 4) | ((f)&0x0F) ) /*make attr byte*/
#define apos(r,c) ((r)*COL2 + (c)*2)              /* memory offset of r,c */

#define MEMR	2		/* memory disp row		*/
#define MEMC	1		/* memory disp col		*/
#define ACCR	2		/* accum row			*/
#define ACCC	16		/* accum col			*/
#define MEMPOS  apos(MEMR,MEMC)       /* memory display position */
#define ACPOS   apos(ACCR,ACCC)      /* accumulator display position */
#define ANSLEN  13		/* answers string length	*/
#define ERRR	1		/* error display row		*/
#define ERRC	16		/* error display col		*/

#define DEC 0			/* modes			*/
#define HEX 1
#define BIN 2
int mode = DEC;			/* startup mode			*/
unsigned char modeloc[3][2] = {5,16, 5,21, 5,26};
unsigned char aoxloc[3][2] = {7,16, 7,21, 7,26};

/* All characters that appear statically highlighted */
unsigned char hichar[][3] = {
        5,1,'M',   5,2,'C',   5,11,'M',   5,12,'-',
        5,16,'D',  5,21,'H',  5,26,'B',
        7,1,'M',   7,2,'R',   7,11,'M',   7,12,'+',
        7,16,'A',  7,21,'O',  7,26,'X',
        9,3,'F',   9,4,'5',   9,11,'F',  9,12,'6',
        9,18,'7',  9,21,'8',  9,24,'9',  9,28,'/',
        11,3,'F',  11,4,'7',  11,11,'F', 11,12,'8',
        11,18,'4', 11,21,'5', 11,24,'6', 11,28,'*',
        13,3,'F',  13,4,'9',  13,11,'F', 13,12,'1', 13,13,'0',
        13,18,'1', 13,21,'2', 13,24,'3', 13,28,'-',
        15,1,'C',  15,13,'#',
        15,18,'0', 15,21,'.', 15,24,'=', 15,28,'+',
        0,0,0};          /* end marker */

/* All characters that appear statically not highlighted */
unsigned char lochar[][3] = {
        1,4,'M', 1,5,'E', 1,6,'M', 1,7,'O', 1,8,'R', 1,9,'Y',
        5,3,'L', 5,4,'E', 5,5,'A',5,6,'R',
        5,17,'E', 5,18,'C', 5,22,'E', 5,23,'X', 5,27,'I', 5,28,'N',
        7,3,'E', 7,4,'C', 7,5,'A', 7,6,'L', 7,7,'L',
        7,17,'N', 7,18,'D', 7,22,'R', 7,27,'O', 7,28,'R',
        9,1,'A', 9,2,'(', 9,5,')', 9,9,'B', 9,10,'(', 9,13,')',
        11,1,'C', 11,2,'(', 11,5,')', 11,9,'D', 11,10,'(', 11,13,')',
        13,1,'E', 13,2,'(', 13,5,')', 13,9,'F', 13,10,'(', 13,14,')',
        15,2,'L', 15,3,'E', 15,4,'A', 15,5,'R',
        15,9,'+', 15,10,'/', 15,11,'-', 15,12,'(', 15,14,')',
        0,0,0};
        
#define ERRLEN	11			/* number of chars in error msgs */
char OVERFLOW[] = "OVERFLOW   ";	/* error messages		*/
char ZERODIV[]  = "DIVIDE BY 0";
char BADKEY[]   = "ILLEGAL KEY";
char FORMATERR[]= "FORMAT ERR ";
char ENTLENGTH[]= "# TOO LONG ";
char DISPOV[]   = "# TOO BIG  ";
char NOERR[]    = "           ";
char *errptr;				/* ptr to current error msg	*/

union REGS rg;			/* cpu regs		*/
struct SREGS segregs;
union{
        long    l;
        unsigned char far  *p;
} addr;

char entry[ANSLEN+2];			/* entry string			*/
double faccum = 0.0;			/* accumulator			*/
double fsave = 0.0;			/* save for chaining		*/
double ftemp;				/* temp for calculations	*/
double fmemacc = 0.0;			/* memory accumulator		*/
char c;					/* input key			*/
int oper;				/* last operator entered	*/
int memop;				/* last memory operator		*/
int equ;				/* an equal sign seen		*/
int collect;			        /* collecting digits		*/
int recall;				/* just did memory recall	*/

#define MEMOP 0
#define ACCOP 1
int optype;				/* acc or mem for fp recovery	*/
int fphandler();		       /* take care of floating errors 	*/
jmp_buf mark;

main()
{
        register int i;
		
     	dvinit();		     	     /* check dv, get display addr */
        setrattr(attr(FG,BG),0,0,ROWS-1,COLS-1); /* full window attributes */
        setrattr(attr(WF,WB),ACCR,ACCC,ACCR+1,ACCC+ANSLEN-1);
        		     		     /* answer window attr      */
        setrattr(attr(EC,WB),ERRR,ERRC,ERRR,ERRC+ANSLEN-1);     
        				     /* answer window attr,err  */
        setrattr(attr(WF,WB),MEMR,MEMC,MEMR,MEMC+ANSLEN-1);      
        				     /* mem answer window attr  */
        dispca(attr(HC,BG),hichar);          /* static highlight chars  */
        dispca(attr(FG,BG),lochar);          /* static normal chars     */
	errptr = NOERR;		     	     /* start error free	*/
	signal(SIGFPE,fphandler);
	if (setjmp(mark) == -1 ){	     /* here on fp error return	*/
		if (optype == ACCOP) faccum = 0.0;
		if (optype == MEMOP) fmemacc = 0.0;
		doerr();
	}
	oper = '\0';
	memop = '\0';
	equ = NO;
	collect = NO;	
	recall = NO;
	optype = ACCOP;
	
        /* input scanner and computations */
    while(1) {
	display();			    /* show accum and mem value */
	setmode();			    /* show current mode	*/
	memop = '\0';
        c = getch();			    /* wait, get char		*/
        c = toupper(c);
        errptr = BADKEY;  		    /* assume bad key		*/
	if ( c == 'M'){	    			/* memory ops 		*/
                errptr = NOERR;
		c = getch();
		c = toupper(c);
                switch (c) {
			CASE '+':           /* M+ or M- get pushed as = */
                        CASE '-':
                                memop = c;
                                c = '=';
                                break;
                        CASE 'C':       	/* MCLEAR */
                                fmemacc = 0.0;
				c = 0xFF;     /* don't want to see C again */
                                break;
                        CASE 'R':       	/* MRECALL */
				if (equ == NO) fsave = faccum;
                                faccum = fmemacc;
				recall = YES;	/* fake any entry */
				collect = YES;
                                break;
			DEFAULT:
			        errptr = BADKEY;    /* bad key	*/
				c = 0xFF;	/* bad key sequence */
                }
	}
        switch (c) {
		CASE 0x1B:			/* ESC ==> exit */
			exit(0);
		CASE '#':			/* Change Sign */
			errptr = NOERR;
			faccum = - faccum;
			break;
		CASE 'B':			/* binary mode */
                        errptr = NOERR;
                        mode = BIN;
			collect = NO;
			recall = NO;
                        break;
                CASE 'C':               /* clear entry or accumulator */
                        errptr = NOERR;
                        if (collect){
                                faccum = 0.0;
                        }
                        else {
                                faccum = 0.0;
                                fsave = 0.0;
                                oper = '\0';
                                equ = NO;
                        }
			collect = NO;
			recall = NO;
                        break;
                CASE 'D':               /* set decimal mode */
                        errptr = NOERR;
                        if (oper == 'A' || oper == 'O' || oper == 'X')
                                oper = '\0';
                        mode = DEC;
			collect = NO;
			recall = NO;
                        break;
                CASE 'H':               /* set hex mode */
                        errptr = NOERR;
                        mode = HEX;
			collect = NO;
			recall = NO;
                        break;
                CASE 'A':               /* logicals stack for = */
                CASE 'O':
                CASE 'X':
                        if (mode != DEC){       /* no decimal logicals */
				collect = NO;
				recall = NO;
                                errptr = NOERR;
                                oper = c;
                                equ = NO;
                        }
                        break;
                CASE '+':
                CASE '-':
                CASE '*':               /* standard ops */
                CASE '/':
                        errptr = NOERR;
                        if (equ == NO && collect){   /* a pending op to do */
                            ftemp = faccum;
                            switch (oper) {
                                CASE '*':
                                        faccum *= fsave;
                                        break;
                                CASE '/':
                                	if (faccum == 0.0)
                                		errptr = ZERODIV;
                                        else faccum = fsave / faccum;
                                        break;
                                CASE '+':
                                        faccum += fsave;
                                        break;
                                CASE '-':
                                        faccum = fsave - faccum;
                            }
                            fsave = ftemp;
                        }
			collect = NO;
			recall = NO;
                        oper = c;
                        equ = NO;
                        break;
                CASE '=':       	/* execute and chain operations */
                        errptr = NOERR;
		        if (equ == NO || (collect && equ)){ /* pending op or
		        				     pending entry */
                            ftemp = faccum;
                            switch (oper){
                                CASE 'A':
                                        faccum = (double)
                                             ((long)fsave & (long)faccum);
                                        break;
                                CASE 'O':
                                        faccum = (double)
                                        	((long)fsave | (long)faccum);
                                        break;
                                CASE 'X':
                                        faccum = (double)
                                        	((long)fsave ^ (long)faccum);
                                        break;
                                CASE '*':
                                        ftemp = fsave;
                                        if ((collect == NO) && (equ == NO))
                                                faccum *= faccum;
                                        else
                                                faccum *= fsave;
                                        break;
                                CASE '/':
                                        if ((collect == NO) && (equ == NO))
                                                faccum = 1 / faccum;
                                        else if (equ){
                                        	if (fsave == 0.0){
                                        		errptr = ZERODIV;
                                        		faccum = 0.0;
                                        	}
                                                else faccum = faccum / fsave;
					}
                                        else {
                                        	if (faccum == 0.0){
                                        		errptr = ZERODIV;
                                        		faccum = 0.0;
                                        	}
                                                else faccum = fsave / faccum;
					}
                                        break;
                                CASE '+':
                                        faccum += fsave;
                                        break;
                                CASE '-':
                                        if (equ)
                                                faccum = faccum - fsave;
                                        else
                                                faccum = fsave - faccum;
                            }
		        }
                        if (equ == NO)
                                fsave = ftemp;
                        equ = YES;
                        collect = NO;
                        recall = NO;
                        switch (memop){
                                CASE '\0':
                                        break;
                                CASE '+':
	                        	optype = MEMOP;
                                        fmemacc = faccum + fmemacc;
					optype = ACCOP;
                                        break;
                                CASE '-':
                                	optype = MEMOP;
                                        fmemacc = fmemacc - faccum;
                                        optype = ACCOP;
                        }
                        memop = '\0';
                        break;
                CASE '0':
                CASE '1':
                        addchar();
                        break;
                CASE '2':
                CASE '3':
                CASE '4':
                CASE '5':
                CASE '6':
                CASE '7':
                CASE '8':
                CASE '9':
                        if ( mode != BIN) {
                        	addchar();
                        	break;
                        }
                CASE '\0':      		/* special keys */
                        c = getch();		/* get 2nd part */
                        if (mode != HEX) break;
                        if ( c == 0x3F) c = 'A';	/* function key 5 */
                        else if (c == 0x40) c = 'B';	/* f6 */
                        else if (c == 0x41) c = 'C';	/* f7 */
                        else if (c == 0x42) c = 'D';	/* f8 */
                        else if (c == 0x43) c = 'E';	/* f9 */
                        else if (c == 0x44) c = 'F';	/* f10 */
                        else break;
                        addchar();
                        break;
                CASE '.':
                        if (mode != DEC) break;
                        addchar();
                        break;
                CASE 0x53:      	/* DEL (since follows 0 - not S) */
                CASE 0x08:      	/* Back Space */
                       	errptr = NOERR;
			if (collect == YES) {
                        	c = '\0';	/* delete flag */
                        	addchar();
                        }
    	}
    }
}

/* set attributes for rectangle */
setrattr(attrib, ulr, ulc, lrr, lrc) 
unsigned char attrib,ulr,ulc,lrr,lrc;
{
        unsigned char far *ptr;
        register int i, j;

        for ( i = ulr; i < lrr+1; i++){		/* rows counter 	*/
		ptr = addr.p + 1 + apos(i,ulc);
        	for ( j = ulc; j < lrc+1; j++){	/* column position 	*/
	                *ptr = attrib;
        	        ptr = ptr + 2;
        	}
        }
        return;
}

/* display an array of characters with attributes */
dispca(attrib,array)     
unsigned char attrib;
unsigned char *array;
{
        unsigned char far *ptr;

        while  (*(array) != 0 && *(array+1) != 0){
                ptr = addr.p + apos( *array,*(array+1) );
                *ptr = *(array+2);
                ptr++;
                *ptr = attrib;
                array = array +3;
        }
        return;
}

setmode()
{
	int i, mattr;
	for ( i = 0; i < 3; i++){
		setrattr(attr(HC,BG),modeloc[i][0],modeloc[i][1],modeloc[i][0],
			modeloc[i][1]);	     /* highlight 1st mode letr*/
		setrattr(attr(FG,BG),modeloc[i][0],modeloc[i][1]+1,
			modeloc[i][0], modeloc[i][1]+2);   /* lowlight rest */
	}
	setrattr(attr(SC,BG),modeloc[mode][0],modeloc[mode][1],
		modeloc[mode][0], modeloc[mode][1]+2);
					/* highlight select mode word */
	if (mode == DEC) mattr = attr(FG,BG);
	else mattr = attr(HC,BG);
	for ( i = 0; i < 3; i++){	/* set allowed and/or/xor command */
		setrattr(mattr,aoxloc[i][0],aoxloc[i][1],
			aoxloc[i][0],aoxloc[i][1]);
	}
	return;
}

addchar()	/* check and put an input character on entry string */
{
	register int i;
	int elen;
	double base;		/* base for conversion */
	double atof();		/* return types */
	static int dpoint;	/* saw a dp on this sequence */
	
	if (recall == YES) return;  /* can't modify mem recall */
	errptr = NOERR;
	if (collect == NO){	/* first char of entry so clear it out	*/
         	 if (equ == NO) fsave = faccum;
		 faccum = 0.0;
		 entry[0] = '\0';
		 dpoint = 0;
	}
	collect = YES;
	elen = strlen(entry);
	if (dpoint == 0 && entry[elen-1] == '.') entry[--elen] = '\0';
	if ( c != '\0'){	/* check length on additions */
		if ( (mode == HEX && elen == 8) ||
	     		(mode == DEC && dpoint == 0 && elen == ANSLEN -2) ||
	     		(elen == ANSLEN -1) ){
				errptr = ENTLENGTH;
				return;
			}
	}
	if (c == '.'){
		if (dpoint == 0) dpoint++;
		else {
			errptr = FORMATERR;
			return;
		}
	}
	if (c == '\0'){			/* delete a char */
		if (elen != 0){
			if (entry[--elen] == '.') dpoint--;
			entry[elen] = '\0'; 
		        if (elen == 0) { /* if last char del, set "0" */
		        	entry[0] = '0';
		        	elen = 1;
		        }
		}
	}
	else {				/* add a char */
		if (elen == 1 && entry[0] == '0'){
			if (c == '0') return;  /* no extra leading zeros */
			else elen--;	/* take off leading zero */
		}
		entry[elen++] = c;
		entry[elen] = '\0';
	} 
	if (mode == DEC) { 
		faccum = atof(entry);
	}
	else {	
		if (mode == HEX) base = 16.0;
		if (mode == BIN) base = 2.0;
		faccum = 0.0;
		for ( i = 0; i < elen; i++){
			faccum = faccum * base + ( (entry[i]<0x40)?
			    entry[i] - 0x30 : entry[i] - 0x41 + 10 );
		}
	}
	return;
}

/* update memory and accum display */
display()
{
	char abuf[30];
	char mbuf[30];
	char tbuf[30];
	
	unsigned char far *ptr;
	register int i,j;
	long int lint;
		
	if (mode == DEC){		
	        if (collect == NO || recall == YES){
			sprintf(abuf,"% #12.11lf",faccum);
			ffixup(abuf);
	        }
	        optype = MEMOP;
		sprintf(mbuf,"% #12.11lf",fmemacc);
		ffixup(mbuf);
		optype = ACCOP;
	}
	else if (mode == HEX) {
                if (collect == NO || recall ==YES){
			lint =  faccum;
			faccum = (double)lint;
			sprintf(abuf,"%13lX",lint);
		}
		optype = MEMOP;
		lint = (long)fmemacc;
		fmemacc = (double)lint;
		sprintf(mbuf,"%13lX",lint);
		optype = ACCOP;
	}
	else {
		if (collect == NO || recall == YES){
			i = (int)faccum;
			faccum = (double)i;
			itoa(i, tbuf, 2);
			sfixup (tbuf, abuf);
		}
		optype = MEMOP;
		i = (int)(fmemacc);
		fmemacc = (double)i;
		itoa(i, tbuf, 2);
		sfixup (tbuf, mbuf);
		optype = ACCOP;
	}
	if (collect == YES && recall == NO)
		    	sfixup (entry, abuf);  /* display entry string */
	
	ptr = addr.p + MEMPOS;
	for (i = 0; i < 13; i++){
		*ptr = mbuf[i];
		ptr = ptr + 2;
	}
	ptr = addr.p + ACPOS;
	for (i = 0; i < 13; i++){
		*ptr = abuf[i];
		ptr = ptr + 2;
	}
	doerr();
	return;
}

ffixup(buf)		/* display fixup for floating point */
char *buf;
{
	register int i, dotpos, zeros;
	char temp[14];
	
	for (i = 0; i <13 && buf[i] != '.'; i++);
	dotpos = i;
	if (dotpos >12){
		errptr = DISPOV;
		buf[12] = '>';
		buf[13] = '\0';
		doerr();
		return;
	}
	for (i = 12; i >1 && buf[i] == '0'; i--);
	if (i < 12){
		zeros = 12 - i;	/* number of trailing zeros */
		temp[0] = buf[0];
		for (i = 1; i <= zeros; i++) 
			temp[i] = ' ';
		for (; i <13; i++)
			temp[i] = buf[i-zeros];
		strcpy (buf,temp);
	}
	return;
}

sfixup(tbuf,obuf)		/* display fixup for strings */
char *tbuf, *obuf;
{
	int x;
	register int i,j;
	
	x = strlen(tbuf);
	if (x > ANSLEN) {	/* too long, display with ">" */
		errptr = DISPOV;
		strncpy (obuf, tbuf, ANSLEN-1);
		obuf[ANSLEN-1] = '>';
	}
	else {			/* right justify if short */
		for (i = 0; i < ANSLEN-x; i++)  obuf[i] = ' ';
		for (j = 0; i < ANSLEN; i++,j++) obuf[i] = tbuf[j];
	}
	return;
}

doerr()		/* update display of error string */
{
	unsigned char far *ptr;
	register int i;
	ptr = addr.p + apos(ERRR,ERRC+1);
	for (i = 0; i < ERRLEN ; i++){
		*ptr = *(errptr+i);
		ptr = ptr + 2;
	}
	return(OK);
}

dvinit()        /* Check for Desqview and use its buffer */
{
        rg.x.ax = 0x2B01;       	/* do date set as DV check */
        rg.x.cx = 0x4445;               /* "DESQ"an illegal date */
        rg.x.dx = 0x5351;
        int86(0x21,&rg,&rg);
        if (rg.h.al != 0xFF){		/* we are in desqview	*/
                rg.h.ah = 0xFE;		/* dv get buff addr	*/
		int86(0x10,&rg,&rg);
		segread(&segregs);
                addr.l=((unsigned long)segregs.es<<16)+(unsigned long)rg.x.di;
        }
	else {
		cputs ("\x07Program requires DESQview!\n");
		exit(1);
	}

	/* put the cursor outside the window */
	rg.h.ah = 0x2;
	rg.h.bh = 0;
	rg.x.dx = 0x151f;  /* row 24 col 79*/
	int86(0x10,&rg,&rg);
	return;
}

int fphandler(sig,num)
	int sig,num;
{
	if (num == FPE_ZERODIVIDE) errptr = ZERODIV;
	else errptr = OVERFLOW;
	doerr();
	_fpreset();
	longjmp(mark,-1);
}

_setargv()		/* dummy since we use no command line args */
{
}

_setenvp()		/* dummy since we don't use environment variables */
{
}
