[comp.sources.unix] v24i045: Reverse polish notation calculator

rsalz@uunet.uu.net (Rich Salz) (03/15/91)

Submitted-by: Arnold Robbins <arnold@audiofax.com>
Posting-number: Volume 24, Issue 45
Archive-name: hp

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	README
#	hp.1
#	Makefile
#	hp.gram.y
#	hp.scan.l
#	hp.h
#	hp.c
# This archive created: Wed Jan 23 17:27:04 1991
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'README'" '(499 characters)'
if test -f 'README'
then
	echo shar: "will not over-write existing file 'README'"
else
cat << \SHAR_EOF > 'README'
Wed Jan 23 17:23:32 EST 1991

The 'hp' program implements a reverse polish notation calculator.
I've had this little program for over five years, and finally decided
to clean it up a little bit and post it.

Enjoy,

Arnold Robbins				AudioFAX, Inc. | Laundry increases
2000 Powers Ferry Road, #200 / Marietta, GA. 30067     | exponentially in the
INTERNET: arnold@audiofax.com Phone:   +1 404 933 7612 | number of children.
UUCP:	  emory!audfax!arnold Fax-box: +1 404 618 4581 |   -- Miriam Robbins
SHAR_EOF
fi
echo shar: "extracting 'hp.1'" '(3480 characters)'
if test -f 'hp.1'
then
	echo shar: "will not over-write existing file 'hp.1'"
else
cat << \SHAR_EOF > 'hp.1'
.if n .ds lq ""
.if n .ds rq ""
.if t .ds lq ``
.if t .ds rq ''
.de QU
\&\\*(lq\\$1\\*(rq\\$2
..
.TH HP 1
.SH NAME
hp \- Reverse Polish Notation calculator
.SH SYNOPSIS
.B hp
[
.I "expression elements"
]
.SH DESCRIPTION
.I Hp
is a desk calculator program using the Reverse Polish Notation
familiar to all stack machine aficionados and users of
Hewlett-Packard calculators.
It accepts expressions composed of operands and operators from
either its argument list or standard input and evaluates them.
.PP
If the expressions to be evaluated are given on the command line,
.I hp
prints the resulting value automatically;
otherwise, the user must request printing with the
.QU "p"
or
.QU "P"
commands.
.PP
An acceptable expression consists of a sequence of
.QU "constants"
and
.QU "commands."
Constants are numeric constants written in the style of FORTRAN,
and are stored internally as double precision floating-point values.
Commands are single characters that request an arithmetic, stack
control, or control flow operation.
The following commands are currently implemented:
.TP
.B p
print the value on the top of the stack.
.TP
.B P
print all the values currently on the stack.
.TP
.B d
delete the top value on the stack (throw it away).
.TP
.B D
empty the stack completely (throw all stacked data away).
.TP
.B +
add top two items on the stack, place sum on the stack.
.TP
.B \-
subtract top of stack from next to top, place difference on the stack.
.TP
.BR * " or " x " or " X
multiply top two items on the stack, place product on the stack.
Since
.QU "*"
is usually expanded by the shell,
.I hp
allows
.QU x
or
.QU X
as synonyms for
.QU * .
(The idea is that
.QU x
is hopefully reminiscent of the grade-school notation used for multiplication.)
.TP
.B /
divide next to top of stack by top of stack, place quotient on the stack.
.TP
.B %
divide next to top of stack by top of stack, place remainder on the stack.
.TP
.BR ^ " or " :
evaluate (next to top of stack) to the (top of stack) power, place
result on the stack.
The
.QU : ,
while not very mnemonic, is not special to the Bourne shell
.RI ( sh (1)),
and so can be used
on the command line.
.TP
.B <
if next to top of stack is less than top of stack, place a 1 on the
stack; otherwise, place a 0 on the stack.
.TP
.B =
if next to top of stack equals top of stack, place a 1 on the stack;
otherwise, place a 0 on the stack.
.TP
.B >
if next to top of stack is greater than top of stack, place a 1 on the
stack; otherwise, place a 0 on the stack.
.TP
.B &
if next to top of stack is nonzero and top of stack is nonzero, place
a 1 on the stack; otherwise, place a 0 on the stack.
.TP
.B |
if next to top of stack is nonzero or top of stack is nonzero,
place a 1 on the stack; otherwise, place a 0 on the stack.
.TP
.B !
if top of stack is nonzero, replace it with a 0; if it is zero,
replace it with a 1 (logical negation).
.TP
.BR q " or " Q
exit.
.I Hp
will also exit when it sees an end-of-file (usually control-D).
.SH EXAMPLES
.nf
hp 32.75 4.5 '*'
hp
1 2 3 4 5 6 7P++++++pd
3.1416 2.7183^ 2.7183 3.1416^>p
.fi
.SH SEE ALSO
.IR eval (1),
.IR lex (1),
.IR yacc (1)
.SH DIAGNOSTICS
.TP
stack overflow
if an attempt is made to push too many items on the stack.
The stack size is currently limited to about 100.
.TP
stack underflow
if an attempt is made to perform an operation
with insufficient operands on the stack.
.TP
<char>: unrecognized command
if a character not corresponding to
any command appears in an expression.
SHAR_EOF
fi
echo shar: "extracting 'Makefile'" '(558 characters)'
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
# makefile for 'hp' --- reverse polish notation calculator

CFLAGS= -O
YFLAGS= -d
DESTDIR=/usr/local/bin
MANSEC=l
CP= cp

OBJS = hp.o hp.gram.o hp.scan.o

hp: $(OBJS)
	$(CC) $(CFLAGS) $(OBJS) -lm -ll -o hp

hp.scan.o : y.tab.h
	lex hp.scan.l
	cc -O -c lex.yy.c
	rm lex.yy.c
	mv lex.yy.o hp.scan.o

hp.gram.o y.tab.h : hp.gram.y

install: hp hp.1
	$(CP) hp $(DESTDIR)
	$(CP) hp.1 /usr/man/man$(MANSEC)/hp.$(MANSEC)

print:
	pr hp.gram.y hp.scan.l hp.h hp.c | lpr
	nroff -man hp.1 | col | lpr

clean:
	rm -f $(OBJS) hp.gram.c y.tab.h

clobber: clean
	rm -f hp
SHAR_EOF
fi
echo shar: "extracting 'hp.gram.y'" '(2209 characters)'
if test -f 'hp.gram.y'
then
	echo shar: "will not over-write existing file 'hp.gram.y'"
else
cat << \SHAR_EOF > 'hp.gram.y'
/* yacc grammar for hp reverse polish calculator */

%{
#include "y.tab.h"

#include "hp.h"

int i;
extern int sound();
extern double ctod(), pow(), fmod();
%}

%start expression
%term DOT DIGIT BIGP LITTLEP PLUS MINUS STAR SLASH PERCENT POW
%term AND OR NOT EQ LT GT LE GE BIGD LITTLED QUIT

%%
expression : constant_or_command_list
	|
	;

constant_or_command_list : constant_or_command_list constant_or_command
	| constant_or_command
	;

constant_or_command : constant
	| command
	;

constant : DOT	{ push(ctod(line, & scanptr)); scanptr--; }
	| DIGIT	{ push(ctod(line, & scanptr)); scanptr--; }
	;

command : LITTLEP	{ if (sound(1)) printf("%lf\n", stack[sp]); }

	| BIGP	{
			for (i = 1; i < sp; i++)
				printf("%lf\n", stack[i]);
		}

	| PLUS	{
			if (sound(2)) {
				stack[sp - 1] += stack[sp];
				sp--;
			}
		}

	| MINUS	{
			if (sound(2)) {
				stack[sp - 1] -= stack[sp];
				sp--;
			}
		}

	| STAR	{
			if (sound(2)) {
				stack[sp - 1] *= stack[sp];
				sp--;
			}
		}

	| SLASH	{
			if (sound(2)) {
				stack[sp - 1] /= stack[sp];
				sp--;
			}
		}

	| PERCENT	{
				if (sound(2)) {
					stack[sp - 1] = fmod(stack[sp - 1], stack[sp]);
					sp--;
				}
			}

	| POW	{
			if (sound(2)) {
				stack[sp - 1] = pow(stack[sp - 1], stack[sp]);
				sp--;
			}
		}

	| LITTLED	{ if (sound(1)) sp--; }

	| BIGD		{ sp = 0; }

	| LT	{
			if (sound(2)) {
				if (stack[sp - 1] < stack[sp])
					stack[sp - 1] = 1.0;
				else
					stack[sp - 1] = 0.0;
				sp--;
			}
		}

	| EQ	{
			if (sound(2)) {
				if (stack[sp - 1] == stack[sp])
					stack[sp - 1] = 1.0;
				else
					stack[sp - 1] = 0;
				sp--;
			}
		}

	| GT	{
			if (sound(2)) {
				if (stack[sp - 1] > stack[sp])
					stack[sp - 1] = 1.0;
				else
					stack[sp - 1] = 0.0;
				sp--;
			}
		}

	| AND	{
			if (sound(2)) {
				if (stack[sp - 1] != 0.0 && stack[sp] != 0.0)
					stack[sp - 1] = 1.0;
				else
					stack[sp - 1] = 0.0;
				sp--;
			}
		}

	| OR	{
			if (sound(2)) {
				if (stack[sp - 1] != 0.0 || stack[sp] != 0.0)
					stack[sp - 1] = 1.0;
				else
					stack[sp - 1] = 0.0;
				sp--;
			}
		}

	| NOT	{
			if (sound(1)) if (stack[sp] != 0.0)
					stack[sp] = 0.0;
				else
					stack[sp] = 1.0;
		}

	| QUIT	{ exit (0); }
	;
SHAR_EOF
fi
echo shar: "extracting 'hp.scan.l'" '(572 characters)'
if test -f 'hp.scan.l'
then
	echo shar: "will not over-write existing file 'hp.scan.l'"
else
cat << \SHAR_EOF > 'hp.scan.l'
%{
/* hp.scan.l --- lex scanner for hp calculator */

#include "y.tab.h"
#undef input
#undef unput
%}

%%
"."	return (DOT);
[0-9]	return (DIGIT);
"P"	return (BIGP);
"p"	return (LITTLEP);
"+"	return (PLUS);
"-"	return (MINUS);
[Xx*]	return (STAR);
"/"	return (SLASH);
"%"	return (PERCENT);
[:^]	return (POW);
"&"	return (AND);
"|"	return (OR);
"!"	return (NOT);
"="	return (EQ);
"<"	return (LT);
">"	return (GT);
"<="	return (LE);
">="	return (GE);
"D"	return (BIGD);
"d"	return (LITTLED);
[Qq]	return (QUIT);
[ \t\n]	;
.	fprintf(stderr, "%s: unknown command.\n", yytext);
SHAR_EOF
fi
echo shar: "extracting 'hp.h'" '(123 characters)'
if test -f 'hp.h'
then
	echo shar: "will not over-write existing file 'hp.h'"
else
cat << \SHAR_EOF > 'hp.h'
/* hp.h --- external declarations for hp */

extern double stack[];
extern int scanptr;
extern int sp;
extern char line[];
SHAR_EOF
fi
echo shar: "extracting 'hp.c'" '(2416 characters)'
if test -f 'hp.c'
then
	echo shar: "will not over-write existing file 'hp.c'"
else
cat << \SHAR_EOF > 'hp.c'
/* hp --- reverse Polish notation calculator program */

#include <stdio.h>
#include <ctype.h>

#define MAXSTACK	100
#define TRUE		1
#define FALSE		0

double stack[MAXSTACK + 1];
int scanptr = -1;
int sp = 0;
char line[BUFSIZ*4];

extern char *strcpy();

main(argc, argv)
int argc;
char **argv;
{
	int i, l;
	int sound();

	if (argc > 1) {
		l = 0;
		/* concatenate args into one line for input() to use... */
		for (i = 1; argv[i] != NULL; i++) {
			(void) strcpy(& line[l], argv[i]);
			l += strlen(argv[i]);
			line[l] = ' ';
			l++;
		}
		line[l] = '\0';
		scanptr = -1;
		yyparse();
		if (sound(1))
			printf("%1.3lf\n", stack[sp]);
	} else
		while (fgets(line, sizeof line, stdin) != NULL) {
			scanptr = -1;
			yyparse();
		}
	
	exit(0);
}

/* ctod --- do atof, but increment pointer into the line buffer */

double ctod(text, indx)
char text[];
int *indx;
{
	double result, atof();
	char buf[BUFSIZ];
	int i = 0;

	while (isdigit(text[*indx])) {
		buf[i++] = text[*indx];
		(*indx)++;
	}

	if (text[*indx] == '.') {
		buf[i++] = '.';
		(*indx)++;
	}
	
	while (isdigit(text[*indx])) {
		buf[i++] = text[*indx];
		(*indx)++;
	}

	if (text[*indx] == 'e' ||  text[*indx] == 'E') {
		buf[i++] = text[*indx];
		(*indx)++;
	
		if (text[*indx] == '-' || text[*indx] == '+') {
			buf[i++] = text[*indx];
			(*indx)++;
		}

		while (isdigit(text[*indx])) {
			buf[i++] = text[*indx];
			(*indx)++;
		}
	}

	buf[i] = '\0';

	result = atof(buf);
	return result;
}

/* fmod --- do floating modulus */

double fmod(x, y)
double x, y;
{
	extern double modf();
	extern double fabs();
	double f;
	int flag = 0;
	double ipart;

	if (y == 0.0)
		return x;

	flag = (x < 0.0);
	x = fabs(x);
	y = fabs(y);
	(void) modf(x / y, & ipart);
	f = x - y * ipart;
	return (flag ? -f : f);
}

/* push --- push one item onto the stack */

push(stuff)
double stuff;
{
	if (sp > MAXSTACK)
		fprintf(stderr, "stack overflow\n");
	else {
		sp++;
		stack[sp] = stuff;
	}
}



/* sound --- sound out the depth of the stack */

int sound(depth)
int depth;
{
	if (sp < depth) {
		fprintf(stderr, "stack underflow\n");
		return FALSE;
	} else
		return TRUE;
}

int input()
{
	register int c;

again:
	scanptr++;
	if ((c = line[scanptr]) == '\0') {
		scanptr = -1;
		return 0;
	} else if (isspace(c))
		goto again;
	else {
		return c;
	}
}

int unput(c)
int c;
{
	line[--scanptr] = c;
}

int yyerror(s)
char *s;
{
	fprintf(stderr, "hp: %s\n", s);
}
SHAR_EOF
fi
exit 0
#	End of shell archive

exit 0 # Just in case...
-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.