Path: utzoo!utgpu!jarvis.csri.toronto.edu!mailrus!tut.cis.ohio-state.edu!bloom-beacon!apple!vsi1!wyse!mips!prls!philabs!micomvax!ncc!alberta!ubc-cs!ubc-bdcvax!holm From: holm@ubc-bdcvax.UUCP (Terrence W. Holm) Newsgroups: comp.os.minix Subject: ic(1) integer calculator (part 1 of 2) Message-ID: <412@ubc-bdcvax.UUCP> Date: 1 Feb 89 23:16:55 GMT Lines: 1002 EFTH MINIX report #66 - February 1989 - ic(1) Ic(1) is an integer calculator. It was originally posted as EFTH report #13 (May 1988). I find it very useful for bin/oct/dec/hex conversions and perhaps others have also. I am giving everyone who originally missed it another chance to download it. Terrence W. Holm holm@bdc.ubc.ca ---------------------------------------------------------- echo x - Makefile gres '^X' '' > Makefile << '/' XCFLAGS = -F -T. XICOBJ = ic.s ic_input.s ic_output.s X Xic: $(ICOBJ) X cc -T. $(ICOBJ) -o ic X chmem =5000 ic X X$(ICOBJ): ic.h X Xprint: X pr ic.h ic.c ic_input.c ic_output.c\ X Makefile | lpr / echo x - ic.1 gres '^X' '' > ic.1 << '/' XNAME X ic(1) - integer calculator X XSYNOPSIS X ic [ args ] ... X XDESCRIPTION X This is a simple RPN calculator, used for small calculations X and base conversions. All calculations are done using 32 bit X integers. X X The standard input is usually a keyboard and the standard output X requires a device with a "termcap" entry. X X The program starts by interpreting any as commands, where X the separation between arguments is considered to be the same as X the ENTER key. For example, "ic 692 784+". After reading the X arguments input is from the keyboard. X X X STACK OPERATIONS X X The operation of this program is similar to an RPN calculator. X A six level stack is used. The ENTER key pushes the stack up one X level. For example, "12+5" is entered as "12 ENTER 5 +". X X The top two entries on the stack are exchanged by the 'x' X command, and the stack is rolled down one (popped) by the X 'p' key. X X The top of the stack may be cleared by pressing the back-space X key. The whole stack and the registers are initialized by a 'z'. X X X NUMERIC ENTRY X X The input and output bases are initially decimal, but they may X be changed using the 'i' and 'o' commands. The 'i' command changes X both bases, but the 'o' command changes just the output base. X These commands take a one character argument of 'h', 'd', 'o' or X 'b' to change to Hexadecimal, Decimal, Octal or Binary. While the X input base is hexadecimal the letters 'a' through 'f' are used X to represent the decimal values 10 through 15. X X When the input base is decimal: multiply, divide and remainder X are signed, otherwise they are performed unsigned. X X The output base may also be changed to ASCII ('a'), this causes X the least significant 7 bits of a value to be displayed as a X character. To input an ASCII value the translate ('t') command X may be used, it accepts one character as its argument. X X X CALCULATIONS X X The arithmetic operations supported are: Negate ('.'), Add ('+'), X Subtract ('-'), Multiply ('*'), Divide ('/'), and Remainder ('%'). X The logical operations available are: Not ('~'), And ('&'), Or ('|'), X and Exclusive-or ('^'). X X After one of these operations the last top of stack value is X saved. It may be restored by pressing 'l' (L). X X X SAVING RESULTS X X Ten temporary registers are available. The Store ('s') command X followed by a digit ('0'..'9') will copy the top of the stack X to the specified register. The Recall ('r') command pushes the X contents of a register onto the top of the stack. X X If the Store command is followed by a '+' preceding the digit, then X the top of the stack will be added to the specified "accumulator" X register. X X Values may also be written to a file. The 'w' command writes the X top of the stack, using the current output base, to a file called X "pad" in the current directory. If the user does not have write X access to the current directory then the file "/tmp/pad_$USER" is X used as the scratch pad. The scratch pad file is erased on the X first use of the 'w' command within each new invocation of "ic". X X X LEAVING THE PROGRAM X X The Quit ('q') key causes an immediate exit. (End of file on X standard input, the SIGINT and the SIGQUIT interrupts also terminate X the program.) X X The 'm' command temporarily leaves "ic" by invoking the Minix "sh" X shell as a sub-process. X X For help while using "ic", hit the 'h' key. If an erroneous key X is pressed the bell will sound. X X X COMMAND SUMMARY X X Note that many commands have an alternative key-code available X on the extended AT keyboard. This aids entry by including most X commands on the right side of the keyboard. X X ENTER Enter (push up) X BS (DEL) Clear top of stack X X h Help X i Input base (h, d, o, b) X l (PGDN) Last top of stack X m Minix shell X o Output base (h, d, o, b, a) X p (DOWN) Pop stack (roll down) X q (END) Quit X r (LEFT) Recall (0-9) X s (RIGHT) Store [+] (0-9) X t Translate (char) X w (PGUP) Write top of stack to scratch pad X x (UP) Exchange top of stack X z (HOME) Zero all state X X . Change sign X + (+) Add X - (-) Subtract X * Multiply X / Divide X % (sh/5) Remainder X X ~ Not X & And X | Or X ^ Exclusive-or X XFILES X ./pad X /tmp/pad_$USER X XSEE ALSO X dc(1), expr(1) X XAUTHOR X Terrence W. Holm / echo x - ic.c gres '^X' '' > ic.c << '/' X/****************************************************************/ X/* */ X/* ic.c */ X/* */ X/* The main loop of the "Integer Calculator". */ X/* */ X/****************************************************************/ X/* origination 1988-Apr-6 Terrence W. Holm */ X/* added Exec_Shell() 1988-Apr-11 Terrence W. Holm */ X/* added "s+" 1988-Apr-18 Terrence W. Holm */ X/* added cmd line args 1988-May-13 Terrence W. Holm */ X/* 'i' also does 'o' 1988-May-28 Terrence W. Holm */ X/* if ~dec:unsigned *%/ 1988-Jul-10 Terrence W. Holm */ X/****************************************************************/ X X X X X X X#include X#include X X#include "ic.h" X X X X Xstatic char copyright[] = { "ic (c) Terrence W. Holm 1988" }; X X X X X/****************************************************************/ X/* */ X/* main() */ X/* */ X/* Initialize. Enter the main processing loop. */ X/* */ X/****************************************************************/ X X Xmain( argc, argv ) X int argc; X char *argv[]; X X { X ic_state state; /* This state record is passed */ X /* to most subroutines */ X X Init_State( &state ); X X X state.scratch_pad = (FILE *) NULL; /* No 'w' command yet */ X X X X Init_Getc( argc, argv ); /* Refs to command line args */ X X X X if ( Init_Termcap() == NULL ) X { X fprintf( stderr, "ic requires a termcap entry\n" ); X exit( 1 ); X } X X X Save_Term(); /* Save terminal characteristics */ X X X if ( signal( SIGINT, SIG_IGN ) != SIG_IGN ) X { X signal( SIGINT, Sigint ); X signal( SIGQUIT, Sigint ); X } X X X Set_Term(); /* Setup terminal characteristics */ X X X X Draw_Screen( &state ); X X while (1) X { X int rc = Process( &state, Get_Char() ); X X if ( rc == EOF ) X break; X X if ( rc == ERROR ) X putchar( BELL ); X } X X X Reset_Term(); /* Restore terminal characteristics */ X X exit( OK ); X } X X X X X X X X X/****************************************************************/ X/* */ X/* Init_State() */ X/* */ X/* Initialize the state record. */ X/* */ X/****************************************************************/ X X XInit_State( s ) X ic_state *s; X X { X s->stack[0] = 0; X s->stack_size = 1; X s->register_mask = 0x000; X s->last_tos = 0; X s->mode = LAST_WAS_ENTER; X s->input_base = DECIMAL; X s->output_base = DECIMAL; X } X X X X X X X X X/****************************************************************/ X/* */ X/* Sigint() */ X/* */ X/* Terminate the program on an interrupt (^C) */ X/* or quit (^\) signal. */ X/* */ X/****************************************************************/ X X XSigint() X X { X Reset_Term(); /* Restore terminal characteristics */ X X exit( 1 ); X } X X X X X X X X X/****************************************************************/ X/* */ X/* Process( state, input_char ) */ X/* */ X/* Determine the function requested by the */ X/* input character. Returns OK, EOF or ERROR. */ X/* */ X/****************************************************************/ X X Xint Process( s, c ) X ic_state *s; X int c; X X { X switch ( c ) X { X case '0' : X case '1' : X case '2' : X case '3' : X case '4' : X case '5' : X case '6' : X case '7' : X case '8' : X case '9' : X X return( Enter_Numeric( s, (int) c - '0' ) ); X X X case 'a' : X case 'b' : X case 'c' : X case 'd' : X case 'e' : X case 'f' : X X return( Enter_Numeric( s, (int) c - 'a' + 10 ) ); X X X case 'h' : case '?' : /* Help */ X X Draw_Help_Screen(); X X Get_Char(); X X Draw_Screen( s ); X return( OK ); X X X case 'i' : /* Set i/p and o/p base */ X X { X int numeral; X X Draw_Prompt( "Base?" ); X X numeral = Get_Base( Get_Char() ); X X Erase_Prompt(); X X if ( numeral == ERROR || numeral == ASCII ) X return( ERROR ); X X s->input_base = numeral; X s->output_base = numeral; X s->mode = LAST_WAS_FUNCTION; X X Draw_Screen( s ); X return( OK ); X } X X X case 'l' : case ESC_PGDN : /* Get last tos value */ X X if ( s->mode != LAST_WAS_ENTER ) X Push( s ); X X s->stack[0] = s->last_tos; X X s->mode = LAST_WAS_FUNCTION; X X Draw_Stack( s ); X return( OK ); X X X case 'm' : /* Invoke a Minix shell */ X X Reset_Term(); X X Exec_Shell(); X X Set_Term(); X X Draw_Screen( s ); X return( OK ); X X X case 'o' : /* Set output base */ X X { X int numeral; X X Draw_Prompt( "Base?" ); X X numeral = Get_Base( Get_Char() ); X X Erase_Prompt(); X X if ( numeral == ERROR ) X return( ERROR ); X X s->output_base = numeral; X s->mode = LAST_WAS_FUNCTION; X X Draw_Screen( s ); X return( OK ); X } X X X case 'p' : case ESC_DOWN : /* Pop: Roll down stack */ X X { X long int temp = s->stack[0]; X int i; X X for ( i = 0; i < s->stack_size-1; ++i ) X s->stack[i] = s->stack[i+1]; X X s->stack[ s->stack_size-1 ] = temp; X X s->mode = LAST_WAS_FUNCTION; X X Draw_Stack( s ); X return( OK ); X } X X X case 'q' : case ESC_END : /* Quit */ X case EOF : case CTRL_D : X X return( EOF ); X X X case 'r' : case ESC_LEFT : /* Recall from register */ X X { X int numeral; X X Draw_Prompt( "Register?" ); X X numeral = Get_Char() - '0'; X X Erase_Prompt(); X X if ( numeral < 0 || numeral >= REGISTERS || X ((1 << numeral) & s->register_mask) == 0 ) X return( ERROR ); X X if ( s->mode != LAST_WAS_ENTER ) X Push( s ); X X s->stack[0] = s->registers[numeral]; X X s->mode = LAST_WAS_FUNCTION; X X Draw_Stack( s ); X return( OK ); X } X X X case 's' : case ESC_RIGHT : /* Store in register, */ X /* or accumulate if */ X /* "s+" is typed. */ X X { X int c; X int numeral; X X X Draw_Prompt( "Register?" ); X X c = Get_Char(); X X if ( c == ESC_PLUS ) X c = '+'; /* Allow keypad '+' */ X X X if ( c == '+' ) X { X Draw_Prompt( "Accumulator?" ); X numeral = Get_Char() - '0'; X } X else X numeral = c - '0'; X X Erase_Prompt(); X X X if ( numeral < 0 || numeral >= REGISTERS ) X return( ERROR ); X X X if ( c != '+' || (s->register_mask & (1 << numeral)) == 0 ) X { X s->register_mask |= 1 << numeral; X s->registers[numeral] = s->stack[0]; X } X else X s->registers[numeral] += s->stack[0]; X X X s->mode = LAST_WAS_FUNCTION; X X Draw_Registers( s ); X return( OK ); X } X X X case 't' : /* Translate from ASCII */ X X { X long int numeral; X X Draw_Prompt( "Character?" ); X X numeral = (long int) Getc(); X X Erase_Prompt(); X X if ( s->mode != LAST_WAS_ENTER ) X Push( s ); X X s->stack[0] = numeral; X X s->mode = LAST_WAS_FUNCTION; X X Draw_Stack( s ); X return( OK ); X } X X X case 'w' : case ESC_PGUP : /* Write tos to a file */ X X { X if ( (int) s->scratch_pad == NULL ) X { X /* Try to open a scratch pad file */ X X strcpy( s->file_name, "./pad" ); X X if ( (s->scratch_pad=fopen(s->file_name,"w")) == NULL ) X { X /* Unsuccessful, try in /tmp */ X char *id; X X strcpy( s->file_name, "/tmp/pad_" ); X X if ( (id=cuserid(NULL)) == NULL ) X return( ERROR ); X X strcat( s->file_name, id ); X X if ( (s->scratch_pad=fopen(s->file_name,"w")) == NULL ) X return( ERROR ); X } X X Draw_Screen( s ); X } X X X /* We have a successfully opened file */ X X Print_Number( s->scratch_pad, s->stack[0], s->output_base ); X putc( '\n', s->scratch_pad ); X fflush( s->scratch_pad ); X X s->mode = LAST_WAS_FUNCTION; X X return( OK ); X } X X X case 'x' : case ESC_UP : /* Exchange top of stack */ X X { X long int temp = s->stack[0]; X X if ( s->stack_size < 2 ) X return( ERROR ); X X s->stack[0] = s->stack[1]; X s->stack[1] = temp; X X s->mode = LAST_WAS_FUNCTION; X X Draw_Stack( s ); X return( OK ); X } X X X case 'z' : case ESC_HOME: /* Clear all */ X X Init_State( s ); X X Draw_Screen( s ); X return( OK ); X X X case BS : case DEL : /* Clear top of stack */ X X s->stack[0] = 0; X X s->mode = LAST_WAS_ENTER; X X Draw_Top_of_Stack( s ); X return( OK ); X X X case '\n' : /* Enter */ X X Push( s ); X X s->mode = LAST_WAS_ENTER; X X Draw_Stack( s ); X return( OK ); X X X case '.' : /* Change sign */ X X s->last_tos = s->stack[0]; X X s->stack[0] = - s->stack[0]; X X s->mode = LAST_WAS_FUNCTION; X X Draw_Top_of_Stack( s ); X return( OK ); X X X case '+' : case ESC_PLUS : /* Add */ X X if ( s->stack_size < 2 ) X return( ERROR ); X X s->last_tos = s->stack[0]; X X Pop( s ); X X s->stack[0] += s->last_tos; X X s->mode = LAST_WAS_FUNCTION; X X Draw_Stack( s ); X return( OK ); X X X case '-' : case ESC_MINUS : /* Subtract */ X X if ( s->stack_size < 2 ) X return( ERROR ); X X s->last_tos = s->stack[0]; X X Pop( s ); X X s->stack[0] -= s->last_tos; X X s->mode = LAST_WAS_FUNCTION; X X Draw_Stack( s ); X return( OK ); X X X case '*' : /* Multiply */ X X if ( s->stack_size < 2 ) X return( ERROR ); X X s->last_tos = s->stack[0]; X X Pop( s ); X X if ( s->input_base == DECIMAL ) X s->stack[0] *= s->last_tos; X else X s->stack[0] = (long int) X ( UNS(s->stack[0]) * UNS(s->last_tos) ); X X s->mode = LAST_WAS_FUNCTION; X X Draw_Stack( s ); X return( OK ); X X X case '/' : /* Divide */ X X if ( s->stack_size < 2 || s->stack[0] == 0 ) X return( ERROR ); X X s->last_tos = s->stack[0]; X X Pop( s ); X X if ( s->input_base == DECIMAL ) X s->stack[0] /= s->last_tos; X else X s->stack[0] = (long int) X ( UNS(s->stack[0]) / UNS(s->last_tos) ); X X s->mode = LAST_WAS_FUNCTION; X X Draw_Stack( s ); X return( OK ); X X X case '%' : case ESC_5 : /* Remainder */ X X if ( s->stack_size < 2 || s->stack[0] == 0 ) X return( ERROR ); X X s->last_tos = s->stack[0]; X X Pop( s ); X X if ( s->input_base == DECIMAL ) X s->stack[0] %= s->last_tos; X else X s->stack[0] = (long int) X ( UNS(s->stack[0]) % UNS(s->last_tos) ); X X s->mode = LAST_WAS_FUNCTION; X X Draw_Stack( s ); X return( OK ); X X X case '~' : /* Not */ X X s->last_tos = s->stack[0]; X X s->stack[0] = ~ s->stack[0]; X X s->mode = LAST_WAS_FUNCTION; X X Draw_Top_of_Stack( s ); X return( OK ); X X X case '&' : /* And */ X X if ( s->stack_size < 2 ) X return( ERROR ); X X s->last_tos = s->stack[0]; X X Pop( s ); X X s->stack[0] &= s->last_tos; X X s->mode = LAST_WAS_FUNCTION; X X Draw_Stack( s ); X return( OK ); X X X case '|' : /* Or */ X X if ( s->stack_size < 2 ) X return( ERROR ); X X s->last_tos = s->stack[0]; X X Pop( s ); X X s->stack[0] |= s->last_tos; X X s->mode = LAST_WAS_FUNCTION; X X Draw_Stack( s ); X return( OK ); X X X case '^' : /* Exclusive-or */ X X if ( s->stack_size < 2 ) X return( ERROR ); X X s->last_tos = s->stack[0]; X X Pop( s ); X X s->stack[0] ^= s->last_tos; X X s->mode = LAST_WAS_FUNCTION; X X Draw_Stack( s ); X return( OK ); X X X default: X return( ERROR ); X } X } X X X X X X X X X/****************************************************************/ X/* */ X/* Enter_Numeric( state, numeral ) */ X/* */ X/* A numeral (0 to 15) has been typed. */ X/* If a number is currently being entered */ X/* then shift it over and add this to the */ X/* display. If the last operation was a function */ X/* then push up the stack first. If the last */ X/* key was "ENTER", then clear out the top of */ X/* the stack and put the numeral there. */ X/* */ X/* Returns OK or ERROR. */ X/* */ X/****************************************************************/ X X Xint Enter_Numeric( s, numeral ) X ic_state *s; X int numeral; X X { X if ( numeral >= s->input_base ) X return( ERROR ); X X X switch ( s->mode ) X { X case LAST_WAS_FUNCTION : X Push( s ); X s->stack[0] = numeral; X Draw_Stack( s ); X break; X X case LAST_WAS_NUMERIC : X s->stack[0] = s->stack[0] * s->input_base + numeral; X Draw_Top_of_Stack( s ); X break; X X case LAST_WAS_ENTER : X s->stack[0] = numeral; X Draw_Top_of_Stack( s ); X break; X X default: X fprintf( stderr, "Internal failure (mode)\n" ); X Sigint(); X } X X s->mode = LAST_WAS_NUMERIC; X X return( OK ); X } X X X X X X X X X/****************************************************************/ X/* */ X/* Push( state ) */ X/* */ X/* Push up the stack one level. */ X/* */ X/****************************************************************/ X X XPush( s ) X ic_state *s; X X { X int i; X X if ( s->stack_size == STACK_SIZE ) X --s->stack_size; X X for ( i = s->stack_size; i > 0; --i ) X s->stack[i] = s->stack[i-1]; X X ++s->stack_size; X } X X X X X X X X X/****************************************************************/ X/* */ X/* Pop( state ) */ X/* */ X/* Pop the stack down one level. */ X/* This routine is only called with */ X/* the stack size > 1. */ X/* */ X/****************************************************************/ X X XPop( s ) X ic_state *s; X X { X int i; X X for ( i = 0; i < s->stack_size-1; ++i ) X s->stack[i] = s->stack[i+1]; X X --s->stack_size; X } X X X X X X X X X/****************************************************************/ X/* */ X/* Exec_Shell() */ X/* */ X/* Fork off a sub-process to exec() the shell. */ X/* */ X/****************************************************************/ X X XExec_Shell() X X { X int pid = fork(); X X if ( pid == -1 ) X return; X X X if ( pid == 0 ) X { X /* The child process */ X X extern char **environ; X char *shell = getenv( "SHELL" ); X X if ( shell == NULL ) X shell = "/bin/sh"; X ------------------------------------------------------------------