[comp.sources.amiga] v89i064: graf - graph multiple functions

page@swan.ulowell.edu (Bob Page) (03/16/89)

Submitted-by: 840493n@ACADIA.BITNET (Bill Nickerson)
Posting-number: Volume 89, Issue 64
Archive-name: applications/graf.1

Graf is a program originally meant for graphing multiple functions of
one variable entered from the keyboard.  A calculator mode was so easy
to add that it is included also.

#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:    Shell Archiver
#	Run the following text with /bin/sh to create:
#	enter.c
#	enter.h
#	eval.c
#	graf.doc
#	grafmain.c
#	graph.c
#	kwords.c
#	kwords.h
#	lex.c
#	lex.h
#	libs.c
#	main.h
#	mmenu.c
#	mmenu.h
#	mytypes.h
#	parse.c
#	readme
#	stack.c
#	stack.h
#	symbol.c
#	symbol.h
# This archive created: Wed Mar 15 15:59:14 1989
cat << \SHAR_EOF > enter.c
/*
	enter.c		-	Bill Nickerson, 1988

	Functions for defining formulas and associated gadgets.

	Export: FreeGads, InitGads, PickFormula, GetFormula
	Static: none
*/

#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include "enter.h"
#include "main.h"

/*
	Imported functions and variables.
*/
extern struct Window *OpenWindow();
extern void CloseWindow();
extern struct IntuiMessage *GetMsg();
extern void Wait();
extern void ReplyMsg();
extern void SetAPen();
extern void DisplayBeep();
extern int ActivateGadget();

extern Formula flist[];
extern struct Gadget *ButtonList;
extern int iswrong;

/*--------------------Parameter input gadget---------------------------------*/

UBYTE InBuf[80];

struct StringInfo StrInfo =
{ InBuf, NULL, 0, 80, 0, 0, 0, 0, 0, 0, NULL, 0L, NULL };

struct Gadget StringG =
{ NULL, 2, 15, 625, 10, SELECTED, 0, STRGADGET,
  NULL, NULL, NULL, NULL, &StrInfo, 0, NULL };

/*--------------------Formula definition gadget------------------------------*/

SHORT ArrowBox[]   = { 0, 0, 0, 10, 10, 10, 10, 0, 0, 0 };
SHORT ArrowUp[]    = { 0, 10, 10, 10, 5, 0, 0, 10 };
SHORT ArrowDown[]  = { 0, 0, 5, 10, 10, 0, 0, 0 };
SHORT ArrowLeft[]  = { 10, 0, 10, 10, 0, 5, 10, 0 };
SHORT ArrowRight[] = { 0, 0, 0, 10, 10, 5, 0, 0 };

struct Border AB   = { 0, 0, 1, 0, JAM1, 5, ArrowBox, NULL };
struct Border AU   = { 0, 0, 1, 0, JAM1, 4, ArrowUp, NULL };
struct Border AD   = { 0, 0, 1, 0, JAM1, 4, ArrowDown, NULL };
struct Border AL   = { 0, 0, 1, 0, JAM1, 4, ArrowLeft, NULL };
struct Border AR   = { 0, 0, 1, 0, JAM1, 4, ArrowRight, NULL };

struct Gadget ABG  = { &StringG, ARROWX, ARROWY, 10, 10, 0, GADGIMMEDIATE,
		       BOOLGADGET, &AB, NULL, NULL, NULL, NULL, ABID, NULL };
struct Gadget AUG  = { &ABG, ARROWX, ARROWY-15, 10, 10, 0, GADGIMMEDIATE,
		       BOOLGADGET, &AU, NULL, NULL, NULL, NULL, AUID, NULL };
struct Gadget ADG  = { &AUG, ARROWX, ARROWY+15, 10, 10, 0, GADGIMMEDIATE,
		       BOOLGADGET, &AD, NULL, NULL, NULL, NULL, ADID, NULL };
struct Gadget ALG  = { &ADG, ARROWX-15, ARROWY, 10, 10, 0, GADGIMMEDIATE,
		       BOOLGADGET, &AL, NULL, NULL, NULL, NULL, ALID, NULL };
struct Gadget ARG  = { &ALG, ARROWX+15, ARROWY, 10, 10, 0, GADGIMMEDIATE,
		       BOOLGADGET, &AR, NULL, NULL, NULL, NULL, ARID, NULL };

/*--------------------Okay/Cancel border gadget------------------------------*/

SHORT BCoords[]	   = { 0, 0, 0, 15, 55, 15, 55, 0, 0, 0 };
struct Border GB   = { 0, 0, 1, 0, JAM1, 5, BCoords, NULL };

/*--------------------Okay gadget--------------------------------------------*/

struct IntuiText OkayT = { 1, 0, JAM1, 11, 4, NULL, "Okay", NULL };

struct Gadget OkayG    = { &ARG, 5, ARROWY+8, 55, 15, 0,
			   GADGIMMEDIATE, BOOLGADGET,
			   &GB, NULL, &OkayT, NULL, NULL, OKAYID, NULL };

/*--------------------Cancel gadget------------------------------------------*/

struct IntuiText CancelT = { 1, 0, JAM1, 4, 4, NULL, "Cancel", NULL };

struct Gadget CancelG	 = { &OkayG, 65, ARROWY+8, 55, 15, 0,
			     GADGIMMEDIATE, BOOLGADGET,
			     &GB, NULL, &CancelT, NULL, NULL, CANCELID, NULL };

/*--------------------Join gadget--------------------------------------------*/

struct IntuiText JoinT   = { 1, 0, JAM1, 11, 4, NULL, "Join", NULL };

struct Gadget JoinG	 = { &CancelG, 35, ARROWY-10, 55, 15, GADGHCOMP,
			     GADGIMMEDIATE | TOGGLESELECT, BOOLGADGET,
			     &GB, NULL, &JoinT, NULL, NULL, JOINID, NULL };

/*--------------------NewWindow for "enter" window---------------------------*/

struct NewWindow SNW = { 5, 10, 630, 75, 0, 1, GADGETDOWN,
			 ACTIVATE, &JoinG, NULL, "Enter", NULL, NULL,
			 0, 0, 0, 0, WBENCHSCREEN };

/*--------------------NewWindow for "select" window--------------------------*/

struct NewWindow SELNW = { 5, 10, 630, 140, 0, 1, GADGETDOWN,
			   ACTIVATE, NULL, NULL, "Select", NULL, NULL,
			   0, 0, 0, 0, WBENCHSCREEN };

SHORT SelXY[]		= { 0, 0, 0, 5, 10, 5, 10, 0, 0, 0 };
struct Border SelB	= { 0, 0, 1, 0, JAM1, 5, SelXY, NULL };
struct Gadget SelG	= { NULL, 5, 0, 10, 5, GADGHCOMP, GADGIMMEDIATE | TOGGLESELECT,
			    BOOLGADGET, &SelB, NULL, NULL, NULL, NULL,
			    0, NULL };
struct Gadget SelOkayG	= { NULL, 5, 120, 55, 15, 0, GADGIMMEDIATE,
			    BOOLGADGET, &GB, NULL, &OkayT, NULL, NULL,
			    OKAYID, NULL };
struct Gadget SelCancelG= { &SelOkayG, 65, 120, 55, 15, 0, GADGIMMEDIATE,
			    BOOLGADGET, &GB, NULL, &CancelT, NULL, NULL,
			    CANCELID, NULL };

/*---------------------------------------------------------------------------*/

struct Gadget *ButtonList;

/*
	Deallocate all memory allocated to button gadget list.
	ButtonList is changed.
*/
void FreeGads()
{
	struct Gadget *tgad;

	while (ButtonList != NULL)
	{
		tgad = ButtonList;
		ButtonList = ButtonList->NextGadget;
		free(tgad);
	}
}

/*
	Make a button gadget list for use when entering/choosing
	formulas.
	ButtonList is changed.
*/
int InitGads()
{
	struct Gadget *tgad;
	int i;

	/*
		Allocate memory for buttons used to choose formulas.
		Stop if no memory is available.
	*/
	for (i = 0; i < MAXFORMULAS; i++)
		if ((tgad = (struct Gadget *)malloc(sizeof(struct Gadget))) == NULL)
		{
			FreeGads();
			return(0);
		}
		else
		{
			*tgad = SelG;
			tgad->TopEdge = 15 + i*10;
			tgad->GadgetID = i;
			tgad->NextGadget = ButtonList;
			ButtonList = tgad;
		}
	return(1);
}

/*
	howmany	- GIMMEONE (just 1 formula), GIMMESOME (>1 formula).

	Choose just one formula or a selection from a list of formulas.
	Returns -1 if CANCEL, or a choice. If GIMMESOME, 0 is returned
	and the chosen formulas have their "selected" flags set.
*/
int PickFormula( howmany )
int howmany;
{
	struct Window *Win;
	struct IntuiMessage *msg;
	struct Gadget *gi;
	ULONG class;
	int i, len, choice = -2;

	/*
		Deselect all gadgets.
	*/
	for (gi = ButtonList; gi != NULL; gi = gi->NextGadget)
		gi->Flags &= ~SELECTED;
	/*
		If we want to choose for than one formula, put "Okay" and
		"Cancel" gadgets on the window.
	*/
	if (howmany == GIMMESOME)
	{
		SelOkayG.NextGadget = ButtonList;
		SELNW.FirstGadget = &SelCancelG;
	}
	else	SELNW.FirstGadget = ButtonList;

	if ((Win = OpenWindow(&SELNW)) == NULL)
		return(-1);

	SetAPen(Win->RPort, 1L);

	/*
		Put all formulas beside their buttons.
	*/
	for (i = 0; i < MAXFORMULAS; i++)
	{
		if ((len = strlen(flist[i].form)) > 70)
			len = 70;
		Move(Win->RPort, 20, 15 + i*10 + Win->RPort->TxBaseline);
		Text(Win->RPort, flist[i].form, len);
	}

	/*
		Keep looping until we have either CANCEL or a choice.
	*/
	while (choice == -2)
	{
		Wait(1<<Win->UserPort->mp_SigBit);
		while (msg = GetMsg(Win->UserPort))
		{
			class = msg->Class;
			ReplyMsg(msg);

			if (class == GADGETDOWN)
			{
				gi = (struct Gadget *)(msg->IAddress);
				switch (gi->GadgetID)
				{
				case OKAYID:
					choice = 0;
					break;
				case CANCELID:
					choice = -1;
					break;
				default:
					if (howmany == GIMMEONE)
						choice = gi->GadgetID;
					else	flist[gi->GadgetID].selected ^= FSELECT;
				}
			}
		}
	}
	CloseWindow(Win);
	return(choice);
}

/*
	w	- window to receive information.
	f	- pointer to structure containing formula string.
	bnds	- pointer to array defining graph bounds.

	Display the given formula and bounds.
	Returns nothing.
*/
void DrawInfo( w, f, bnds )
struct Window *w;
char *f;
double bnds[];
{
	int len;
	char tmp[20];

	Move(w->RPort, 200, 33 + w->RPort->TxBaseline);
	Text(w->RPort, "                                             ", 45);
	if ((len = strlen(f)) > 40)
	{
		len = 40;
		Move(w->RPort, 200 + w->RPort->TxWidth*40, 33 + w->RPort->TxBaseline);
		Text(w->RPort, " ....", 5);
	}
	Move(w->RPort, 200, 33 + w->RPort->TxBaseline);
	Text(w->RPort, f, len);

	sprintf(tmp, "Y lo = %-6.3e", bnds[1]);
	Move(w->RPort, 200, 46 + w->RPort->TxBaseline);
	Text(w->RPort, tmp, strlen(tmp));
	sprintf(tmp, "Y hi = %-6.3e", bnds[0]);
	Move(w->RPort, 200 + w->RPort->TxWidth*20, 46 + w->RPort->TxBaseline);
	Text(w->RPort, tmp, strlen(tmp));
	sprintf(tmp, "X lo = %-6.3e", bnds[2]);
	Move(w->RPort, 200, 59 + w->RPort->TxBaseline);
	Text(w->RPort, tmp, strlen(tmp));
	sprintf(tmp, "X hi = %-6.3e", bnds[3]);
	Move(w->RPort, 200 + w->RPort->TxWidth*20, 59 + w->RPort->TxBaseline);
	Text(w->RPort, tmp, strlen(tmp));
}

/*
	f	- pointer to structure containing formula string.
	bnds	- pointer to array defining graph bounds.

	Allow the given formula or bounds to be changed.
	Returns 1 if anything was changed, 0 if changes were cancelled.
*/
int GetFormula( f, bnds )
Formula *f;
double bnds[];
{
	struct Window *Win;
	struct IntuiMessage *msg;
	struct Gadget *gi;
	ULONG class;
	char tform[80];
	double bnd, tbnd[4];
	int tselected, i;

	strcpy(InBuf, f->form);
	strcpy(tform, f->form);
	tselected = f->selected;
	for (i = 0; i < 4; i++)
		tbnd[i] = bnds[i];

	/*
		Set formula's initial "join dots" state.
	*/
	if (f->selected & FJOIN)
		JoinG.Flags |= SELECTED;
	else	JoinG.Flags &= ~SELECTED;

	if ((Win = OpenWindow(&SNW)) == NULL)
		return(0);

	ActivateGadget(&StringG, Win, NULL);
	SetAPen(Win->RPort, 1L);
	DrawInfo(Win, tform, tbnd);

	while (1)
	{
		Wait(1<<Win->UserPort->mp_SigBit);
		while (msg = GetMsg(Win->UserPort))
		{
			class = msg->Class;
			ReplyMsg(msg);

			if (class == GADGETDOWN)
			{
				gi = (struct Gadget *)(msg->IAddress);
				switch (gi->GadgetID)
				{
				case ABID:
					parse(InBuf, NULL);
					if (!iswrong)
					{
						strcpy(tform, InBuf);
						DrawInfo(Win, tform, tbnd);
					}
					else	DisplayBeep(Win->WScreen);
					break;
				case AUID:
				case ADID:
				case ALID:
				case ARID:
					if (sscanf(InBuf, "%lf", &bnd) == 1)
					{
						tbnd[gi->GadgetID-1] = bnd;
						DrawInfo(Win, tform, tbnd);
					}
					else	DisplayBeep(Win->WScreen);
					break;
				case JOINID:
					tselected ^= FJOIN;
					break;
				case OKAYID:
					strcpy(f->form, tform);
					f->selected = tselected;
					for (i = 0; i < 4; i++)
						bnds[i] = tbnd[i];
					CloseWindow(Win);
					return(1);
				case CANCELID:
					CloseWindow(Win);
					return(0);
				default:
					break;
				}
			}
		}
	}
}

SHAR_EOF
cat << \SHAR_EOF > enter.h
/*
	enter.h		-	Bill Nickerson, 1988

	Defines for gadgets on "enter formula" screen.
*/

#ifndef ENTERH
#define ENTERH


/*
	Bits defining a formula is selected and whether the points
	defined by a formula are to be joined when graphed.
*/
#define FSELECT		1
#define FJOIN		2

#define GIMMEONE	1
#define GIMMESOME	2

#define ARROWX		145
#define ARROWY		45

#define ABID		0
#define AUID		1
#define ADID		2
#define ALID		3
#define ARID		4
#define OKAYID		20
#define CANCELID	21
#define JOINID		22


#endif
SHAR_EOF
cat << \SHAR_EOF > eval.c
/*
	eval.c	-	Bill Nickerson, 1988

	Evaluates a sequence of p-code instructions representing an
	expression as a function of "fnvar".
	If an error has occurred, errno will be a non-zero value, otherwise
	it will be zero.

	Export: evaluate
	Static: none
*/

#include <math.h>
#include "stack.h"
#include "kwords.h"

/*
	Imported functions and variables.
*/
extern void Push(), MakeAStack();
extern anElement Pop();

extern int errno;

/*
	pcode	- array of p-code for interpretation. End delimited by STOPt.
	fnvar	- variable that this p-code is a function of. ie pcode(fnvar)

	If successful, returns result of evaluation and errno will be zero.
	If some sort of arithmetic error occurred (like div by zero), then
	zero is returned and errno will be non-zero.

	NOTE: This function does absolutely no error checking and assumes
	that the p-code array is a valid function.
*/
double evaluate( pcode, fnvar )
double pcode[];
double fnvar;
{
	double a1, a2, tmp;
	int pc = 0;

	errno = 0;
	MakeAStack();

	while ((int)pcode[pc] != STOPt)
	{
		if (errno != 0)
			break;

		switch ((int)pcode[pc++])
		{
		case PLUSt:	Push(Pop() + Pop());
				break;
		case MINUSt:	a1 = Pop();
				Push(Pop() - a1);
				break;
		case TIMESt:	Push(Pop() * Pop());
				break;
		case DIVt:	a1 = Pop();
				Push(Pop() / a1);
				break;
		case MODt:	a1 = Pop();
				Push(fmod(Pop(), a1));
				break;
		case ACOSt:	Push(acos(Pop()));
				break;
		case COSHt:	Push(cosh(Pop()));
				break;
		case COSt:	Push(cos(Pop()));
				break;
		case ASINt:	Push(asin(Pop()));
				break;
		case SINHt:	Push(sinh(Pop()));
				break;
		case SINt:	Push(sin(Pop()));
				break;
		case ATANt:	Push(atan(Pop()));
				break;
		case TANHt:	Push(tanh(Pop()));
				break;
		case TANt:	Push(tan(Pop()));
				break;
		case CEILt:	Push(ceil(Pop()));
				break;
		case FLOORt:	Push(floor(Pop()));
				break;
		case EXPt:	Push(exp(Pop()));
				break;
		case LNt:	Push(log(Pop()));
				break;
		case LOGt:	Push(log10(Pop()));
				break;
		case SQRTt:	Push(sqrt(Pop()));
				break;
		case POWERt:	a1 = Pop();
				Push(pow(Pop(), a1));
				break;
		case ABSt:	Push(fabs(Pop()));
				break;
		case FACTt:	a1 = floor(Pop());
				for (tmp = 1.0; a1 > 1.0; a1 -= 1.0)
					tmp *= a1;
				Push(tmp);
				break;
		case SUMt:	a2 = floor(Pop());
				a1 = floor(Pop());
				if (a1 > a2)
				{
					tmp = a1;
					a1 = a2;
					a2 = tmp;
				}
				for (tmp = 0.0; a1 <= a2; a1 += 1.0)
					tmp += a1;
				Push(tmp);
				break;
		case NEGt:	Push(-1 * Pop());
				break;
		case PUSHt:	Push(pcode[pc++]);
				break;
		case FNVARt:	Push(fnvar);
				break;
		default:	break;
		}
	}

	if (errno == 0)
		tmp = Pop();
	else	tmp = 0.0;

	return(tmp);
}
SHAR_EOF
cat << \SHAR_EOF > graf.doc
Graf	-	Bill Nickerson
==============================

History.
========
Graf is a program originally meant for graphing multiple functions of one
variable entered from the keyboard. A calculator mode was so easy to add
that it is included also.

The inspiration for graf came from the graph sketching classes we had in
intro calculus. I wanted a nice and quick way of checking my work. Also, since
I was marking and teaching a night class for a translators, I wanted a sample
lexical analyzer and expression parser. Besides, I hadn't really done anything
majorly graphic with my Amiga since I got it a couple years ago and this just
happen to be a good excuse.

Execution.
==========
Graf must be run from some sort of cli. If no arguments are given, then you
are popped into graphing mode, indicated by a window appearing in the upper
left hand corner of the screen. If you give it the solitary argument "calc",
then a title line is output and you are in calculator mode, indicated by the
number 1 in square brackets at the left side of the current line. If any
other arguments are given, a usage message is displayed and you get the cli
back.

Functions Available.
====================
Before the operation of the program is explained, a list of functions would
probably be useful. The parser is really quite powerful and does a lot of
error checking for you. Errors that are made during parsing are indicated
and errors that occur during evaluation (like taking the square root of -1)
are either pointed out or ignored, depending on which mode you happen to be
in. Anyhow, a list of functions....

--**-----------
NOTE: arguments to the trig functions must be given in radians.
--**-----------

-x		- negation of x.

acos(x)		- arccos of x.
asin(x)		- arcsin of x.
atan(x)		- arctan of x.
cosh(x)		- hyperbolic cos of x.
sinh(x)		- hyperbolic sin of x.
tanh(x)		- hyperbolic tan of x.
cos(x)		- cos of x.
sin(x)		- sin of x.
tan(x)		- tan of x.

exp(x)		- e to power x.
ln(x)		- natural log of x.
log(x)		- log base 10 of x.

sqrt(x)		- square root of x.
<<(x)		- floor of x.
>>(x)		- ceiling of x.
|(x)		- absolute value of x.
!(x)		- factorial of floor of x.

x * y		- product of x and y.
x / y		- division of x by y.
x % y		- modulus of x by y.
x ^ y		- x to power of y.
x + y		- sum of x and y.
x - y		- difference of x and y.
x : y		- sum of integers between floor of x and y.

Parentheses work the way they usually do, the usual limits on boundaries apply,
and precedence is as usual. For the most part, the functions above are listed
according to precedence. Summation is a bit weird, but examples should suffice.

	1:3+2:5		is (((1:3)+2):5) == ((6+2):5) == (8:5) == 26
	(1:3)+(2:5)	is ((1:3)+(2:5)) == (6 + 14) == 20
	1:3*2:5		is ((1:(3*2)):5) == ((1:6):5) == 21:5 == 221

--**-----------
As a side note, how many have noticed that a few of the IEEE math routines
do not give errors where there should be some? For example, tan of 90 degrees
actually returns a value (defined as HUGE in <math.h>) and no error. This is
probably supposed to stand for infinity, but I'd rather it be an error. If you
get weird values for invalid functions, the math routines could be to blame....
--**-----------

Graphing.
=========

After the little window pops up when you type "graf" from your cli, you can
see three options: "graph", "enter", and "quit". It is obvious which one exits
the program, so I won't dwell on it. The one to start with is "enter". This
option is used to enter up to 10 functions into the system.

Enter.
------
Initially, you get a window titled "Select" with 10 little boxes down the
left side. Graf wants you to select a function to enter (if blank) or change
(if not blank). When you click on one of them, a new window titled "Enter"
appears. This window is more interesting and has a lot of things to play
with. First, an explanation of the different gadgets.

The "okay" and "cancel" gadgets do just what you normally expect.

The "join" gadget toggles everytime you click it (try it if you don't believe
me). It is used for functions that define dots that have gaps between them.
When join is active, the dots are joined together as they are plotted giving a
smooth plot instead of something resembling bird droppings. Try functions with
and without join to see how it works. Each function has its own join flag, so
you can use it at your own descretion.

The funny little arrow gadget is very important. To the right of it are the
words "Y lo" (lower Y axis boundary), "Y hi" (upper Y axis boundary),
"X lo" (left X axis boundary), "X hi" (right X axis boundary), and some
numbers. Each triangle on the arrow gadget controls a graph boundary on both
the X and Y axes. You enter numbers representing boundaries in the string
gadget at the top of the window. Commonsense tells you which triangle controls
what. If you enter an illegal number, the screen will flash and no boundary
is changed. If a legal number is entered, the boundary is updated on the
screen. The square at the middle controls the entry of functions. When you
click on it, whatever is in the string gadget at the top gets checked and,
if valid, entered as a function to be graphed. If the string is invalid, the
screen blinks and nothing is changed. As much of the function as will fit is
displayed in the area just above the graph boundaries.

Right now, the cursor is on a string gadget and is waiting for you to
enter something. Using this, you can enter any function up to 80 characters in
length. At this point, you may be wondering how you can write a function
of x. It is not what you think. The parser will not accept anything but the
functions and symbols given above and one special symbol - "#". It is this
special symbol that represents x. An example of a function and its alternate
representation might be

	F(x) = sin(x) + x	==	sin(#) + #

Try entering a few functions and changing the graph boundaries. When you're
finished, click on "okay".

Graph.
------
The next option to try is "graph". The Select window appears again, but this
time there are "okay" and "cancel" gadgets at the bottom. In this window,
you can select any number of the functions just by clicking on their select
boxes. Each box toggles as you click it so you'll know which ones you've
chosen. Click okay to begin graphing them or cancel to quit.

The evaluator is fairly quick, so you shouldn't have to wait long for your
graph. You will notice speed differences if you have a lot of trancendental
functions, though. After all graphs have been completed, a full screen
crosshair appears and you can move the mouse around to check the approximate
locations of different points on your function. I thought this might be a
useful little feature (ie. approximating "zeroes" of a function). The window
initially in the upper left tracks position. It can be moved, but you'll have
to click in the graphing window again to get your location displayed. To
exit back to the main menu, hit any key on the keyboard. And that's it.

Using the Calculator.
=====================
If you typed "graf calc" at the cli, you now have a "[1]" sitting at the left
of your cursor. This indicates the current line you're about to enter for
evaluation. Here the rules are simple:

- enter a function.
- If the function has "#" anywhere in it, you will enter a loop and will
	be prompted to enter values for "#". The function is evaluated at
	the given value and the result is displayed. To exit the loop,
	enter "1\".
- If the function is valid, you get an answer.
- If the function is invalid, whether because of syntax or the rules of
	math, you get an error.
- to exit, press <CTRL> \ to signal end-of-file.

Looping when a function contains "#" is useful for when you want evaluate one
function for many values. The same operators described above are valid in the
calculator. It's as simple as that.

Questions, Bitching, Etc.
=========================

Contact Bill Nickerson, 840493n@aucs on BitNet. I graduate in May, so don't
bother after then. I may pop up somwhere later on....

--**-----------
This has been a FreeWare Production.
SHAR_EOF
cat << \SHAR_EOF > grafmain.c
/*
	grafmain.c		-	Bill Nickerson, 1988.

	This program allows calculations to be made or formulas to be
	graphed. To enter graphing mode,

		graf

	To enter calculator mode,

		graf calc

	Read "graf.doc" for further information.
*/

#include <stdio.h>
#include "main.h"
#include "mmenu.h"
#include "enter.h"

/*
	Imported functions and variables.
*/
extern int OpenLibs();
extern int InitGads();
extern int PickOption();
extern int GetFormula();
extern int PickFormula();
extern void DrawFormula();
extern void CloseLibs();
extern void FreeGads();
extern void parse();
extern double evaluate();
extern int initkw();

extern int iswrong, errno, fnvarused;

/* Formula and bounds (up, down, left, right) storage. */
Formula flist[MAXFORMULAS];
double bnds[4];

/*
	Perform initization. Make a button list for choosing formulas, open
	libraries, and set up data structures.
	Returns nothing.
*/
void Initialize( mode )
int mode;
{
	int i;

	if (mode == GRAF)
	{
		/*
			Allocate memory for buttons used to choose formulas.
			Stop if no memory is available.
		*/
		if (!InitGads())
		{
			puts("No room for gadgets.\n");
			exit(1);
		}
		if (!OpenLibs())
		{
			puts("Couldn't open libraries.\n");
			exit(1);
		}
		/*
			Initialize formula list to empty strings.
		*/
		for (i = 0; i < MAXFORMULAS; i++)
		{
			*(flist[i].form) = '\0';
			flist[i].selected = 0;
		}
		bnds[0] = bnds[3] = 10.0;
		bnds[1] = bnds[2] = -10.0;
	}
	if (!initkw())
	{
		puts("Couldn't initialize keyword table.\n");
		if (mode == GRAF)
			CloseLibs();
		exit(1);
	}
}

/*
	Perform system cleanup. Deallocate button list memory and close
	libraries.
	Returns nothing.
*/
void CleanUp()
{
	FreeGads();
	CloseLibs();
}

/*
	Select formulas and perform graphing.
	Returns nothing.
*/
void Graph()
{
	int i;

	for (i = 0; i < MAXFORMULAS; i++)
		flist[i].selected &= ~FSELECT;
	if (PickFormula(GIMMESOME) < 0)
		return;
	DrawFormula(flist, bnds, 0.0);
}

/*
	Enter formula and/or bounds for graphing.
	Returns nothing.
*/
void Enter()
{
	int choice;

	choice = PickFormula(GIMMEONE);
	GetFormula(&flist[choice], bnds);
}

/*
	Enter calculator mode and instantly evaluate any formula entered
	from standard input.
	Returns nothing.
*/
void Calculate()
{
	int line = 1;
	char buf[160];
	double pcode[160], fnvar, reslt;

	Initialize(CALC);
	puts("\ngrafCalc v1.0 - (c) Bill Nickerson, 1988\n");
	for (; printf("[%d] ", line), (fgets(buf, 159, stdin) != NULL); line++)
	{
		parse(buf, pcode);
		if (iswrong)
			printf("Line %d is wrong.\n", line);
		else
		{
			do
			{
				if (fnvarused)
				{
					fprintf(stdout, "Evaluate at?\n");
					while (scanf("%lf", &fnvar) != 1)
					{
						while(getchar() != '\n')
							;
						fprintf(stdout, "Uh, uh.\n");
					}
					/*
						Ingest the trailing char.
					*/
					if (getchar() != '\n')
					{
						fnvarused = 0;
						while (getchar() != '\n')
							;
					}
				}
				else	fnvar = 0.0;

				reslt = evaluate(pcode, fnvar);
				if (errno == 0)
					printf("== %lf\n", reslt);
				else	printf("Math error on line %d.\n", line);
			}
			while (fnvarused);
		}
	}
	exit(0);
}

/*
	Initialize data and control selections.
*/
void main( argc, argv )
int argc;
char *argv[];
{
	if ((argc > 2) || ((argc == 2) && (strcmp(argv[1], "calc") != 0)))
	{
		printf("usage: %s [calc]\n", *argv);
		exit(1);
	}

	if (argc == 2)
		Calculate();

	Initialize(GRAF);
	while (1)
	{
		switch (PickOption())
		{
		case GBID:
			Graph();
			break;
		case EBID:
			Enter();
			break;
		case QBID:
			CleanUp();
			exit(0);
			break;
		default:
			break;
		}
	}
}
SHAR_EOF
cat << \SHAR_EOF > graph.c
 /*
	graph.c		-	Bill Nickerson, 1988

	Functions for graphing formulas.

	Export: DrawFormula
	Static: none
*/

#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include "enter.h"
#include "main.h"

/*
	Imported functions and variables.
*/
extern struct IntuiMessage *GetMsg();
extern struct Window *OpenWindow();
extern void CloseWindow(), Wait(), ReplyMsg();
extern void SetAPen(), SetDrMd(), Move(), Draw();
extern void parse();
extern double evaluate();

extern Formula flist[];
extern int errno;

/*--------------------NewWindow for graphing---------------------------------*/

struct NewWindow GRNW = { 0, 0, 640, 200, 0, 1, VANILLAKEY | MOUSEMOVE,
			  BORDERLESS | ACTIVATE | REPORTMOUSE,
			  NULL, NULL, NULL, NULL, NULL,
			  0, 0, 0, 0, WBENCHSCREEN };

/*--------------------NewWindow for coordinates------------------------------*/

struct NewWindow COORDSNW = { 0, 0, 100, 35, 0, 1, 0, WINDOWDRAG,
			      NULL, NULL, NULL, NULL, NULL,
			      0, 0, 0, 0, WBENCHSCREEN };

/*---------------------------------------------------------------------------*/

/*
	f	- pointer to structures containing formula strings.
	b	- pointer to array defining graph bounds.
	res	- resolution to use when graphing. Currently unused.

	Draw a graph of selected formulas on the screen and wait for a
	mouse button to be pressed. If either x or y bounds are equal, no
	graph is drawn.
	Returns nothing.
*/
void DrawFormula( f, b, res )
Formula *f;
double *b, res;
{
	struct Window *Win, *CWin;
	struct IntuiMessage *msg;
	char buf[15];
	double pcode[80], scalex, scaley, dx, incx, reslt;
	int i, xa, ya, x, y, lx, ly, set, done;

	if ((b[0] == b[1]) || (b[2] == b[3]))
		return;

	if ((Win = OpenWindow(&GRNW)) == NULL)
		return;
	if ((CWin = OpenWindow(&COORDSNW)) == NULL)
	{
		CloseWindow(Win);
		return;
	}

	SetAPen(Win->RPort, 1L);

	/*
		Find resolution, draw axes and labels (later).
	*/
	scaley = 199/(b[0]-b[1]);
	scalex = 639/(b[3]-b[2]);
	incx =  (b[3]-b[2])/639;
	ya = (int)(-b[2]*scalex);
	xa = (int)(b[0]*scaley);

	if (b[0]*b[1] <= 0.0) /* There is an X axis here somewhere. */
	{
		Move(Win->RPort, 0, xa);
		Draw(Win->RPort, 639, xa);
	}
	if (b[2]*b[3] <= 0.0) /* There is an Y axis here somewhere. */
	{
		Move(Win->RPort, ya, 0);
		Draw(Win->RPort, ya, 199);
	}

	for (i = 0; i < MAXFORMULAS; i++)
		if (flist[i].selected & FSELECT)
		{
			set = 0;
			parse(flist[i].form, pcode);
			for (dx = b[2]; dx <= b[3]; dx += incx)
			{
				reslt = evaluate(pcode, dx);
				if (errno != 0)
				/*
					If errno isn't zero, then an exception
					occurred and the result isn't valid.
					The next statement makes sure that the
					next coordinate plotted isn't joined
					to the one that caused the exception.
				*/
					set = 0;
				else
				{
					x = (int)((dx-b[2])*scalex);
					y = (int)((b[0]-reslt)*scaley);
					/*
						Make sure we're within screen
						bounds.
					*/
					if ((x >= 0) && (x < 640) &&
						(y >= 0) && (y < 200))
					{
						Move(Win->RPort, x, y);
						if (set && (flist[i].selected & FJOIN))
							Draw(Win->RPort, lx, ly);
						else
						{
							set = 1;
							Draw(Win->RPort, x, y);
						}
					}
					else	set = 0;
				}
				lx = x;
				ly = y;
			}
		}

	/*
		Wait for a mouse button to be pressed. Draw crosshairs and
		track mouse position in the meantime.
	*/
	SetDrMd(Win->RPort, COMPLEMENT);
	set = done = 0;
	while (!done)
	{
		Wait(1<<Win->UserPort->mp_SigBit);
		while (msg = GetMsg(Win->UserPort))
		{
			if (msg->Class == MOUSEMOVE)
			{
				x = msg->MouseX;
				y = msg->MouseY;
			}
			else	done = 1;
			ReplyMsg(msg);
		}
		/*
			Write coordinates to coordinate window.
		*/
		sprintf(buf, "%3.3e", x/scalex+b[2]);
		Move(CWin->RPort, 5, 15 + CWin->RPort->TxBaseline);
		Text(CWin->RPort, buf, strlen(buf));
		sprintf(buf, "%3.3e", -(y/scaley-b[0]));
		Move(CWin->RPort, 5, 25 + CWin->RPort->TxBaseline);
		Text(CWin->RPort, buf, strlen(buf));

		Move(Win->RPort, x, 0);
		Draw(Win->RPort, x, 199);
		Move(Win->RPort, 0, y);
		Draw(Win->RPort, 639, y);
		if (set)
		{
			Move(Win->RPort, lx, 0);
			Draw(Win->RPort, lx, 199);
			Move(Win->RPort, 0, ly);
			Draw(Win->RPort, 639, ly);
		}
		else	set = 1;
		lx = x;
		ly = y;
	}
	CloseWindow(Win);
	CloseWindow(CWin);
}

SHAR_EOF
cat << \SHAR_EOF > kwords.c
/*
	kwords.c	-	Bill Nickerson, 1988

	Implement routines for initializing symbol table with keywords.

	Export: init
	Static: none
*/

#include <stdio.h>
#include "lex.h"
#include "symbol.h"
#include "kwords.h"

/*
	Imported variables.
*/
extern Entry *symtab[];

/* Reserved words of language. Delimited by NULL pointer. */
struct kw
{
	char *reserved;
	int token;
}
reserved_words[] =
{
	"acos",		ACOSt,
	"cosh",		COSHt,
	"cos",		COSt,
	"asin",		ASINt,
	"sinh",		SINHt,
	"sin",		SINt,
	"atan",		ATANt,
	"tanh",		TANHt,
	"tan",		TANt,
	"exp",		EXPt,
	"ln",		LNt,
	"log",		LOGt,
	"sqrt",		SQRTt,
	NULL,		0
};

/*
	Initialize symbol table with reserved words.
	Returns 1 if successful, 0 if failure.
*/
int initkw()
{
	struct kw *tmp = reserved_words;
	int i;

	for (i = 0; i < BUCKETS; i++)
		symtab[i] = NULL;
	for (; tmp->reserved != NULL; tmp++)
		if (insert(tmp->reserved, tmp->token) == NULL)
			return(0);
	return(1);
}
SHAR_EOF
cat << \SHAR_EOF > kwords.h
/*
	kwords.h	-	Bill Nickerson, 1988

	Defines for keywords in language.
*/

#ifndef KWORDSH
#define KWORDSH


/* No particular meaning. */

#define SPECIALt	0


/* Trig functions. */

#define ACOSt		1
#define COSHt		2
#define COSt		3
#define ASINt		4
#define SINHt		5
#define SINt		6
#define ATANt		7
#define TANHt		8
#define TANt		9

/* Numerical manipulation functions. */

#define CEILt		10	/* >> */
#define FLOORt		11	/* << */

/* Other transcendental functions. */

#define EXPt		12
#define LNt		13
#define LOGt		14

#define SQRTt		15
#define SUMt		16	/* : */

/* Standard functions. */

#define PLUSt		17	/* + */
#define MINUSt		18	/* - */
#define TIMESt		19	/* * */
#define DIVt		20	/* / */
#define MODt		21	/* % */

/* Others. */

#define RANGEt		22	/* not currently used. */
#define ABSt		23	/* | */
#define POWERt		24	/* ^ */
#define FACTt		25	/* ! */

#define FNVARt		26	/* # */
#define NUMt		27

#define LPt		28	/* ( */
#define RPt		29	/* ) */
#define NEGt		30	/* - (unary) */
#define PUSHt		31
#define STOPt		32
#define ERRORt		33


#endif
SHAR_EOF
cat << \SHAR_EOF > lex.c
/*
	lex.c	-	Bill Nickerson, 1988

	Implements a lexical analyzer for mathematical formulas.

	Export: lex
	Static: sgetc, sungetc, sgetnum
*/

#include <stdio.h>
#include <ctype.h>
#include "lex.h"
#include "symbol.h"
#include "kwords.h"

/*
	Imported functions.
*/
extern Entry *lookup();

/* Lexeme of identifier and number value of number. */
char id[MAXIDLEN+1];
double num;

/* Position of identifier in symbol table. */
Entry *place;

/* Pointer within current input string. */
char *inptr;

/*
	The next three function mimic getc, ungetc, and scanf for strings.
	They all manipulate the global input pointer, inptr.
*/
int sgetc()
{
	if (*inptr == '\0')
		return(EOF);
	return(*inptr++);
}

void sungetc()
{
	inptr--;
}

void sgetnum( num )
double *num;
{
	char numbuf[80], *ptr = numbuf;

	while (isdigit(*inptr))
		*ptr++ = *inptr++;
	if (*inptr == '.')
		do
		{
			*ptr++ = *inptr++;
		}
		while (isdigit(*inptr));
	*ptr = '\0';
	sscanf(numbuf, "%lf", num);
}

/*
	Returns a token representing the element gathered from the input
	stream.
*/
int lex()
{
	int c, d, i;

	while (1)
	{
		c = sgetc();
		/*
			End of input.
		*/
		if (c == EOF)
			return(EOF);
		/*
			Whitespace removal.
		*/
		if (c == ' ' || c == '\t' || c == '\n')
			continue;
		/*
			Gather identifiers. This lexer will not accept any
			identifiers that aren't reserved keywords.
			'#', below, represents the only allowed user id.
		*/
		if (isalpha(c))
		{
			for (i = 0; (i < MAXIDLEN) && isalnum(c); i++)
			{
				id[i] = c;
				c = sgetc();
			}
			id[i] = '\0';

			if (c != EOF)
				sungetc();
			if ((place = lookup(id)) == NULL)
				return(ERRORt);
			return(place->token);
		}
		/*
			Gather float numbers.
		*/
		if (isdigit(c))
		{
			sungetc();
			sgetnum(&num);
			return(NUMt);
		}
		/*
			Handle special symbols. '#' represents the only
			allowable user variable.
		*/
		switch (c)
		{
		case '+':	return(PLUSt);
		case '-':	return(MINUSt);
		case '*':	return(TIMESt);
		case '/':	return(DIVt);
		case '%':	return(MODt);
		case '^':	return(POWERt);
		case '#':	return(FNVARt);
		case '!':	return(FACTt);
		case '|':	return(ABSt);
		case '(':	return(LPt);
		case ')':	return(RPt);
		case ':':	return(SUMt);
		case '<':
		case '>':	d = sgetc();
				if (d != c)
				{
					if (d != EOF)
						sungetc();
					return(c);
				}
				switch (c)
				{
				case '<':	return(FLOORt);
				case '>':	return(CEILt);
				}
		default:	return(ERRORt);
		}
	}
}
SHAR_EOF
cat << \SHAR_EOF > lex.h
/*
	lex.h	-	Bill Nickerson, 1988

	Define for lexical analysis stage.
*/

#ifndef LEXH
#define LEXH


#define MAXIDLEN	80


#endif
SHAR_EOF
cat << \SHAR_EOF > libs.c
/*
	libs.c	-	Bill Nickersoin, 1988

	Functions for opening and closing libraries.

	Export: OpenLibs, CloseLibs
	Static: none
*/

#define Rev	0L

#include <intuition/intuition.h>
#include "mytypes.h"

/*
	Imported functions and variables.
*/
extern void CloseLibrary();
extern long OpenLibrary();

extern long IntuitionBase;
extern long GfxBase;
long LayersBase;

/*
	Close any libraries if they happen to be open.
	Returns nothing.
*/
void CloseLibs()
{
	if (LayersBase != NULL)
		CloseLibrary(LayersBase);
	if (GfxBase != NULL)
		CloseLibrary(GfxBase);
	if (IntuitionBase != NULL)
		CloseLibrary(IntuitionBase);
}

/*
	Open necessary libraries.
	Returns TRUE if all could be opened or FALSE if an error occurred.
*/
boolean OpenLibs()
{
	IntuitionBase = GfxBase = LayersBase = NULL;

	if ((IntuitionBase = OpenLibrary("intuition.library", Rev)) == NULL)
		return(FALSE);

	if ((GfxBase = OpenLibrary("graphics.library", Rev)) == NULL)
	{
		CloseLibs();
		return(FALSE);
	}

	if ((LayersBase = OpenLibrary("layers.library", Rev)) == NULL)
	{
		CloseLibs();
		return(FALSE);
	}

	return(TRUE);
}
SHAR_EOF
cat << \SHAR_EOF > main.h
/*
	main.h

	Types and such for formulas.
*/

#ifndef MAINH
#define MAINH


#define CALC		0
#define GRAF		1

#define MAXFORMULAS	10

typedef struct aformula
{
	char form[80];
	int selected;

} Formula;


#endif
SHAR_EOF
cat << \SHAR_EOF > mmenu.c
/*
	mmenu.c		-	Bill Nickerson, 1988

	Functions for choosing main menu options.

	Export: PickOption
	Static: MakeMMenu
*/

#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include "mmenu.h"

/*
	Functions defined elsewhere.
*/
extern struct Window *OpenWindow();
extern void CloseWindow();
extern struct IntuiMessage *GetMsg();
extern void Wait();
extern void ReplyMsg();
extern void SetAPen();

/*----------------------------------------------------------------------
	Graph button bit image and Intuition structure.
*/

USHORT GraphD[GBDY*3] = 
{
	0x7FFF, 0xFFFF, 0xC000,
	0x8000, 0x0000, 0x2000,
	0x8000, 0x0000, 0x2000,
	0x8E78, 0xE791, 0x2000,
	0x9145, 0x1451, 0x2000,
	0x9045, 0x1451, 0x2000,
	0x9779, 0xF79F, 0x2000,
	0x9151, 0x1411, 0x2000,
	0x9149, 0x1411, 0x2000,
	0x8E45, 0x1411, 0x2000,
	0x8000, 0x0000, 0x2000,
	0x8000, 0x0000, 0x2000,
	0x7FFF, 0xFFFF, 0xC000
};

struct Image GraphI =
{
	0, 0,			/* Left top. */
	GBDX, GBDY,		/* Width, height. */
	1,			/* Depth. */
	GraphD,			/* Address of data. */
	0x01, 0x00,		/* PlanPick, PlaneOnOff. */
	NULL			/* Next image. */
};

struct Gadget GraphG =
{
	NULL,			/* Next gadget, set by open function. */
	13, 13,			/* LeftEdge, TopEdge. */
	GBDX, GBDY,		/* Width, Height. */
	GADGIMAGE,		/* Flags. */
	GADGIMMEDIATE,		/* Activation flags. */
	BOOLGADGET,		/* GadgetType. */
	&GraphI,		/* GadgetRender, set by open function. */
	NULL,			/* SelectRender. */
	NULL,			/* GadgetText. */
	NULL,			/* MutualExclude. */
	NULL,			/* SpecialInfo. */
	GBID,			/* GadgetID. */
	NULL			/* UserData. */
};

/*----------------------------------------------------------------------
	Enter button bit image and Intuition structure.
*/

USHORT EnterD[EBDY*3] =
{
	0x7FFF, 0xFFFF, 0xC000,
	0x8000, 0x0000, 0x2000,
	0x8000, 0x0000, 0x2000,
	0x9F45, 0xF7DE, 0x2000,
	0x9064, 0x4411, 0x2000,
	0x9064, 0x4411, 0x2000,
	0x9E54, 0x479E, 0x2000,
	0x904C, 0x4414, 0x2000,
	0x904C, 0x4412, 0x2000,
	0x9F44, 0x47D1, 0x2000,
	0x8000, 0x0000, 0x2000,
	0x8000, 0x0000, 0x2000,
	0x7FFF, 0xFFFF, 0xC000
};

struct Image EnterI =
{
	0, 0,
	EBDX, EBDY,
	1,
	EnterD,
	0x01, 0x00,
 	NULL
};

struct Gadget EnterG =
{
	&GraphG,
	13, 28,
	EBDX, EBDY,
	GADGIMAGE,
	GADGIMMEDIATE,
	BOOLGADGET,
	&EnterI,
	NULL,
	NULL,
	NULL,
	NULL,
	EBID,
	NULL
};

/*----------------------------------------------------------------------
	Quit button bit image and Intuition structure.
*/

USHORT QuitD[QBDY*2] =
{
	0x7FFF, 0xFFF0,
	0x8000, 0x0008,
	0x8000, 0x0008,
	0x8E45, 0xF7C8,
	0x9144, 0x4108,
	0x9144, 0x4108,
	0x9144, 0x4108,
	0x9544, 0x4108,
	0x9244, 0x4108,
	0x8D39, 0xF108,
	0x8000, 0x0008,
	0x8000, 0x0008,
	0x7FFF, 0xFFF0
};

struct Image QuitI =
{
	0, 0,
	QBDX, QBDY,
	1,
	QuitD,
	0x01, 0x00,
 	NULL
};

struct Gadget QuitG =
{
	&EnterG,
	15, 43,
	QBDX, QBDY,
	GADGIMAGE,
	GADGIMMEDIATE,
	BOOLGADGET,
	&QuitI,
	NULL,
	NULL,
	NULL,
	NULL,
	QBID,
	NULL
};

/*----------------------------------------------------------------------*/

struct NewWindow NW =
{
/* LeftEdge */ 		20,
/* TopEdge */		20,
/* Width */		60,
/* Height */		60,
/* DetailPen */		0,
/* BlockPen */		1,
/* IDCMPFlags */	GADGETDOWN,
/* Flags */		ACTIVATE | WINDOWDRAG | WINDOWDEPTH,
/* FirstGadget */	&QuitG,
/* CheckMark */		NULL,
/* Title */		NULL,
/* Screen */		NULL,
/* BitMap */		NULL,
/* MinWidth */		0,
/* MinHeight */		0,
/* MaxWidth */		0,
/* MaxHeight */		0,
/* Type */		WBENCHSCREEN
};

int MakeMMenu()
{
	int i;
	USHORT *GD, *ED, *QD;

	/*
		Allocate chip memory for gadget image data.
	*/
	if ((GD = (USHORT *)AllocMem(GBDY*3*sizeof(USHORT), MEMF_CHIP)) == NULL)
		return(0);
	if ((ED = (USHORT *)AllocMem(EBDY*3*sizeof(USHORT), MEMF_CHIP)) == NULL)
		return(0);
	if ((QD = (USHORT *)AllocMem(QBDY*2*sizeof(USHORT), MEMF_CHIP)) == NULL)
		return(0);

	for (i = 0; i < GBDY*3; i++)
		GD[i] = GraphD[i];
	for (i = 0; i < EBDY*3; i++)
		ED[i] = EnterD[i];
	for (i = 0; i < QBDY*2; i++)
		QD[i] = QuitD[i];

	GraphI.ImageData = GD;
	EnterI.ImageData = ED;
	QuitI.ImageData = QD;

	return(1);
}

/*------------------------------------------------------------------------*/

int PickOption()
{
	struct Window *Win;
	struct IntuiMessage *msg;
	struct Gadget *gi;
	ULONG class;
	int choice;

	if (!MakeMMenu())
		return(0);

	if ((Win = OpenWindow(&NW)) == NULL)
		return(0);

	SetAPen(Win->RPort, 1L);

	Wait(1<<Win->UserPort->mp_SigBit);
	while (msg = GetMsg(Win->UserPort))
	{
		class = msg->Class;
		ReplyMsg(msg);
		if (class == GADGETDOWN)
		{
			gi = (struct Gadget *)(msg->IAddress);
			choice = gi->GadgetID;
		}
	}

	CloseWindow(Win);

	FreeMem(GraphI.ImageData, GBDY*3*sizeof(USHORT));
	FreeMem(EnterI.ImageData, EBDY*3*sizeof(USHORT));
	FreeMem(QuitI.ImageData, QBDY*2*sizeof(USHORT));

	return(choice);
}

SHAR_EOF
cat << \SHAR_EOF > mmenu.h
/*
	mmenu.h		-	Bill Nickerson, 1988

	Defines for main menu options.
*/

#ifndef MMENUH
#define MMENUH


#define GBDX	35
#define GBDY	13
#define GBID	1

#define EBDX	35
#define EBDY	13
#define EBID	2

#define QBDX	29
#define QBDY	13
#define QBID	3


#endif
SHAR_EOF
cat << \SHAR_EOF > mytypes.h
/*
	mytypes.h

	Definition of some commonly used constants.
*/

#ifndef MYTYPESH
#define MYTYPESH


#ifndef NULL
#define NULL	0
#endif

#ifndef TRUE
#define TRUE	1
#endif

#ifndef FALSE
#define FALSE	0
#endif

typedef int boolean;


#endif
SHAR_EOF
cat << \SHAR_EOF > parse.c
/*
	parse.c	-	Bill Nickerson, 1988

	Implements routines to produce code for formula evaluation.

	Export: parse
	Static: emit, match, expr, term, exponent, factor
*/

#include <stdio.h>
#include "kwords.h"

/*
	Vars imported from lexer.
*/
extern int lex();
extern char *inptr;
extern double num;

/*
	Forward declarations.
*/
extern void emit(), expr(), term(), exponent(), factor();

/* Set to 1 when formula is not correct, 0 otherwise. "fnvarudes" indicates
   whether or not a user-variable ("#") was encountered.
*/
int iswrong;
int fnvarused;

/* Pointer to p-code array when emitting code. */
double *codeptr;

/* Next token for parser. */
int lookahead;

/*
	formula	- string containing formula to evaluate.
	code	- array to store emitted p-code in or NULL.

	Parse the given formula and put p-code in code array. If pcode is NULL
	then no code is produced and only parsing is done.
	If parsing is successful, iswrong will be 0, else it will be 1.
	Returns nothing.
*/
void parse( formula, code )
char *formula;
double code[];
{
	inptr = formula;
	codeptr = code;
	iswrong = 0;
	fnvarused = 0;
	lookahead = lex();

	expr();
	emit((double)STOPt);

	if (lookahead != EOF)
		iswrong = 1;
}

/*---------------------------------------------------------------------------
	Parsing routines.
*/
void emit( tok )
double tok;
{
	if (codeptr != NULL)
		*codeptr++ = tok;
}

void match( tok )
int tok;
{
	if (lookahead == tok)
		lookahead = lex();
	else	iswrong = 1;
}

void expr()
{
	int tok;

	term();
	while ((tok = lookahead) == PLUSt || tok == MINUSt || tok == SUMt)
	{
		match(tok);
		term();
		emit((double)tok);
	}
}

void term()
{
	int tok;

	exponent();
	while ((tok = lookahead) == TIMESt || tok == DIVt || tok == MODt)
	{
		match(tok);
		exponent();
		emit((double)tok);
	}
}

void exponent()
{
	int tok, exponents = 0;

	/*
		Exponent are right associative, so we have to gather them
		all up and emit them later.
	*/
	factor();
	while ((tok = lookahead) == POWERt)
	{
		exponents++;
		match(tok);
		factor();
	}
	while (exponents--)
		emit((double)POWERt);
}

void factor()
{
	int tok, minuses;

	switch (tok = lookahead)
	{
	/*
		Monadic operators.
	*/
	case ACOSt:
	case COSHt:
	case COSt:
	case ASINt:
	case SINHt:
	case SINt:
	case ATANt:
	case TANHt:
	case TANt:
	case EXPt:
	case LNt:
	case LOGt:
	case CEILt:
	case FLOORt:
	case ABSt:
	case FACTt:	
	case SQRTt:	match(tok);
	/*
		Bracketed expressions.
	*/
	case LPt:	match(LPt);
			expr();
			match(RPt);
			if (tok != LPt)
				emit((double)tok);
			break;
	/*
		Only one id per function. A special code is emitted for it.
	*/
	case FNVARt:	match(tok);
			emit((double)FNVARt);
			fnvarused = 1;
			break;
	/*
		Push constant numbers.
	*/
	case NUMt:	match(tok);
			emit((double)PUSHt);
			emit(num);
			break;
	/*
		Unary minus. Emit a negation instruction only if there is
		an odd number of minus signs in front to reduce code.
	*/
	case MINUSt:	for (minuses = 0; lookahead == MINUSt; minuses++)
				match(MINUSt);
			factor();
			if (minuses & 1)
				emit((double)NEGt);
			break;
	/*
		Error detected.
	*/
	default:	iswrong = 1;
			break;
	}
}
SHAR_EOF
cat << \SHAR_EOF > readme
Compilation Notes
-----------------

This program was compiled with Lattice C version 4.01
on a 512K Amiga 1000 with 2 drives.

When compiling, ignore any warnings that have to do with
"pointers to not point to object of same type" because
they are given when automatic initialization of structures
occurs and are harmless (in this case, anyhow).

-- Bill Nickerson, 840493n@Acadia
SHAR_EOF
cat << \SHAR_EOF > stack.c
/*
	stack.c		-	Bill Nickerson, 1988.

	Functions to implement an array-based stack.

	Export: IsEmpty, Push, Pop, MakeAStack
	Static: none
*/

#include "stack.h"

anElement stack[STACKSIZE];
int sp;

/*
	Check if stack is empty.
	Return 1 if so, 0 if not.
*/
int IsEmpty()
{
	return(sp == 0);
}

/*
	e	- element to put on stack.

	Push element e on stack.
	Returns nothing.
*/
void Push( e )
anElement e;
{
	if (sp < STACKSIZE)
		stack[sp++] = e;
}

/*
	Pop top element from stack.
	Returns the popped data item or 0.0.
*/
anElement Pop()
{
	if (sp > 0)
		return(stack[--sp]);
	else	return(0.0);
}

/*
	Make a new stack.
	Returns nothing.
*/
void MakeAStack()
{
	sp = 0;
}
SHAR_EOF
cat << \SHAR_EOF > stack.h
/*
	stack.h		-	Bill Nickerson, 1988

	Type definitions for stacks.
*/

#ifndef STACKH
#define STACKH


#define STACKSIZE	50

typedef double anElement;


#endif
SHAR_EOF
cat << \SHAR_EOF > symbol.c
/*
	symbol.c	-	Bill Nickerson, 1988

	Routines to insert and lookup ids in a symbol table.

	Export: lookup, insert
	Static: hash
*/

#include <stdio.h>
#include <string.h>
#include "lex.h"
#include "kwords.h"
#include "symbol.h"

/* Symbol table. */
Entry *symtab[BUCKETS];

/*
	str	- string to find hash value for.

	Find hash value of str.
	Returns hash value of string.
*/
int hash( str )
char *str;
{
	int val = 0;

	for (; *str; str++)
		val = (val + *str) % BUCKETS;
	return(val);
}

/*
	id	- pointer to string to lookup in table.

	Find a given string in the symbol table.
	Returns pointer to symbol table location or NULL if no entry.
*/
Entry *lookup( id )
char *id;
{
	Entry *tmp;

	for (tmp = symtab[hash(id)]; tmp != NULL; tmp = tmp->next)
		if (strcmp(id, tmp->id) == 0)
			break;
	return(tmp);
}

/*
	id	- pointer to string to insert into table.
	tok	- token for id.

	Insert a string in the symbol table if it isn't already there.
	Returns pointer to symbol table location or NULL if no memory.
*/
Entry *insert( id, tok )
char *id;
int tok;
{
	Entry *tmp;
	int pos;

	if ((tmp = lookup(id)) != NULL)
		return(tmp);
	if ((tmp = (Entry *)malloc(sizeof(Entry))) == NULL)
		return(NULL);

	tmp->value = 0.0;
	tmp->used = 0;
	strcpy(tmp->id, id);
	tmp->token = tok;

	pos = hash(id);
	tmp->next = symtab[pos];
	symtab[pos] = tmp;

	return(tmp);
}
SHAR_EOF
cat << \SHAR_EOF > symbol.h
/*
	symbol.h	-	Bill Nickerson, 1988

	Defines for symbol table functions.
*/

#ifndef SYMBOLH
#define SYMBOLH


#define BUCKETS		40

typedef struct en
{
	int token;
	char id[MAXIDLEN+1];
	double value;
	int used;
	struct en *next;

} Entry;


#endif
SHAR_EOF
#	End of shell archive
exit 0
-- 
Bob Page, U of Lowell CS Dept.  page@swan.ulowell.edu  ulowell!page
Have five nice days.