rsalz@bbn.com (Rich Salz) (05/18/88)
Submitted-by: Wayne Mesard <mesard@bbn.com> Posting-number: Volume 14, Issue 98 Archive-name: calc [ This program evalues mathematical expressions, like calc '2 * 3 / sin 45' it works off the command line, or standard input. Yeah, you could consider this redundant given dc and bc, but I find this easier to use than either one of them. Porters beware of the machine.h / mch_defines stuff. --r$ ] #! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh <file", e.g.. If this archive is complete, you # will see the following message at the end: # "End of shell archive." PATH=/bin:/usr/bin:/usr/ucb ; export PATH if test -f 'Makefile' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'Makefile'\" else echo shar: Extracting \"'Makefile'\" \(195 characters\) sed "s/^X//" >'Makefile' <<'END_OF_FILE' BIN = ../bin/ CFLAGS = -O X calc: $(BIN)calc X X$(BIN)calc: calc.c machine.h X $(CC) calc.c -lm -o $(BIN)calc X machine.h: X $(CC) mch_defines.c -o mch_defines X mch_defines > machine.h X rm mch_defines END_OF_FILE if test 195 -ne `wc -c <'Makefile'`; then echo shar: \"'Makefile'\" unpacked with wrong size! fi # end of 'Makefile' fi if test -f 'POSTME' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'POSTME'\" else echo shar: Extracting \"'POSTME'\" \(0 characters\) sed "s/^X//" >'POSTME' <<'END_OF_FILE' END_OF_FILE if test 0 -ne `wc -c <'POSTME'`; then echo shar: \"'POSTME'\" unpacked with wrong size! fi # end of 'POSTME' fi if test -f 'calc.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'calc.c'\" else echo shar: Extracting \"'calc.c'\" \(11315 characters\) sed "s/^X//" >'calc.c' <<'END_OF_FILE' X/************************************************************************** X * calc: a command-line calculator program * X * Copyright (c) 1988 Wayne Mesard * X * * X * This is free software. It may be reproduced, retransmitted, * X * redistributed and otherwise propogated at will, provided that * X * this notice remains intact and in place. * X * * X * Please direct bug reports, code enhancements and comments * X * to mesard@BBN.COM. * X * * X **************************************************************************/ X X X#include <stdio.h> X#include <ctype.h> X#include <strings.h> X#include <math.h> X#include <setjmp.h> X X#define PIdiv180 0.017453292519943295 X#define PI 3.14159265358979323846 X#include "machine.h" X#ifdef Mch_Lsz X# define TwoToTheNLessOne (unsigned long)(1<<(Mch_Lsz-1)) X#else /* Assume 16 bits */ X# define TwoToTheNLessOne 32768 X#endif X X#define MAXLINE 512 X X#define OP "{([" X#define CP "})]" X#define SYMBOLS "{([+-/xX*%:&^|<>~gnpst" /* Used to distinguish unary from X binary minus. Letters at end of X string are ends of function names. */ X X#define SYMBOL_LOWERS "xabcdef" /* Used to detect implied multiplication. X * Identifies these as symbols and not X * part of a function name. X */ X X#define DECFMT "\t% .16g\n" X#define BINFMT "\tb%*s\n" X#define FLTFMT "\t% .*f\n" X#define HEXFMT "\th%lX\n" X#define OCTFMT "\to%lo\n" X#define ASCFMT "\t@%c\n" X jmp_buf err_recover; int abort_on_err = 0; double answer = 0.0; X main(argc, argv) /* calc: a desk-top calculator */ int argc; char *argv[]; X{ X char s[MAXLINE]; X int precision = -1; X char mode = '\0'; X int getwhitlessline(), isatty(); X int keepchecking = 1; X X for (argv++, argc--; argc && **argv == '-' && keepchecking; argv++, argc--) X switch ((*argv)[1]) { X case 'p': X if (isdigit((*argv)[2])) X precision = atoi( &(*argv)[2]); X else { X abort_on_err = 1; X error("bad argument to -p option"); X } X break; X case 'e': X abort_on_err = 1; X break; X case 'd': X case 'h': X case 'o': X case 'b': X case '@': X mode = (*argv)[1]; X break; X case '\0': X keepchecking = 0; X break; X default: /* Oops, this must be a unary minus, not an option. */ X keepchecking = 0; X argv--; X argc++; X break; X } X X if (argc) { X s[0] = '\0'; X while (argc-- > 0) X strcat (s, *argv++); X abort_on_err = 1; X Compute (mode, s, precision); X } X else { X if (isatty(0)) X printf ("Enter equations. Blank line to terminate.\n"); X setjmp(err_recover); X while (getwhitlessline (s)) X Compute (mode, s, precision); X } X} X X Compute(mode, s, precision) char mode, *s; int precision; X{ X double calc(); X char *ftob(), binnum[MAXLINE]; X X answer = calc(s); X switch(mode) { X case '\0': X case 'd': X if (precision >= 0) X printf(FLTFMT, precision, answer); X else X printf(DECFMT, answer); X break; X case 'h': X printf(HEXFMT, (unsigned long) answer); X break; X case 'o': X printf(OCTFMT, (unsigned long) answer); X break; X case 'b': X printf(BINFMT, (precision > 0 ? precision : 0), X ftob(binnum, answer)); X break; X case '@': X printf(ASCFMT, (char) answer); X break; X } X} X X X X/******* X * When debugging uncomment this proc and change the following proc's name X * to calc2 X * double calc(e) char *e; X{ X double calc2(); X double val; X X printf("calling with **%s**\n", e); X val = calc2(e); X printf("returning **%f**\n", val); X return(val); X} X*******/ X X X/* Recursively, parse an equation string. X * This is hideously inefficient, since findop() is called on each invokation. X * O(n) would be possible if findop() where modified to walk through the string X * once and build a priority queue for evaluation of operators. X * But hey, the kids love it, and I know for a fact that my Data Structures X * prof never wrote a linear algorithm in his life:-) X */ X double calc(eqn) char *eqn; X{ X double vleft, temp; X long tempi; X char left[MAXLINE], eqncp[MAXLINE]; X char *findop (), *opptr; X double btof(); X X while (*eqn == ' ' || *eqn == '\t') X eqn++; X X if (!*eqn) X error("missing expression"); X X else if (opptr = findop(eqn)) { X strncpy (left, eqn, opptr - eqn); X left[opptr - eqn] = '\0'; X vleft = calc(left); X switch (*opptr) { X case '+': X return(vleft + calc(++opptr)); X case '-': X return(vleft - calc(++opptr)); X case '/': X if ((temp = calc(++opptr)) == 0.0) X error ("division by zero"); X else X return(vleft / temp); X case 'x': X case 'X': X case '*': X return(vleft * calc(++opptr)); X case '%': X if ((temp = fabs(floor (calc(++opptr)+0.5))) == 0.0 || X temp > (TwoToTheNLessOne-1)) X error("bad argument to modulo"); X return((double)((long) (floor (vleft) + 0.5) % X (long) temp)); X case ':': X return(pow(vleft, calc(++opptr))); X case '&': X return((double)((unsigned long)vleft & X (unsigned long)calc(++opptr))); X case '^': X return((double)((unsigned long)vleft ^ X (unsigned long)calc(++opptr))); X case '|': X return((double)((unsigned long)vleft | X (unsigned long)calc(++opptr))); X case '<': X return((double)((unsigned long)vleft << X (unsigned long)calc(++opptr))); X case '>': X return((double)((unsigned long)vleft >> X (unsigned long)calc(++opptr))); X X default: /* implied multiplication */ X return(vleft * calc(opptr)); X } X } X X else if (index (OP, *eqn)) { X strcpy(eqncp, ++eqn); X eqncp[strlen (eqncp) - 1] = '\0'; X return(calc (eqncp)); X } X X else if (*eqn == '+') X return(calc(eqn+1)); X else if (*eqn == '-') X return(-1.0 * calc(eqn+1)); X else if (*eqn == '~') X return((double)(~ (unsigned long)calc(eqn+1))); X X else if (strncmp(eqn, "sin", 3) == 0) X return(sin (calc(eqn+3) * PIdiv180)); X else if (strncmp(eqn, "cos", 3) == 0) X return(cos (calc(eqn+3) * PIdiv180)); X else if (strncmp(eqn, "tan", 3) == 0) X return(tan (calc(eqn+3) * PIdiv180)); X else if (strncmp(eqn, "atan", 4) == 0) X return(atan (calc(eqn+4)) / PIdiv180); X X else if (strncmp(eqn, "sqrt", 4) == 0) X return(sqrt (calc(eqn+4))); X else if (strncmp(eqn, "log", 3) == 0) X return(log10 (calc(eqn+3))); X else if (strncmp(eqn, "ln", 2) == 0) X return(log (calc(eqn+2))); X else if (strncmp(eqn, "exp", 3) == 0) X return(exp (calc(eqn+3))); X else if (strncmp(eqn, "pi", 2) == 0 || strncmp(eqn, "PI", 2) == 0) X return(PI); X else if (strncmp(eqn, "prev", 4) == 0) X return(answer); X X else if (*eqn == 'h') { X sscanf(eqn+1, "%lx", &tempi); X return((double) tempi); X } X else if (*eqn == 'o') { X sscanf(eqn+1, "%lo", &tempi); X return((double) tempi); X } X else if (*eqn == 'b') X return(btof(eqn+1)); X else if (*eqn == '@') X return((double) *(eqn+1)); X else if (!(isdigit(*eqn) || *eqn == '.') ) X error("illegal expression"); X else X return(atof (eqn)); X} X X X X/* X * Takes a parenthesized expression and returns a pointer to the closing paren. X */ X char *findclose(s) char *s; X{ X register int lev = 0; X X for (; *s && !(lev==1 && index(CP, *s)); s++) X if (index(OP, *s)) X lev++; X else if (index(CP, *s)) X lev--; X X if (!*s) X error("unmatched open paren"); X else X return(s); X} X X X/** Precedence levels for binary operators **/ X X#define OPTYPE int X#define NONOP 0 X#define NULLOP 1 X#define EXP 3 X#define MULT 5 X#define DIV 5 X#define MOD 6 X#define ADD 7 X#define SUBTR 7 X#define LSHIFT 8 X#define RSHIFT 8 X#define BAND 9 X#define BXOR 10 X#define BOR 11 X char *findop(s) char *s; X{ X register OPTYPE op; X OPTYPE bestop = NULLOP; X char *bestptr = 0; X register char last = '\0'; X X while (*s) { X op = NONOP; X if (*s == ' ' || *s == '\t') { /* Don't let lasts get assigned to space */ X s++; X continue; X } X else { X switch (*s) { X case ':': X op = EXP; X break; X case '%': X op = MOD; X break; X case 'x': X case 'X': X case '*': X if (!(*s=='x' && last=='e' && *(s+1)=='p')) /* exp() function */ X op = MULT; X break; X case '/': X op = DIV; X break; X case '+': X /* "+" means unary plus (not add) if it follows a X * symbol or a function name. X */ X if (!index(SYMBOLS, last)) X op = ADD; X break; X case '-': X /* "-" means unary minus (not subtract) if it follows a X * symbol or a function name. X */ X if (!index(SYMBOLS, last)) X op = SUBTR; X break; X case '<': X op = LSHIFT; X break; X case '>': X op = RSHIFT; X break; X case '&': X op = BAND; X break; X case '^': X op = BXOR; X break; X case '|': X op = BOR; X break; X default: X /* Implied multiplication occurs when a digit or a X * close paren is followed by a func-call, or an open X * paren. The check for "co" and "at" is to distinguish X * 'c' and 'a' as hex digits and their appearance in X * "cos" and "atan". X */ X if ((last && (isdigit(last) || index(CP, last))) && X ((islower(*s) || index(OP, *s)) || X (!isdigit(last) && isdigit(*s))) && X (!index(SYMBOL_LOWERS, *s) || X !strncmp("co", s, 2) || !strncmp("at", s, 2))) X op = MULT; X } X X if (op >= bestop) { X bestop = op; X bestptr = s; X } X } X X if (index(OP, *s)) X s = findclose(s); X X last = *s++; X } X return(bestptr); X} X X X X/* X * Places a binary representation of "val" in the string "s" and returns X * a pointer to the start of that string. "val" should be (or will be coerced X * to )an integer between +/- 2^n, where n is the number of bits in a long int. X */ X char *ftob(s, val) char *s; double val; X{ X unsigned long lval = (val<0.0 ? -val : val); X unsigned long i; X char *s0 = s; X X if (lval == 0) X *s++ = '0'; X else X for (i = TwoToTheNLessOne; i; i>>=1) X if (lval & i) X *s++ = '1'; X else { X *s = '0'; X if (s != s0) X s++; X } X X *s = '\0'; X return(s0); X} X X X X/* X * Takes a string containing a binary number and returns its X * decimal equivelant. X */ X double btof(s) char *s; X{ X unsigned long i, val = 0; X X for (i = (unsigned long)1<<(strlen(s)-1); i; i>>=1, s++) X if (*s == '1') X val |= i; X else if (*s != '0') X error("bad binary digit"); X X X return((double) val); X} X X X X X/* X * Reads a line from the stdin, and puts it in s after striping X * off all spaces and tabs. X * Returns the length of s. X */ X int getwhitlessline(s) char *s; X{ X register int i, c; X X for(i = 0; i <= MAXLINE && ((c=getchar()) != '\n') && c!=EOF; i+=(c!=' ' && c!='\t')) X s[i] = c; X s[i] = '\0'; X return(i); X} X X X X/* X * Displays an error message and exits unless a jmp_buf has been X * set to return to in just such emergencies. (Capt. Kirk always X * defined a jmp_buf.) X */ X error(msg) char *msg; X{ X printf("calc: error--%s\n", msg); X if (abort_on_err) X exit(1); X else X longjmp(err_recover, 0); X} X END_OF_FILE if test 11315 -ne `wc -c <'calc.c'`; then echo shar: \"'calc.c'\" unpacked with wrong size! fi # end of 'calc.c' fi if test -f 'calc.man' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'calc.man'\" else echo shar: Extracting \"'calc.man'\" \(5961 characters\) sed "s/^X//" >'calc.man' <<'END_OF_FILE' X.TH CALC 1L "24 February 1988" " " " " X X.de AI \"Init annotation environment. X.PD 0 X.nf X.na X.ta .5i X.. X.de AU \"Uninit annotation environment. X.PD X.fi X.ad X.. X.de BA \"Begin annotation X.fi X.IP "\\$1" 25 X.. X.de EA \"End annotation X.nf X.PP X.. X X.SH NAME calc \- a command line calculator X X.SH SYNOPSIS X.B calc X[ X.B \-d \-h \-o \-b \-@ X.BI \-p n X.B X\-e \- X] X[ X.I expression X] X X.SH DESCRIPTION X.I calc evaluates mathematical expressions. X An expression is any decimal number in integer or real format. It may also be a binary, octal or hexidecimal integer prefixed by, respectively, X.I b, X.I o, or X.I h, or an ASCII character, prefixed by X.I @. X An expression is also any expression preceded by one of the following: X X.nf X.ta 1i 2i 3i X sin log - (unary minus) X cos ln + (unary plus) X tan sqrt ~ (one's complement) X atan exp X.fi X or any two expressions separated by one of the following operators: X X.nf X.ta 2i +.5i +.5i X.RS 1i Exponentiation: : Multiplication: * x X X (none: implied multiplication) Division: / Modulo: % Addition: + Subtraction: - Left shift: < Right shift: > Bitwise AND: & Bitwise XOR: ^ BitWise OR: | X.RE X.fi X An expression is any expression preceded by an opening delimeter: X X.nf X.ta 1i +.5i +.5i X { [ ( X.fi X and followed by a closing delimeter: X X X.nf X.ta 1i +.5i +.5i X } ] ) X.fi X The special symbols "pi" and "PI" are also valid expressions. So is X"prev" which returns the value of the previous equation X(multiple-expression mode only). X If the expression is omitted from the command line, then the program will be in multiple-expression mode and repeatedly read expressions from the standard input until it encounters an empty line or an end of file. X X.SH OPTIONS X.IP\fB\-d\fP X(The default mode.) The answer is printed as a decimal number, or in scientific format if it is very large. X.IP\fB\-o\fP The answer is printed in octal, rounded down to the nearest integer. X.IP\fB\-h\fP The answer is printed in hexadecimal, rounded down to the nearest integer. X.IP\fB\-b\fP The answer is printed in binary, rounded down to the nearest integer. X.IP\fB\-@\fP The answer is printed as an ASCII character (modulo 128). X.IP\fB\-p\fP\fIn\fP Only meaningful in decimal and binary mode. Specifies the precision (the number of digits appearing after the decimal point) for a decimal number, or the number of minimum number of digits appearing in a binary number. (In order to line up the columns when multiple calculations are being performed.) There must not be a space between the ``\fBp\fP'' and X.I n. X.IP\fB\-e\fP Will cause X.I calc to exit when a bad expression is entered. This is only meaningful in multiple-expression mode. X.IP\fB\-\fP Indicates the end of the argument list. This is used when the beginning of the expression might accidently be interpreted as an option. (See EXAMPLES below.) X X.SH USAGE NOTES Arguments to trig functions are specified in degrees. X All binary operators group left-to-right, unary operators and functions group right-to-left. Priority of functions and operators is almost identical to that of C (except modulo is slightly lower here): X X.RS .5i X.nf X.ta 1.5i +1.75i Highest: Unary Op's Functions X Exponentiation X Multiplication Division X Modulo X Addition Subtraction X Left shift Right shift X And X Xor Lowest: Or X.fi X.RE X The C shell use many of X.IRcalc 's symbols for its own evil purposes. These include all three pairs of delimeters, and the asterisk. Whenever you need a delimeter, you are advised to enclose the entire expression in double quotes to keep the shell from messing with it. Alternatively, you can omit the expression from the command line, and have X.I calc prompt you for it, in which case the shell will never see what is typed. X Computations are performed using double precision floating point numbers with the following exceptions: The modulo operation (%) rounds its arguments to the nearest integer. Bitwise and bit shift operations expect (or will truncate to) positive integers. Hex and octal modes expect (or will truncate to) the nearest integer. When an integer is expected, it must be less than the largest long integer allowed on the machine (typically, X.if t 2\u\s-231\s0\d). X.if n 2:31). You will get undefined results if you go sticking large numbers where they don't belong. X X.SH EXAMPLES X.AI calc 2 + 5 x 6 X 32 X X.BA "calc '(2 + 5) x 6'" Parens quoted to hide them from the shell. X.EA X 42 X calc -p2 "atan(tan(45))" X 45.00 X calc -h 20 X h14 X calc -o @A X o101 X X.BA "calc -b 2:8 + 3" Exponentiation takes precidence over addition. X.EA X b100000011 X calc b101 - hc X -7 X X.BA "calc - -h4ff" X``\fB-\fP'' used so ``\fB-h\fP'' won't be interpreted as an option X.EA X -1279 X.AU X X.SH DIAGNOSTICS X.I calc prints its error messages on the X.B standard output. Normally, when an error occurs X.I calc terminates with an exit status of 1. The exception is when in multiple-expression mode if the X.I e option has X.B not been specified. In this case, X.I calc will simply report the error and move on to the next input. X The error messages are: X.RS .5i X.na X.IP "bad argument to -p option" Option must be followed by an integer argument with no intervening spaces. X.IP "missing expression" An operator of function expected an expression and didn't find one. X.IP "division by zero" Right-hand expression to the division operator evaluated to zero. X.IP "bad argument to modulo" Right-hand expression to the modulo operator was zero, or was greater than or equal to X.if t 2\u\s-231\s0\d. X.if n 2:31. X.IP "illegal expression" An expression couldn't be parsed. X.IP "unmatched open paren" A parenthesized expression was opened, but never closed. X.IP "bad binary digit" An expression preceded by a ``\fBb\fP'' contained a character other than ``1'' and ''0''. X.ad X.RE X X.SH BUGS Ascii format (-@, and @) doesn't do anything to pretty up control characters. For example, "calc -@ 12" may have a disconcerting result. X X X.SH AUTHOR Wayne Mesard, MESARD@BBN.COM END_OF_FILE if test 5961 -ne `wc -c <'calc.man'`; then echo shar: \"'calc.man'\" unpacked with wrong size! fi # end of 'calc.man' fi if test -f 'machine.h' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'machine.h'\" else echo shar: Extracting \"'machine.h'\" \(110 characters\) sed "s/^X//" >'machine.h' <<'END_OF_FILE' X#define Mch_Csz 8 X#define Mch_Ssz 16 X#define Mch_Isz 32 X#define Mch_Lsz 32 X#define Mch_BE 1 X#define Mch_sgc 1 END_OF_FILE if test 110 -ne `wc -c <'machine.h'`; then echo shar: \"'machine.h'\" unpacked with wrong size! fi # end of 'machine.h' fi if test -f 'mch_defines.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'mch_defines.c'\" else echo shar: Extracting \"'mch_defines.c'\" \(2812 characters\) sed "s/^X//" >'mch_defines.c' <<'END_OF_FILE' X/* This program was snagged off the USENET comp.lang.c mailing list 22 July 1987 */ X X/* The contents of the following program are copyright 1987 by John Cowan. It is hereby released to the public domain. X This program emits C #define statements to the standard output describing the machine it is executing on. The following #defines are generated: X Mch_Csz - size of a char, in bits X Mch_Ssz - size of a short int, in bits X Mch_Isz - size of a plain int, in bits X Mch_Lsz - size of a long int, in bits X Mch_BE - defined if the machine is big-endian; that is, if X the most significant byte in a number appears first. X Mch_LE - defined if the machine is little-endian; that is, if X the least significant byte in a number appears first. X Mch_PDP - defined if the machine uses PDP-11 byte ordering; X LE for bytes-in-a-word and BE for words-in-a-long. X Mch_ONE - defined if the machine uses one's complement arithmetic. X Mch_sgc - defined if characters can be signed. X*/ X X#include <stdio.h> X char bittest[9] = "\001\001\001\001\001\001\001\001"; /*Changed from [6] for CRAY X-MP -WM*/ char endtest[6] = "\001\002\003\004\005"; long be = 1; long le = 1; long pdp; int byteoff; int bytesize; long longval; X main() X { X int i; X X byteoff = (*(int *) bittest & 2047) - 1; X switch (byteoff) { X case 256: bytesize = 8; break; X case 512: bytesize = 9; break; X case 1024: bytesize = 10; break; X default: fprintf(stderr, "mch: bogus byte size\n"); exit(1); X } X printf("#define Mch_Csz %d\n", bytesize); X printf("#define Mch_Ssz %d\n", sizeof(short) * bytesize); X printf("#define Mch_Isz %d\n", sizeof(int) * bytesize); X printf("#define Mch_Lsz %d\n", sizeof(long) * bytesize); X longval = *(long *) endtest; X for (i = 0; i < sizeof(long); i++) { X be *= byteoff; X be += endtest[i]; X } X for (i = sizeof(long) - 1; i >= 0; i--) { X le *= byteoff; X le += endtest[i]; X } X pdp = 0x02010403; X if (longval == be) X printf("#define Mch_BE 1\n"); X else if (longval == le) X printf("#define Mch_LE 1\n"); X else if (longval == pdp) X printf("#define Mch_PDP 1\n"); X else { X fprintf(stderr, "mch: bogus endianism\n"); X exit(1); X } X if (~0 == 0) X printf("#define Mch_ONE 1\n"); X if ('\377' < 0) /* modified 1987/07/22 R. Dhesi */ X printf("#define Mch_sgc 1\n"); X } END_OF_FILE if test 2812 -ne `wc -c <'mch_defines.c'`; then echo shar: \"'mch_defines.c'\" unpacked with wrong size! fi # end of 'mch_defines.c' fi echo shar: End of shell archive. exit 0 -- Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.