[comp.sources.amiga] v89i036: blk - requester layout utility

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

Submitted-by: well!shf (Stuart H. Ferguson)
Posting-number: Volume 89, Issue 36
Archive-name: intuition/blk.1

"Blk" is a requester-generation tool to make requesters easy.  It
converts a funky requester source format into 'C' declarations for
including in user code.

#	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:
#	Makefile
#	ReadMe
#	blk.c
#	blk.doc
#	colors.r
#	lex.c
#	lex.h
#	listmac.h
#	macros.r
#	pro.c
#	std.h
# This archive created: Tue Mar  7 23:06:09 1989
cat << \SHAR_EOF > Makefile
# Makefile for 'blk' utility using lex package.
# Manx 3.6

blk : blk.o lex.o
	ln blk.o lex.o -lc

blk.o lex.o : lex.h


# Makefile for 'pro' demo program.  Makes 'blk' if required.

pro : pro.o
	ln pro.o -lc

pro.o : prototype.h

prototype.h : colors.r blk
	blk colors.r prototype.h
SHAR_EOF
cat << \SHAR_EOF > ReadMe
IntuiTools: blk

"Blk" is a little requester-generation tool I created to make requesters
easy.  It converts a funky requester source format into 'C' declarations
for including in user code.  Its operation is documented in blk.doc, and
an example requester is included that takes you from requester source
(colors.r) to inclusion into user code (pro.c).

Have fun and happy hacking.

	Stuart Ferguson		1/89
	(shf@well.UUCP)

	123 James Ave.
	Redwood City, Ca.
		94062


P.S.  The lexical analysis stuff is kind of a hack -- it just proved
useful for this application.  I disclaim it completely.
SHAR_EOF
cat << \SHAR_EOF > blk.c
/*
 * 	blk	- Automatic Requester formatter and generator.
 *
 * Reads a requester description and formats graphically then generates the
 * appropriate C-code to specify the requester.  See the examples and the
 * documentaion for using the program.
 *
 * Process flow:  Box structure read by recursive-descent.  Boxes formatted
 * and layed-out, also recursively.  Borders generated and optimized into a
 * minimum set of Border structs.  Working preview displayed for fiddling
 * with.  Output declarations written to file.
 *
 * Problems:  In a nutshell -- error recovery.  It doesn't do too well if
 * it runs out of memory, especially in the Border optimization code.
 * Also, the user error messages on parse errors could be much better --
 * like giving line number or a sample of the code at that point -- something
 * that would give a clue as to what went wrong.  Other than that, it's
 * pretty good.
 *
 * Differences from version 1:  Version 1 was distributed on a Fish disk
 * and was a real hack.  This version supports a 'C'-like preprocessor 
 * with #include's and #define's and macros with arguments.  This makes
 * the Requester source files look much nicer since the underlying 
 * grammar is so unreadable.  
 *
 * Disclaimer: This is a tool I hacked up for my own use in creating requesters
 * for Modeler 3D.  It works for me, but I make no claim as to the robustness
 * or other quality of the code.  It's not mouse-driven, but it's such a
 * useful tool that it's worth learning anyway.  Besides, you can put it in
 * your makefile's and have it work just like any other compiler.
 *
 * I'm making this available as a service to Amiga developers.  You are
 * encouraged to enhance or modify as you need to make it more useful for
 * your own purposes.  If you make any changes that make this a better
 * general-purpose tool, let me know about them.
 *
 *	Stuart Ferguson		1/89
 *	(shf@well.UUCP)
 */

#include <stdio.h>
#include <functions.h>
#include <exec/types.h>
#include <intuition/intuition.h>

/* STD.H is my collection of useful macros.
 */
#include "std.h"

/* LEX.H is the lexical analysis defines.
 */
#include "lex.h"


#define abs(x)          ((x)<0 ? -(x) : (x))

/* Size of font for text boxes (fixed width assumed).
 */
#define FONTWIDTH	8
#define FONTHEIGHT	8

/* Box types
 */
#define HBOX	1
#define VBOX	2
#define FILL	3
#define BLOK	4
#define TEXT	5
#define HRULE	6
#define VRULE	7

/*
 * Extended Gadget structure to hold an optional special gadget ID name
 * and the parent box for this gadget.
 */
struct SuperGadget {
	struct Gadget	g;
	struct Box	*box;
	char		*gnam;
};

/*
 * A requester is constructed from a tree of Boxes.  Boxes are arranged in a
 * binary tree, one subtree is what is inside the Box (sub), the other is the
 * remaining Boxes at the same level (next). 
 */
typedef struct Box {
	struct SuperGadget	*gad;	/* gadget - if any */
	struct Box     *next, *sub;	/* binary tree links */
	short           type,		/* box type - possibilities above */
	                col;	/* color - for borders and text */
	short           xs, ys,	/* box size (x,y) */
	                x, y,	/* box position (x,y) */
	                nfil;	/* number of filers inside this box */
	char           *val;		/* string for TEXT boxes */
};


/* GLOBAL */

int		infoLevel = 1,	/* degree of verbosity (0=quiet, 
				 * 1=normal, 2=verbose)
				 */
		printBoxes = 0,	/* printout flag (false normally) */
		showPreview;	/* preview flag (depends on whether there's
				 * an output file and on the '-d' flag.
				 */
char           *globStr = "static ";	/* structures normally static */

FILE           *file;		/* output file */
char           *base;		/* base name */
short           def_bcol = 1, 	/* default border and */
		def_tcol = 1;	/* text colors */

/* String pointer ID's returned from the lexer.
 */
char           *fill_pstr, *tbox_pstr, *hbox_pstr, *vbox_pstr,
	       *blok_pstr, *s_pstr, *p_pstr, *pv_pstr, *ph_pstr;

/* The Requester structures, the lists of parts and the guy itself.
 */
struct Border    *blst = NULL;	/* list header for border structs */
struct IntuiText *itlst = NULL;	/* list header for text structs */
struct Gadget    *glst = NULL;	/* list header for gadgets */

#define LATER	0

struct Requester mreq = {
	NULL,5,5,LATER,LATER,0,0,LATER,LATER,LATER,
	0,0,NULL,{NULL},NULL,NULL,{NULL}
};

/*
 * Generic templates for creating Intuition structures.
 */
struct Border
	generic_border = {0, 0, LATER, 0, JAM1, LATER, LATER, LATER};
struct Gadget
	generic_gadget = {
		LATER, LATER, LATER, LATER, LATER,
		GADGHCOMP,RELVERIFY,BOOLGADGET|REQGADGET,
		LATER, NULL, LATER, 0, LATER, LATER, NULL
	};
struct IntuiText
	generic_itext = {LATER, 0, JAM2, LATER, LATER, LATER, LATER, LATER};
struct StringInfo
	generic_sinfo = {LATER, NULL, 0, LATER, 0};
struct PropInfo
	generic_pinfo = {AUTOKNOB|PROPBORDERLESS,0x8000,0x8000,0x8000,0x8000};

/* Two macros to extract GadgetType info from a Gadget structure.
 */
#define GADGETTYPEBITS	(~GADGETTYPE)
#define GTYPE(g)	(((g)->GadgetType)&GADGETTYPEBITS)

/*
 * The preview window.
 */
struct NewWindow nwin = {
	0, 0, 300, 150, -1, -1,
	CLOSEWINDOW | REQCLEAR | MOUSEMOVE | GADGETDOWN | GADGETUP | VANILLAKEY,
	WINDOWDEPTH | WINDOWDRAG,
	NULL, NULL, (UBYTE *) "Preview", NULL,
	NULL, 0, 0, 0, 0, WBENCHSCREEN
};

struct IntuitionBase *IntuitionBase;


struct Box * ReadBoxList ();
short Layin ();
char * IMClass ();

/* Lexer interface functions.
 */
char * FindString ();
short NextToken ();


/*
 * Returns a new, initialized Box struct.
 */
struct Box * NewBox (type)
	short           type;
{
	struct Box     *b;

	if (!(b = NEW (struct Box))) {
		MemError ();
		return NULL;
	}
	b->type = type;
	b->nfil = 0;
	b->gad = NULL;
	b->val = NULL;
	b->next = b->sub = NULL;
	return b;
}


/*
 * Recursively frees Box tree.
 */
FreeBox (box)
	struct Box *box;
{
	register struct Gadget	*g;
	register struct StringInfo *si;

	if (!box) return;

	FreeBox (box->sub);
	FreeBox (box->next);

	if (g = (struct Gadget *) box->gad) {
		if (GTYPE(g) == STRGADGET) {
			si = (struct StringInfo *) g->SpecialInfo;
			FREE_X (si, struct StringInfo, si->MaxChars);
		} else if (GTYPE(g) == PROPGADGET) {
			FREE (g->SpecialInfo, struct PropInfo);
			FREE (g->GadgetRender, struct Image);
		}
		FREE (g, struct SuperGadget);
	}
	FREI (box);
}


/*
 * Recursively examine all nodes of Box tree and allocate Border structs for
 * all the HRULE and VRULE boxes.  Adds new Borders to the main list. 
 * Returns 0 for failure, 1 for sucess.
 */
int CreateBorder (box)
	register struct Box    *box;
{
	register struct Border *bd;

	if (!box) return 1;

	if (box->type == HRULE || box->type == VRULE) {
		if (!(bd = NEW (struct Border))) {
			MemError ();
			FreeBorder ();
			return 0;
		}
		*bd = generic_border;
		bd->FrontPen = box->col;
		bd->Count = 2;
		if (!(bd->XY = NEW_N (SHORT, 4))) {
			MemError ();
			FREI (bd);
			FreeBorder ();
			return 0;
		}
		bd->XY[0] = bd->XY[2] = box->x;
		bd->XY[1] = bd->XY[3] = box->y;
		if (box->type == HRULE) bd->XY[2] += box->xs - 1;
		  else bd->XY[3] += box->ys - 1;

		bd->NextBorder = blst;
		blst = bd;
	}
	if (!CreateBorder (box->sub)) return 0;
	return (CreateBorder (box->next));
}


/*
 * Frees all Border structs in main Border list.
 */
FreeBorder ()
{
	register struct Border *b, *nxt;

	for (b = blst; b; b = nxt) {
		nxt = b->NextBorder;
		FREE_N (b->XY, SHORT, b->Count * 2);
		FREI (b);
	}
	blst = NULL;
}


/*
 * Recursively examine all nodes of Box tree and allocate IntuiText structs
 * for TEXT boxes that are not string gadgets.  Adds new Borders to the main
 * list.  Returns 1 for sucess, 0 for failure.
 */
int CreateIText (box)
	register struct Box     *box;
{
	struct IntuiText	*it;

	if (!box) return 1;

	/*
	 * "box->val" may have been zero-ed by a string gadget grabbing
	 * that text.  If so, this is not an IntuiText.
	 */
	if (box->type == TEXT && box->val) {
		if (!(it = NEW(struct IntuiText))) {
			MemError ();
			FreeIText ();
			return 0;
		}
		*it = generic_itext;
		it->IText = (UBYTE *) box->val;
		it->LeftEdge = box->x;
		it->TopEdge = box->y;
		it->FrontPen = box->col;
		it->NextText = itlst;
		itlst = it;
	}
	if (!CreateIText (box->sub)) return 0;
	return (CreateIText (box->next));
}


/*
 * Frees all IntuiText structs in the main list.  (No need to free the
 * text itself since that is managed by the lexer.)
 */
FreeIText ()
{
	register struct IntuiText *it, *nxt;

	for (it = itlst; it; it = nxt) {
		nxt = it->NextText;
		FREI (it);
	}
	itlst = NULL;
}


/*
 * First pass at merging redundant Borders:  Examines all the Borders in
 * the list for adjacency.  Any borders that could use the same set of
 * polyline commands are merged into a single struct. 
 */
MergeBorders ()
{
	register struct Border *a, *b;
	short           i0, i1, x, y, *xy, j;
	register short  i, ac, bc, merge;

	do {
		merge = -1;
		/*
		 * Examine all pairs of borders, "a" and "b", that
		 * are drawn with the same color, seaching for a pair
		 * that can be merged.  When loop exits with merge=-1,
		 * all pairs have been merged.
		 */
		for (a = blst; a; a = a->NextBorder) {
			for (b = a->NextBorder; b; b = b->NextBorder) {
				if (a->FrontPen != b->FrontPen) continue;

				/*
				 * Examine the 4 pairs of endpoints of each
				 * polyline to see if any are adjacent to
				 * each other.  If any are found, the pairs
				 * located are encoded into "merge" and
				 * the search loop exits.
				 */
				ac = a->Count;
				bc = b->Count;
				for (i0 = 0; i0 < 2; i0++)
					for (i1 = 0; i1 < 2; i1++) {
						x = a->XY[i0*2 * (ac - 1)]
						  - b->XY[i1*2 * (bc - 1)];
						y = a->XY[i0*2 * (ac - 1) + 1]
						  - b->XY[i1*2 * (bc - 1) + 1];
						if (abs (x) + abs (y) == 1)
							merge = (i0 << 1) + i1;
					}
				if (merge != -1)
					break;
			}
			if (merge != -1)
				break;
		}
		if (merge == -1) continue;

		/*
		 * Merging: Create a new polyline data array and move
		 * the two parent polylines into the new one, possibly
		 * reversing one or both in the process.
		 * -- HELP ME:	Is there a nice way out if this
		 *		allocation fails...?
		 */
		xy = NEW_N (SHORT, (bc + ac) * 2);
		x = ((merge & 2) == 0);		/* x = reverse "a" */
		y = ((merge & 1) == 1);		/* y = reverse "b" */
		j = 0;
		for (i = 0; i < ac; i++) {
			i0 = (x ? ac - 1 - i : i) * 2;
			xy[j++] = a->XY[i0];
			xy[j++] = a->XY[i0 + 1];
		}
		for (i = 0; i < bc; i++) {
			i0 = (y ? bc - 1 - i : i) * 2;
			xy[j++] = b->XY[i0];
			xy[j++] = b->XY[i0 + 1];
		}

		/*
		 * Set "a" to have the new polyline data array.
		 */
		a->Count = j / 2;
		FREE_N (a->XY, SHORT, ac * 2);
		a->XY = xy;

		/*
		 * Find "b's" predecessor and remove "b" from list.
		 */
		for (a = blst; a && a->NextBorder != b; a = a->NextBorder);
		a->NextBorder = b->NextBorder;
		FREE_N (b->XY, SHORT, bc * 2);
		FREE (b, struct Border);

	} while (merge != -1);
}


/*
 * Second pass of Border merging: Eliminates linear segments from all
 * Borders XY lists.  The first pass will create lots of redundant points
 * along linear line segments.  This pass will compress those out.
 */
MergeLinear ()
{
	register struct Border *b;
	register short  i0, i1, i2, k, *xy;

	/*
	 * Examine all borders with more than 1 line segment.
	 */
	for (b = blst; b; b = b->NextBorder) {
		if (b->Count < 3) continue;

		/*
		 * Scan along the polyline list and compress out linear
		 * segments by skiping over them.
		 */
		xy = b->XY;
		i0 = 0;
		i1 = 1;
		i2 = 2;
		k = 2;
		while (i2 < b->Count) {
			/*
			 * Skip past linear segments. (I.e. find the bend.)
			 */
			while (i2 < b->Count &&
			       (xy[i0 * 2] == xy[i1 * 2]
				 && xy[i1 * 2] == xy[i2 * 2] ||
				xy[i0 * 2 + 1] == xy[i1 * 2 + 1]
				 && xy[i1 * 2 + 1] == xy[i2 * 2 + 1])) {
				i1++;
				i2++;
			}
			if (i2 >= b->Count) continue;

			/*
			 * Move polyline data to itself after skipping.
			 */
			xy[k++] = xy[i1 * 2];
			xy[k++] = xy[i1 * 2 + 1];
			i0 = i1;
			i1 = i2;
			i2 = i1 + 1;
		}
		xy[k++] = xy[i1 * 2];
		xy[k++] = xy[i1 * 2 + 1];

		k /= 2;
		if (k == b->Count) continue;

		/*
		 * If this border has gotten shorter, allocate a new
		 * array and transfer the new polyline data.
		 */
		xy = NEW_N (SHORT, k * 2);
		for (i0 = 0; i0 < k * 2; i0++) xy[i0] = b->XY[i0];
		FREE_N (b->XY, SHORT, b->Count * 2);
		b->XY = xy;
		b->Count = k;
	}
}


/*
 * Set the XSize and YSize fields for this box and all below.
 */
Format (box)
	struct Box     *box;
{
	struct Box     *b;
	short           mx, my, sx, sy, nf;

	ASSERT (box);

	/*
	 * Deal with the basis (leaf) cases.
	 */
	switch (box->type) {
	
	    /* Blok and text nodes have fixed, already computed size.
	     */
	    case BLOK:
	    case TEXT:
		return;

	    /* Fill node has no intrinsic size.
	     */
	    case FILL:
		box->xs = box->ys = 0;
		box->nfil = 1;
		return;

	    /* H and VRULES have no intrinsic X or Y size, respectively.
	     */
	    case HRULE:
		box->xs = 0;
		return;
	    case VRULE:
		box->ys = 0;
		return;
	}

	/*
	 * H and VBOXes are the recursive case.  First format each
	 * internal box.
	 */
	for (b = box->sub; b; b = b->next) Format (b);

	/*
	 * Compute total and max sizes in each direction. Total (sx,sy) is sum
	 * of all sub-boxes, max (mx,my) is max of sub-boxes. Also inherit
	 * filler count.
	 */
	my = mx = sx = sy = nf = 0;
	for (b = box->sub; b; b = b->next) {
		sx += b->xs;
		sy += b->ys;
		if (b->type == box->type || b->type == FILL) nf += b->nfil;
		if (b->xs > mx) mx = b->xs;
		if (b->ys > my)	my = b->ys;
	}
	box->nfil = nf;

	/*
	 * For horizontal boxes, bounding box is sum in x and max in y; for
	 * vertical, bouding box is max in x and sum in y.  This is the
	 * minimum size of the containing box for the given subboxes.  It
	 * may still expand due to fillers.
	 */
	if (box->type == HBOX) {
		box->xs = sx;
		box->ys = my;
	} else if (box->type == VBOX) {
		box->xs = mx;
		box->ys = sy;
	}
}


/*
 * Compute the position of the boxes internal to this box given that this
 * box has correct location.  The box size computed by Format() is a minimum
 * size, "mx" and "my" are the max that the box can be expanded by filler.
 */
Layout (box, mx, my)
	struct Box     *box;
	short           mx, my;
{
	struct Box     *b;
	short           ish, z, nfil;
	long            gap, ifil;

	ASSERT (box);

	/*
	 * Rules fill out to their max possible size.
	 */
	if (box->type == HRULE) box->xs = mx;
	 else if (box->type == VRULE) box->ys = my;

	/*
	 * Process only HBOX and VBOX cases recursively.  Any other case (a
	 * basis case) has its position set correctly (see assumptions at
	 * head of function).
	 */
	if (box->type != HBOX && box->type != VBOX) return;

	/* Get important values.  Set the "is-hbox" (ish) flag.  Get the
	 * current X size for HBOXes or the Y size for VBOXes as "z".
	 * "gap" is the differece between the max size and minimum size 
	 * given by the Format(), and is how much fillers can expand.
	 */
	ish = (box->type == HBOX);
	z = (ish ? box->x : box->y);
	gap = (ish ? mx - box->xs : my - box->ys);

	/*
	 * Set positions by setting filler sizes.
	 */
	ifil = 0;
	Layin (box, &ifil, ish, z, box->nfil, gap);

	/* Update box size.  If it had fillers, it got as big as
	 * it could.
	 */
	if (box->nfil) {
		if (ish) box->xs = mx;
		    else box->ys = my;
	}
}


/*
 * Layout internal boxes.  Having this as a recursive function deals with
 * the case of VBOXes within VBOXes and HBOXes within HBOXes.
 *
 * NOTE: I'd comment this function, but I can't figure it out.  It seems to
 * figure out the horizonal position of each box and update it as it goes
 * along.  It also calls itself when there are nested same-class boxes.
 * Oh well.  There's probably a better way to do it anyway.
 */
short Layin (box, ifil, ish, z, nfil, gap)
	struct Box     *box;
	short          *ifil, ish, z, nfil;
	long            gap;
{
	struct Box     *b;
	short           t;

	for (b = box->sub; b; b = b->next) {
		if (ish) {
			b->x = z;
			b->y = box->y;
		} else {
			b->x = box->x;
			b->y = z;
		}

		if (b->type == FILL) {
			t = (gap * (*ifil + 1)) / nfil - (gap ** ifil) / nfil;
			(*ifil)++;
			if (ish) b->xs = t;
			    else b->ys = t;

		} else if ((ish && b->type == HBOX)
		       || (!ish && b->type == VBOX)) {
			if (ish) b->ys = box->ys;
			    else b->xs = box->xs;
			t = Layin (b, ifil, ish, z, nfil, gap) - z;
			if (ish) b->xs = t;
			    else b->ys = t;

		} else Layout (b, box->xs, box->ys);

		z += (ish ? b->xs : b->ys);
	}
	return z;
}


/*
 * Use the computed position of the boxes to set the position of
 * the associated gadgets.
 */
PositionGadgets ()
{
	struct Box	*b;
	struct Gadget	*g;

	for (g = glst; g; g = g->NextGadget) {
		b = ((struct SuperGadget *) g)->box;
		g->LeftEdge = b->x;
		g->TopEdge = b->y;
		g->Width = b->xs;
		g->Height = b->ys;
	}
}


/*
 * Returns pointer to string containing box type name for printout.
 */
char * BoxType (typ)
	short           typ;
{
	switch (typ) {
	    case HBOX:	return ("HBOX");
	    case VBOX:	return ("VBOX");
	    case BLOK:	return ("BLOK");
	    case TEXT:	return ("TEXT");
	    case FILL:	return ("FILL");
	    case HRULE:	return ("HRULE");
	    case VRULE:	return ("VRULE");
	}
}


/*
 * Recursively prints this box and all its contents.
 */
PrintBox (box, lev)
	struct Box     *box;
	short           lev;
{
	int             i;

	if (!box) return;

	for (i = 0; i < lev; i++) printf ("  ");

	printf ("%s (%d,%d) %dx%d", BoxType (box->type),
		box->x, box->y, box->xs, box->ys);
	if (box->type == TEXT)	printf (" <%s>", box->val);
	if (box->gad) 		printf (" [gadget]");
	printf ("\n");

	PrintBox (box->sub, lev + 1);
	PrintBox (box->next, lev);
}



/*
 * ==== INPUT SECTION ====
 *
 * File input uses the "lex" front-end for macro processing.  Main entry
 * points for this package are the NextToken() and Backspace() functions.
 * NextToken() returns the code for the next lexical item in the input 
 * stream and sets a buffer pointer to point to its value.  Backspace()
 * resets the lex package to re-read the last token read, so that the
 * file is effectively backspaced one token.  FindString() is also used
 * to get the unique identifer pointer for a string from the hash table.
 */


/*
 * Read a number if there is one.  Otherwise return false and don't
 * change n's value.
 */
BOOL Qnum (n, radix)
	short          *n, radix;
{
	short           i = 0, tok;
	char           *buf;

	tok = NextToken (&buf);
	if (tok != RT_NUM) {
		Backspace ();
		return 0;
	}
	for (; *buf >= '0' && *buf <= '9'; buf++) {
		i = i * radix + (*buf - '0');
	}
	*n = i;
	return 1;
}


/*
 * Reads a double-quoted string like
 *	"stuff"
 * from the file.  Returns pointer to the string contents. 
 */
char * ReadString ()
{
	short           tok;
	char           *buf;

	tok = NextToken (&buf);
	if (tok != RT_STR) {
		fprintf (stderr, "String not found.\n");
		Backspace ();
		return NULL;
	}
	return buf;
}


/*
 * Read gadget ID of the form 
 *	:number
 * if there is one.  Read as hex.  If there is one, create a new
 * SuperGadget structure and add to the main gadget list.
 */
struct SuperGadget * ReadOptGadget (box)
	struct Box *box;
{
	struct SuperGadget *sg;
	short           tok, id;
	char           *buf;

	tok = NextToken (&buf);
	if (tok != RT_CHR || *buf != ':') {
		Backspace ();
		return NULL;
	}
	if (!Qnum (&id, 16)) {
		fprintf (stderr, "Error reading gadget ID number\n");
		return NULL;
	}

	if (!(sg = NEW (struct SuperGadget))) {
		MemError ();
		return NULL;
	}
	sg->g = generic_gadget;
	sg->gnam = NULL;
	sg->box = box;
	sg->g.GadgetID = id;
	sg->g.NextGadget = glst;
	glst = (struct Gadget *) sg;
	return sg;
}


/*
 * Get a box from the open file.  Boxes are either single tokens ("f"
 * for FILL box, "-" and "|" for ordinary rules) or is a
 * composite of the form "("type data")".  Type can be "h" for HBOX,
 * "v" for VBOX, "b" for BLOK, "t" for TEXT, or "-" and "|" again for 
 * special rules.
 *
 * If there isn't a box here, ReadBox() returns NULL with the lexical
 * stream positioned back to read whatever was really there.
 */
struct Box * ReadBox ()
{
	short           tok, i;
	char           *buf, c;
	struct Box     *b;

	tok = NextToken (&buf);

	if (tok == RT_ID && buf == fill_pstr) return NewBox (FILL);

	if (tok != RT_CHR) {
		Backspace ();
		return NULL;
	}

	c = *buf;
	if (c == '-') {
		if (!(b = NewBox (HRULE))) return NULL;
		b->ys = 1;
		b->col = def_bcol;
		return b;
	}
	if (c == '|') {
		if (!(b = NewBox (VRULE))) return NULL;
		b->xs = 1;
		b->col = def_bcol;
		return b;
	}
	if (c != '(') {
		Backspace ();
		return NULL;
	}

	/*
	 * Decode the value inside the '('.
	 */
	tok = NextToken (&buf);
	c = *buf;
	if (tok == RT_ID)
		if (buf == hbox_pstr) {
			if (!(b = NewBox (HBOX))) return NULL;
			b->sub = ReadBoxList ();
		} else if (buf == vbox_pstr) {
			if (!(b = NewBox (VBOX))) return NULL;
			b->sub = ReadBoxList ();
		} else if (buf == tbox_pstr) {
			if (!(b = NewBox (TEXT))) return NULL;
			b->col = def_tcol;
			Qnum (&b->col, 10);
			if (!(b->val = ReadString ())) {
				FreeBox (b);
				return NULL;
			}
			b->xs = strlen (b->val) * FONTWIDTH;
			b->ys = FONTHEIGHT;
		} else if (buf == blok_pstr) {
			if (!(b = NewBox (BLOK))) return NULL;
			i = Qnum (&b->xs, 10);
			i &= Qnum (&b->ys, 10);
			if (!i) {
				fprintf (stderr, "Block needs X and Y sizes\n");
				return NULL;
			}
		} else {
			fprintf (stderr, "Unrecognized box type <%s>\n", buf);
			return NULL;
		}
	else if (tok == RT_CHR)
		switch (c) {
		    case '-':
			if (!(b = NewBox (HRULE))) return NULL;
			if (!Qnum (&b->ys, 10)) {
				fprintf (stderr, "Bad hrule structure.\n");
				return NULL;
			}
			b->col = def_bcol;
			Qnum (&b->col, 10);
			break;
		    case '|':
			if (!(b = NewBox (VRULE))) return NULL;
			if (!Qnum (&b->xs, 10)) {
				fprintf (stderr, "Bad vrule structure\n");
				return NULL;
			}
			b->col = def_bcol;
			Qnum (&b->col, 10);
			break;
		    default:
			fprintf (stderr, "Unrecognized box type <%c>\n", c);
			return NULL;
		}
	else {
		fprintf (stderr, "Unrecognized box type <%s>\n", buf);
		return NULL;
	}
	/*
	 * Pick up the closing ')'.
	 */
	tok = NextToken (&buf);
	if (tok != RT_CHR || *buf != ')') {
		fprintf (stderr, "Parse error - expected ')' !\n");
		FreeBox (b);
		return NULL;
	}

	/*
	 * Read the optional Gadget for this box (as ":id").
	 */
	b->gad = ReadOptGadget (b);
	return b;
}


/*
 * Read a list of boxes from the file stream.  Recursive: read a box,
 * then read a list. 
 */
struct Box * ReadBoxList ()
{
	struct Box      *b;

	b = ReadBox ();
	if (!b) return NULL;

	b->next = ReadBoxList ();
	return b;
}


/*
 * Create a new StringInfo struct and initialize to point to the 
 * given string buffer.  Allocates space for the buffer along with
 * the info struct itself (NEW_X).  Removes trailing spaces.
 */
APTR NewStrInfo (buf)
	char *buf;
{
	struct StringInfo	*si;
	short			i;
	char			*str;

	i = strlen (buf) + 1;
	if (!(si = NEW_X (struct StringInfo, i))) {
		MemError ();
		return NULL;
	}
	*si = generic_sinfo;
	si->Buffer = (UBYTE *) (str = (char *) (si+1));
	si->MaxChars = i;
	strcpy (str, buf);
	for (i -= 2; i>=0 && str[i] == ' '; i--) str[i] = 0;
	return (APTR) si;
}


/* Create new PropInfo struct.  Set the free motion flag based on the
 * id for this gadget "pv" = vert prop, "ph" = horiz prop, "p" = h+v prop.
 */
APTR NewPropInfo (id)
	char *id;
{
	register struct PropInfo	*pi;

	if (!(pi = NEW (struct PropInfo))) {
		MemError ();
		return NULL;
	}
	*pi = generic_pinfo;
	if (id == p_pstr || id == pv_pstr) pi->Flags |= FREEVERT;
	if (id == p_pstr || id == ph_pstr) pi->Flags |= FREEHORIZ;
	return (APTR) pi;
}


/*
 * Reads the list of gadget info from the end of the file.  Reads as much
 * as there is.  Format is:
 *	number {s|p|pv|ph} {:string} string
 * stuff in {}'s is optional.  Each entry gives extra info for the numbered
 * gadget.  {s|p} is string or prop flag.  {:string} is the optional named
 * value rather than just the nubmer.  The last string is the gadget flags.
 * Each set of info gets added to the corresponding gadget structure in
 * the main list.
 */
ReadGadInfo ()
{
	struct Gadget  *g;
	struct Box     *box;
	short           tok;
	char           *buf, c, *actf;
	short           i;
	USHORT		flag;

	while (Qnum (&i, 16)) {
		/*
		 * Locate the gadget in question and it's associated box.
		 */
		for (g = glst; g; g = g->NextGadget)
			if (g->GadgetID == i) break;
		if (!g) {
			fprintf (stderr, "Unknown gadget ID: %x\n", i);
			continue;
		}
		box = ((struct SuperGadget *) g)->box;

		/* Get the optional string or prop flag.
		 */
		tok = NextToken (&buf);
		if (tok == RT_ID) {
			if (buf == s_pstr) {
				g->GadgetType &= ~GADGETTYPEBITS;
				g->GadgetType |= STRGADGET;
				if (!(g->SpecialInfo = NewStrInfo (box->val)))
					return;
				box->val = NULL;
			} else if (buf == p_pstr
			    || buf == ph_pstr
			    || buf == pv_pstr) {
				g->GadgetType &= ~GADGETTYPEBITS;
				g->GadgetType |= PROPGADGET;
				if (!(g->SpecialInfo = NewPropInfo (buf)))
					return;
				if (!(g->GadgetRender =
				    (APTR) NEW (struct Image))) {
					MemError ();
					FREE (g->SpecialInfo, struct PropInfo);
					return;
				}
			} else {
				fprintf (stderr,
				    "Expected \"s\" or \"p\": <%s>\n", buf);
				break;
			}
			tok = NextToken (&buf);
		}

		/* Get optional gadget ID name string.
		 */
		if (tok == RT_CHR && *buf == ':') {
			((struct SuperGadget *) g)->gnam = ReadString ();
			tok = NextToken (&buf);
		}
		Backspace ();

		/* Get and process required activation flags string.
		 */
		actf = ReadString ();
		g->Activation &= ~RELVERIFY;
		for (; *actf; actf++) {
			switch (*actf) {
			    case 'B':
				g->Flags &= ~GADGHIGHBITS;
				g->Flags |= GADGHBOX;
				flag = 0;
				break;
			    case 't':
				flag = TOGGLESELECT;
				break;
			    case 'v':
				flag = RELVERIFY;
				break;
			    case 'e':
				flag = ENDGADGET;
				break;
			    case 'i':
				flag = GADGIMMEDIATE;
				break;
			    case 'c':
				flag = STRINGCENTER;
				break;
			    case 'f':
				flag = FOLLOWMOUSE;
				break;
			}
			g->Activation |= flag;
		}
	}
}


/*
 * Get values for the identifier strings from the lexical analyzer.
 * The lexer will return the same pointer for any identifier which
 * matches.
 */
AssignStrings ()
{
	fill_pstr = FindString ("f");
	hbox_pstr = FindString ("h");
	vbox_pstr = FindString ("v");
	tbox_pstr = FindString ("t");
	blok_pstr = FindString ("b");
	s_pstr = FindString ("s");
	p_pstr = FindString ("p");
	ph_pstr = FindString ("ph");
	pv_pstr = FindString ("pv");
}


/*
 * To read file: open, read base name, read optional default border and text
 * colors, read a box (a BIG box), read gadget info blocks, close.
 */
struct Box * ReadFile ()
{
	struct Box     *box;
	short           i, tok;
	char	       *buf;

	AssignStrings ();
	tok = NextToken (&base);
	if (tok != RT_ID) {
		fprintf (stderr, "Cannot find base name\n");
		return NULL;
	}
	Qnum (&def_bcol, 10);
	Qnum (&def_tcol, 10);

	if (infoLevel > 1) printf ("base name: \"%s\"\ndefault border color:"
		" %d\ndefault text color: %d\n", base, def_bcol, def_tcol);

	box = ReadBox ();
	ReadGadInfo ();

	/*
	 * Make sure we're at the end of the file to make the
	 * lexer happy.  Print up to 10 error messages unless there
	 * is no box from the previous call in which case there's something
	 * wrong anyway.  (Unless in verbose mode, then show 'em all.)
	 */
	i = ((box || infoLevel > 1) ? 10 : 0);
	while (NextToken (&buf) != RT_EOF) {
		if (i) {
			fprintf (stderr,
			    "Token found after end of data: <%s>\n", buf);
			if (!--i) fprintf (stderr, "... etc.\n");
		}
	}

	return box;
}


/*
 * ====  OUTPUT SECTION  ====
 *
 * Dumps structures created during the input and resolution phases of
 * the processing.  Just takes a pointer to a Requester in WriteRequester()
 * and dumps the related structures as well.
 */

/*
 * Write string info and buffer declarations from string gadgets
 * (if any).
 */
WriteStrGad (glist)
	struct Gadget		*glist;
{
	struct Gadget		*g;
	struct StringInfo	*si;
	int			i, n;

	/* Count number of string gadgets.
	 */
	for (n = 0, g = glist; g; g = g->NextGadget)
		if (GTYPE(g) == STRGADGET) n++;

	if (!n) return;

	/* Write the necessary buffers for the string infos.
	 */
	fprintf (file, "\n%sUBYTE %s_nbuf[%d][NUMCHR] = {\n\t",
		globStr, base, n);
	i = n;
	for (g = glist; g; g = g->NextGadget) {
		if (GTYPE(g) != STRGADGET) continue;

		si = (struct StringInfo *) g->SpecialInfo;
		fprintf (file, " \"%s\"", si->Buffer);
		if (--i) fprintf (file, ",");
	}

	fprintf (file, "\n};\n\n%sstruct StringInfo %s_sinfo[] = {\n",
		globStr, base);
	i = 0;
	for (g = glist; g; g = g->NextGadget) {
		if (GTYPE(g) != STRGADGET) continue;

		si = (struct StringInfo *) g->SpecialInfo;
		fprintf (file, "\t{&%s_nbuf[%d][0],undo,0,NUMCHR,0}",
			base, i++);
		if (--n) fprintf (file, ",");
		fprintf (file, "\n");
	}
	fprintf (file, "};\n");

	if (infoLevel > 1) printf ("wrote %d StringInfo structs\n", i);
}


/*
 * Write prop info and image declarations for prop gadgets (if any).
 */
WritePropGad (glist)
	struct Gadget		*glist;
{
	struct Gadget		*g;
	struct PropInfo		*pi;
	int			i, n;

	/* Count number of prop gadgets.
	 */
	for (n = 0, g = glist; g; g = g->NextGadget)
		if (GTYPE(g) == PROPGADGET) n++;

	if (!n) return;

	/* Write the necessary images for the autoknobs.
	 */
	fprintf (file, "\n%sstruct Image %s_pimg[%d];\n", globStr, base, n);

	/* Write the PropInfo structures themselves.
	 */
	fprintf (file, "\n%sstruct PropInfo %s_pinfo[] = {\n", globStr, base);
	i = n;
	for (g = glist; g; g = g->NextGadget) {
		if (GTYPE(g) != PROPGADGET) continue;

		pi = (struct PropInfo *) g->SpecialInfo;
		fprintf (file, "\t{%u,%u,%u,%u,%u}", pi->Flags,
			pi->HorizPot, pi->VertPot,
			pi->HorizBody, pi->VertBody);
		if (--i) fprintf (file, ",");
		fprintf (file, "\n");
	}
	fprintf (file, "};\n");

	if (infoLevel > 1) printf ("wrote %d PropInfo structs\n", n);
}


/*
 * Write the gadgets from the main gadget list.  Returns number of
 * gadgets written.
 */
int WriteGadgets (glist)
	struct Gadget	*glist;
{
	struct Gadget	*g;
	int		k = 1, nimg=0, nprp=0, nstr=0;
	char		*nam;

	if (!glist) return 0;

	WriteStrGad (glist);
	WritePropGad (glist);

	fprintf (file, "\n%sstruct Gadget %s_gad[] = {\n", globStr, base);

	for (g = glist; g; g = g->NextGadget) {
		if (g->NextGadget)
			fprintf (file, "\t{&%s_gad[%d]", base, k++);
		else
			fprintf (file, "\t{NULL");

		fprintf (file, ",%d,%d,%d,%d,%u,%u,%u,", g->LeftEdge,
			g->TopEdge, g->Width, g->Height, g->Flags,
			g->Activation, g->GadgetType);

		if (GTYPE(g) == PROPGADGET)
			fprintf (file, "(APTR)&%s_pimg[%d]", base, nimg++);
		else
			fprintf (file, "NULL");

		fprintf (file, ",\n\t NULL,NULL,0,(APTR)");

		if (GTYPE(g) == PROPGADGET)
			fprintf (file, "&%s_pinfo[%d]", base, nprp++);
		else if (GTYPE(g) == STRGADGET)
			fprintf (file, "&%s_sinfo[%d]", base, nstr++);
		else
			fprintf (file, "NULL");

		if (nam = ((struct SuperGadget *) g)->gnam)
			fprintf (file, ",%s", nam);
		else
			fprintf (file, ",0x%x", g->GadgetID);

		if (g->NextGadget)
			fprintf (file, "},\n");
		else
			fprintf (file, "}\n");
	}
	fprintf (file, "};\n");

	if (infoLevel > 1) printf ("wrote %d Gadget structs\n", k);
	return k;
}


/*
 * Write out list of IntuiText structs for main list.  Returns number
 * of structures written.
 */
int WriteText (tlist)
	struct IntuiText	*tlist;
{
	struct IntuiText	*it;
	int			k = 1;

	if (!tlist) return 0;

	fprintf (file, "\n%sstruct IntuiText %s_txt[] = {\n", globStr, base);

	for (it = tlist; it; it = it->NextText) {
		fprintf (file, "\t{%d,%d,%d,%d,%d,&ta,(UBYTE*)\"%s\",",
			it->FrontPen, it->BackPen, it->DrawMode,
			it->LeftEdge, it->TopEdge, it->IText);
		if (it->NextText)
			fprintf (file, "&%s_txt[%d]},\n", base, k++);
		else
			fprintf (file, "NULL},\n");
	}
	fprintf (file, "};\n");

	if (infoLevel > 1) printf ("wrote %d IntuiText structs\n", k);
	return k;
}


/*
 * Write out list of XY arrays from Border struct main list 
 */
WriteBorderXY (lst)
	struct Border *lst;
{
	register struct Border *b;
	register short  i;

	fprintf (file, "\n%sshort %s_brd_XY[] = {\n", globStr, base);
	for (b = lst; b; b = b->NextBorder) {
		fprintf (file, "\t");
		for (i = 0; i < b->Count; i++) {
			fprintf (file, "%d,%d", b->XY[i * 2], b->XY[i * 2 + 1]);
			if (i != b->Count - 1 || b->NextBorder)
				fprintf (file, ", ");
		}
		fprintf (file, "\n");
	}
	fprintf (file, "};\n");
}


/*
 * Write out list of Border structs from main list.  Returns nubmer of
 * structures written.
 */
int WriteBorder (lst)
	struct Border *lst;
{
	register struct Border *b;
	register short  i = 0, k = 1;

	if (!lst) return 0;

	WriteBorderXY (lst);

	fprintf (file, "\n%sstruct Border %s_brd[] = {\n", globStr, base);
	for (b = lst; b; b = b->NextBorder) {
		fprintf (file, "\t{0,0,%d,0,JAM1,%d,&%s_brd_XY[%d],",
			 b->FrontPen, b->Count, base, i);
		i += b->Count * 2;
		if (b->NextBorder)
			fprintf (file, "&%s_brd[%d]},\n", base, k++);
		else
			fprintf (file, "NULL}\n");
	}
	fprintf (file, "};\n");

	if (infoLevel > 1) printf ("wrote %d Border structs\n", k);
	return k;
}


/*
 * Reverse the gadget list so it will make more sense to the client.
 * This way they will appear in the arrays in the order that they 
 * appear in the description file.
 */
struct Gadget * ReverseGadList (head)
	struct Gadget *head;
{
	struct Gadget *newhead = NULL, *nxt;

	for (; head; head = nxt) {
		nxt = head->NextGadget;
		head->NextGadget = newhead;
		newhead = head;
	}
	return newhead;
}


/*
 * The main output function.
 */
WriteRequester (name, req)
	char             *name;
	struct Requester *req;
{
	short           i, ng, nt, nb;

	if (!(file = fopen (name, "w"))) {
		fprintf (stderr, "Can't open output file\n");
		return;
	}

	req->ReqGadget = ReverseGadList (req->ReqGadget);
	ng = WriteGadgets (req->ReqGadget);
	nt = WriteText (req->ReqText);
	nb = WriteBorder (req->ReqBorder);

	/*
	 * The requester itself.
	 */
	fprintf (file, "\n%sstruct Requester %s_req = {\n\
\tNULL,0,0,%d,%d,0,0,", globStr, base, req->Width, req->Height);
	if (ng) fprintf (file, "%s_gad,", base);
	else	fprintf (file, "NULL,");
	if (nb) fprintf (file, "%s_brd,", base);
	else	fprintf (file, "NULL,");
	if (nt) fprintf (file, "%s_txt,", base);
	else	fprintf (file, "NULL,");
	fprintf (file, "0,0,\n\tNULL,{NULL},NULL,NULL,{NULL}\n};\n");

	fclose (file);
}


MemError ()
{
	fprintf (stderr, "Out of memory.\n");
}


/* Main entry point.  Decode args and call body function.  Args are:
 *
 *	-p	: Print box description
 *	-q	: Run silent, run deep
 *	-v	: Verbose (not much different than normal, really)
 *	-d	: Display preview (is default unless output requested)
 *	-s	: Send Requester declarations to stdout
 */
main (argc, argv)
	int             argc;
	char           *argv[];
{
	int	i, junk = 0, prev = 0, tostdout = 0;
	char	*infile = NULL, *outfile = NULL;

	/*
	 * Decode arguments.
	 */
	for (i = 1; i < argc; i++) {
		if (argv[i][0] == '-') {
			switch (argv[i][1]) {
			    case 'p':
				printBoxes = 1;
				break;
			    case 'q':
				infoLevel = 0;
				break;
			    case 'v':
				infoLevel = 2;
				break;
			    case 'd':
				prev = 1;
				break;
			    case 's':
				tostdout = 1;
				break;
			    case 'g':
				globStr = "";
				break;
			    default:
				junk = 1;
			}
		} else {
			if (!infile) infile = argv[i];
			  else if (!outfile) outfile = argv[i];
			  else junk = 1;
		}
	}
	if (junk || !infile) {
		printf ("Usage: %s [-p|q|v|d|s|g] <file> [<outfile>]\n",
			argv[0]);
		exit (1);
	}
	if (tostdout) {
		outfile = "*";
		infoLevel = 0;
	}
	showPreview = (!outfile || prev);

	if (IntuitionBase = (struct IntuitionBase *)
	    OpenLibrary ("intuition.library", 0L)) {
		Body (infile, outfile);
		CloseLibrary (IntuitionBase);
	}
}


Body (infile, outfile)
	char           *infile, *outfile;
{
	struct Window  *win;
	struct Box     *b;
	short           h, w;

	if (infoLevel > 0) printf (
		"Requester generator v2   Jan 1989  Stuart Ferguson\n");
	if (!OpenLexFile (infile)) {
		fprintf (stderr, "Cannot open %s\n", infile);
		return;
	}
	if (b = ReadFile ()) {
		Format (b);
		b->x = b->y = 0;
		Layout (b, b->xs, b->ys);
		if (printBoxes) PrintBox (b, 0);

		if (CreateIText (b)) {
			if (CreateBorder (b)) {
				MergeBorders ();
				MergeLinear ();
				PositionGadgets ();

				mreq.Width = b->xs;
				mreq.Height = b->ys;
				mreq.ReqGadget = glst;
				mreq.ReqText = itlst;
				mreq.ReqBorder = blst;

				if (showPreview) PreviewRequester (&mreq);
				if (outfile) WriteRequester (outfile, &mreq);

				FreeBorder ();
			}
			FreeIText ();
		}
		FreeBox (b);
	} else fprintf (stderr, "Error reading box description.\n");
	LexCleanup ();
}


/*
 * Open a window to preview the requester layout.
 */
PreviewRequester (req)
	struct Requester *req;
{
	struct Window  *win;
	short           h, w;

	w = req->Width + 12;
	h = req->Height + 16;
	if (w > 640 || h > 200) {
		fprintf (stderr, "Requester too large for preview.\n");
		return;
	}
	if (w < 150) w = 150;
	if (h < 60) h = 60;

	nwin.Width = w;
	nwin.Height = h;
	if (!(win = OpenWindow (&nwin))) {
		fprintf (stderr, "Unable to open preview window.\n");
		return;
	}

	req->LeftEdge =
		(w - win->BorderRight + win->BorderLeft - req->Width) / 2;
	req->TopEdge =
		(h - win->BorderBottom + win->BorderTop - req->Height) / 2;

	RequesterLoop (win, req);
	CloseWindow (win);
}


RequesterLoop (win, req)
	struct Window *win;
	struct Requester *req;
{
	struct Gadget *g;
	struct IntuiMessage *im;
	ULONG class, oldflags;
	USHORT code;
	int gend = 0, looping;

	/*
	 * Determine if this requester can be terminated with a gadget.
	 * If not, provide an alternate exit facility.
	 */
	for (g = req->ReqGadget; g; g = g->NextGadget)
		if (g->Activation & ENDGADGET) {
			gend = 1;
			break;
		}
	oldflags = req->Flags;
	if (!gend) {
		if (infoLevel > 0) printf (
			"No Endgadget -- Press ESC to exit Requester.\n");
		req->Flags |= NOISYREQ;
	}

	if (!Request (req, win)) {
		fprintf (stderr, "Unable to post Requester.\n");
		req->Flags = oldflags;
		return;
	}

	looping = 1;
	while (looping) {
		im = (struct IntuiMessage *) GetMsg(win->UserPort);
		if (!im) {
			WaitPort (win->UserPort);
			continue;
		}
		class = im->Class;
		code = im->Code;
		ReplyMsg (im);
		if (class == VANILLAKEY && code == 27) break;
		if (infoLevel > 0) printf ("Message : %s\n", IMClass (class));
		if (class == REQCLEAR) looping = 0;
	}
	if (looping) EndRequest (req, win);
	req->Flags = oldflags;
}


/*
 * Returns name of message class.  Lots more classes here than are
 * possible, but what the hell.
 */
char * IMClass (class)
	ULONG class;
{
	switch (class) {
	    case SIZEVERIFY:	return ("SIZEVERIFY");
	    case NEWSIZE:	return ("NEWSIZE");
	    case REFRESHWINDOW:	return ("REFRESHWINDOW");
	    case MOUSEBUTTONS:	return ("MOUSEBUTTONS");
	    case MOUSEMOVE:	return ("MOUSEMOVE");
	    case GADGETDOWN:	return ("GADGETDOWN");
	    case GADGETUP:	return ("GADGETUP");
	    case REQSET:	return ("REQSET");
	    case MENUPICK:	return ("MENUPICK");
	    case CLOSEWINDOW:	return ("CLOSEWINDOW");
	    case RAWKEY:	return ("RAWKEY");
	    case REQVERIFY:	return ("REQVERIFY");
	    case REQCLEAR:	return ("REQCLEAR");
	    case MENUVERIFY:	return ("MENUVERIFY");
	    case NEWPREFS:	return ("NEWPREFS");
	    case DISKINSERTED:	return ("DISKINSERTED");
	    case DISKREMOVED:	return ("DISKREMOVED");
	    case WBENCHMESSAGE:	return ("WBENCHMESSAGE");
	    case ACTIVEWINDOW:	return ("ACTIVEWINDOW");
	    case INACTIVEWINDOW:	return ("INACTIVEWINDOW");
	    case DELTAMOVE:	return ("DELTAMOVE");
	    case VANILLAKEY:	return ("VANILLAKEY");
	    case INTUITICKS:	return ("INTUITICKS");
	}
}


#define DEBUG

/*
 * Debug routines.
 */
#ifdef DEBUG

PrintBorder ()
{
	struct Border *b;
	short i;

	printf ("Borders:\n");
	for (b=blst; b; b=b->NextBorder) {
		printf ("%d %d %d %d\n:: ", b->LeftEdge,
			b->TopEdge, b->FrontPen, b->Count);
		for (i=0; i<b->Count; i++)
			printf ("%d,%d ", b->XY[i*2],b->XY[i*2+1]);
		printf ("\n");
	}
}


PrintGadget ()
{
	USHORT		typ;
	struct Gadget	*g;

	printf ("Gadgets:\n");
	for (g=glst; g; g=g->NextGadget) {
		printf ("%d,%d %d,%d  ", g->LeftEdge, g->TopEdge,
			g->Width, g->Height);
		typ = GTYPE(g);
		if (typ == PROPGADGET)	printf ("PROP");
		if (typ == STRGADGET)	printf ("STRING");
		if (typ == BOOLGADGET)	printf ("BOOL");
		printf ("\n");
	}
}
#endif
SHAR_EOF
cat << \SHAR_EOF > blk.doc


		blk - Automatic Requester Generator
			Stuart Ferguson


Blk generates C code for declaring requesters.  It automatically formats
borders and text and generates the declarations for them.  The requester
can contain boolean, string and proportional gadgets.


Boxes:

A Requester and all its internal structures are described to blk as a
tree of nested boxes.  The root box is the Requester itself.  Inner
boxes can be stacked horizontally or vertically or be within each
other.  Borders and text strings are also boxes for the purpose of
formatting.  The box grammer, which is loosly based on lisp syntax, can
be described by the BNF-like description given below.  (Non-terminal
symbols are named and terminal symbols are double quoted strings.)

	box   :== fill | text | bord | block | seq
	fill  :== "f"
	text  :== "(t" onum str ")"
	str   :== [double quoted string]
	bord  :== rule | "(" rule num onum ")"
	rule  :== "|" | "-"
	block :== "(b" num num ")"
	seq   :== "(" ("h"|"v") blist ")"
	blist :== box | box blist
	onum  :== num | [nothing]
	num   :== [unsigned integer]


Some examples:

The following box description would produce a 10 x 10 pixel area with
borders on all four sides:

	(h | (v - (b 10 10) - ) | )

The central structure, "(b 10 10)," is a 10 x 10 block.  This is one of
three boxes in a vbox, or vertically oriented list of boxes.  The other
two boxes in the list are hrules, the "-"'s, which are horizontal "rules,"
or lines.  This produces a box containing an empty area with lines above
and below it.  This box is further included with two vertical rules, "|,"
in an hbox - a horizontally oriented list.  This puts horizontal lines
on each side of the box, closing the square.

      -----       |-----|
      xxxxx       |xxxxx|
      xxxxx       |xxxxx|
      xxxxx       |xxxxx|
      -----       |-----|

Rules are elastic, and while they have no intrinsic length of their own,
they will expand to fill any space available within their bounding box.
The vbox above:

	(v - (b 10 10) - )

has dimensions 10 x 12, so each horizontal rule expands to a length of
10 to fill the enclosing box.

Fills are another type of elastic object.  For example, suppose you wanted
to format a set of strings, like:

	Line #1
	Blah Blah
	ETC

You could list the text boxes in a vbox, thus:

	(v (t "Line #1") (t "Blah Blah") (t "ETC"))

This would produce the formated lines as above.  But then suppose you wanted
each line centered within its box.  For this you use fills.  Each fill
expands to take up as much free space as it can within its bounding box,
and if a string is sandwiched between two fills horizontally, it will be
squeezed by competing fills into the center of its bounding box.

	(v
	   (h f (t "Line #1")   f)
	   (h f (t "Blah Blah") f)
	   (h f (t "ETC")       f)
	)

Now the boxes within the outer vbox are not text boxes but hboxes
containing text boxes and fills to the left and right.  The width of the
vbox is computed as the maximum width of each box it contains.  Since
fills have no intrinsic size, the widest hbox is the one containing the
text box (t"Blah Blah"), which has a width of 9 characters (blk always 
assumes a fixed size font).  If the characters are 8 pixels wide, the
largest hbox will be 72 pixels.  Each of the other two shorter hboxes
are contained within a vbox also of width 72, so each fill expands to
make up the difference.  For example, the first hbox contains two fills
and a 56 pixel wide text box.  To fill up a 72 pixel wide bounding box,
each fill can expand 8 pixels horizontally, thus centering the text box
inbetween them.  The result is:

	 Line #1
	Blah Blah
	   ETC

This whole structure is just a box, and can be used within other box
structures.  For example, to put a border around the above box, you could
use the definition from the first example.  Specifically:

 (h | (v - 
   (v
      (h f (t"Line #1")   f)
      (h f (t"Blah Blah") f)
      (h f (t"ETC")       f)
    )
  -) |)

The problem with this (you will see if you try it) is that the rules
suround the text too closely.  Since there is no space between the text and
the lines they lie almost right on top of each other.  Text boxes look fine
right next to each other, but rules need to be explicitly spaced away from
text.  This can be done with "struts," blocks used explicitly as spacers,
like so:

	(h | (v - (b 0 5) (h (b 5 0)
	   (v
	      (h f (t"Line #1")   f)
	      (h f (t"Blah Blah") f)
	      (h f (t"ETC")       f)
	   )
	 (b 5 0)) (b 0 5) -) |)

The 0 x 5 and 5 x 0 blocks have no volume but since they have width or
height, they affect the spacing of the other components in their h- or
vboxes.

Text and rules take on the default color (see "blk files" below) unless the
color is explicitly stated.  For text boxes this is done with the optional
parameter in the text box description, as in (t 3 "hi there"), which
specifies that the text be color #3.  For rules, a different description
format is used.  Instead of simply | or -, a form such as (| 1 3) or (- 1 2)
is used.  The two examples create a vrule of color 3 and an hrule of color 2
respectively.  The first number is the width of the rule and should always
be 1.

With these simple tools, it is possible to design a wide variety of nicely
layed-out requesters without dealing with a single coordinate or C struct.  
What is more, the strings in the above box definition could be changed to 
anything, and the result would still be three strings centered within a 
perfectly sized box.

Blk also supports a 'C'-style preprocesser for include files, macros,
comments and "ifdef's".  Macros can make deeply nested box structures
more readable and consistent.


Gadgets:

Any sub-box in a box definition can be defined to be a gadget hit box by
following it with the sequence:

   ":" num

From the above example, each text box could be defined as a hit box for a
gadget by restating it as:

      (h f (t"Line #1"):1   f)
      (h f (t"Blah Blah"):2 f)
      (h f (t"ETC"):3       f)

Given these box definitions, blk will generate code for 3 BOOLEAN,
RELVERIFY, REQGADGET, GADGHCOMP gadgets with the GadgetID's 1,2 and 3.
Other types of gadgets can be defined using an extended gadget definition.
These are of the form:

   num {"s"|"p"|"pv"|"ph"} {":"id} flags

Stuff in {}'s is optional; "id" and "flags" are double-quoted strings.
"Num" is the id number for the gadget given after the ":" in the box
definition.  The optional "s", "p", "pv" and "ph" specify string or 
proportional gadgets, the default being boolean.  "ph" is a FREEHORIZ
proportional gadget, "pv" is FREEVERT, and "p" is both.  The optional 
"id" string specifies a string to be used for the GadgetID field in the
gadget struct instead of just using "0x<num>," where num is the id 
given in the box description.  "Flags" is a set of characters used to 
specify flags available in defining a gadget.  Valid flags are (case 
is significant):

      B : GADGHBOX
      t : TOGGLESELECT
      v : RELVERIFY
      e : ENDGADGET
      i : GADGIMMEDIATE
      c : STRINGCENTER  (string gadgets only)
      f : FOLLOWMOUSE

Some examples:

1ph""             - horizontally moving proportional gadget with no flags
7:"OK_ID""ev"     - boolean gadget with id "OK_ID"
                     RELVERIFY and ENDGADGET flags set
5s"v"             - RELVERIFY string gadget
5s:"STR_ID""i"    - string gadget with id "STR_ID", GADGIMMEDIATE flag


Blk output:

The code that blk outputs follows some simple conventions.  These were
implemented for my own convenience and can be changed by altering the source
code.  Structs are assigned in arrays, the name of each array being some
base name followed by the type of struct.  For example, if the base name
were "xyz," some structs would be "xyz_req," "xyz_str," etc.  They are:

	struct Requester <base>_req      /* not an array */

	struct Gadget <base>_gad[]
	struct IntuiText <base>_txt[]
	struct Border <base>_brd[]
	short <base>_brd_XY[]

	UBYTE <base>_nbuf[][NUMCHR]
	struct StringInfo <base>_sinfo[]

	struct Image <base>_pimg[]
	struct PropInfo <base>_pinfo[]

Some of these may be ommited if there are no data of that type.

In user code, the symbol "ta" must be declared as struct TextAttr as in:

	struct TextAttr ta = { ... };
or
	extern struct TextAttr ta;

To use string gadgets, the symbol "NUMCHR" must be #define'd as the number
of characters in the string buffer.  The symbol "undo" must be declared as

	UBYTE undo[NUMCHR];

to be used as the undo buffer for string gadgets (or #define'd as NULL).  
The string buffers will be assigned in the order that they are encountered
in reading the box description, so the buffer for the first encountered 
will be <base>_nbuf[0], the buffer for the second will be <base>_nbuf[1], 
and so on.


Blk files:

A blk input file contains all the information to describe a single
requester.  It starts with a string that is to be the base name for this set
of declarations.  There are then two optional numbers which are the default
border color and default text color.  Neither need to be specified and will
both default to 1.  Then comes the box definition followed by a set of
extended gadget descriptions.  Some examples are included in this release.


Using blk:

The blk commandline is:

	blk [-p|q|v|s|d|g] <infile> [<outfile>]

	<infile>	- Input description file -- required.
	<outfile>	- Output 'C' header file.
	-p		- Print box description.
	-s		- Output 'C' header file to stdout.
	-d		- Display a preview Requester.
	-g		- Write global declarations.
	-q		- Run quiet.
	-v		- Run verbose.

"Infile" is the input requester description.  If no output is specified,
the resulting Requester will be displayed in a window.  If output is
specified, blk will not normally display a preview, but it will if the
"-d" option is specified.  The "-p" option prints a description of the
formatted box tree structure to stdout.  This can be a huge ammout of 
output, but can be useful if you need to know the exact coordinates or 
size of something.  "-g" will write the 'C' declrations as global; the
default is "static".  'C' output can be redirected to stdout with the
"-s" option.  If this option is used, "-q" is also assumed.

The preview Requester will work and can be played with.  Blk will display
the classes of the messages it gets from Intuition as you play with the
Gadgets in the Requester.  If the Requester itself has no "ENDGADGET"
type gadgets for terminating the Requester, blk allows you to end the
preview session by pressing ESC when the preview window is active.


Caveats:

The program is massivly recursive.  Be sure that you have enough stack
to deal with the box description you give.  The deeper you nest your
box descriptions, the deeper blk will go in interpreting them.  I keep
my stack at 12000 and don't have any problems with it.

Blk is intended in the same spirit as a compiler, so the 'C' output 
declarations are not designed to be directly editable.  For example, 
the Gadget flags are not described symbolically, but rather are dumped 
numerically into the declared structures.  The answer to problems
with this is to modify the blk source.

If you do decide to hack the source, be so kind as to keep me informed
of any useful improvements you make in case I decide to do yet another
version.

	Stuart Ferguson		1/89
	(shf@well.UUCP)

	123 James Ave.
	Redwood City, Ca.
		94062
SHAR_EOF
cat << \SHAR_EOF > colors.r
#include "macros.r"

/* Button with text and some space for borders for racks of buttons.
 * Button is hbox that will fill horizontally with text centered.
 */
#define TBUTTON(txt,num) \
	(h f (b 5 0) (v (b 0 4) (t txt) (b 0 3)) (b 5 0) f):num

col 1 1

START_REQ
	TITLE ("Default Color")
	(b 0 5)
	(h f |
	  (v - TBUTTON ("Matte",1) - TBUTTON ("Glossy",2)     -) |
	  (v - TBUTTON ("Unshaded",3) - TBUTTON ("Outline",4) -)
	 | f)
	(b 0 4)
	(h f |
	   (v - TBUTTON ("Transparent",10) -) |
	   (v - TBUTTON ("Opaque",11)      -)
	 | f)
	(b 0 4)
	(h f
	   (v f(t 2"Smoothing:")f)
	   (b 8 0)
	   (v -(h |TBUTTON ("On",12)|TBUTTON ("Off",13)|)-)
	   f
	)
	(b 0 5)
	(h f (v f (t"+"):5 f)
	   (b 15 0)
	   | (v (-1) (b 0 1) (h(b 5 0) (t"15 "):6 (b 5 0)) (-1)) |
	   (b 15 0)
	   (v f (t"-"):7 f) f)
	(b 0 5)
	OKCAN(8,9)
END_REQ


1 "tv" 2 "tv" 3 "tv" 4 "tv"
10 "tv" 11 "tv"  12 "tv" 13 "tv"
6s"v"

OKCAN_EXT(8,9)
SHAR_EOF
cat << \SHAR_EOF > lex.c
/*
 * Lexical Analysis Functions Takes input stream and returns "tokens".
 * Implements pre-processor, allowing #include's and #define's tokens are
 * character strings.
 */
#include <stdio.h>
#include <ctype.h>
#include <functions.h>
#include <exec/types.h>
#include "listmac.h"
#include "std.h"
#include "lex.h"


struct StringElt {
	struct Nod      node;
	char           *buf;
};

struct Token {
	struct Nod      node;
	char           *buf;
	short           type, can_be_macro;
	char            chr;
};

struct Macro {
	struct Nod      node;
	struct Lst      arguments;
	struct Lst      tokens;
	char           *name;
	short           active, nargs;
};

struct ActiveMacro {
	struct Nod      node;
	struct Macro   *mac;
	struct Token   *curtok;
};

struct LexFile {
	struct Nod      node;
	FILE           *fp;
	short           newln;
	short           nextchar;
};

struct IfBlock {
	struct Nod     *node;
	short           skip_else;
};


struct Token   *CopyToken ();
struct Macro   *FindMacro ();
char           *FindString ();


#define BUFLEN 100
static char     buf[BUFLEN];
static short    bufpos;
static struct Token
		rawtok, *lasttok, *retok;

static struct LexFile *toplf;
static struct Lst macros, activemacs, files, ifblocks;
static short    files_open = 0, init_done = 0;
static char	*includestr, *definestr, *ifdefstr,
		*ifndefstr, *elsestr, *endifstr;

static char    *libbase = "hd:include/", libnam[80], basnam[80];

#define NHASH 53
struct Lst      hashTab[NHASH];


/*
 * Open a file for lexing - could be an include.
 * Set newline flag to true.
 */
short OpenLexFile (name)
	char           *name;
{
	struct LexFile *lf;
	struct Macro   *mac;

	/*
	 * on first call, init environment vars 
	 */
	if (!init_done) {
		NewList (&files);
		NewList (&macros);
		NewList (&ifblocks);
		NewList (&activemacs);
		InitHash ();
		includestr = FindString ("include");
		definestr = FindString ("define");
		ifdefstr = FindString ("ifdef");
		ifndefstr = FindString ("ifndef");
		elsestr = FindString ("else");
		endifstr = FindString ("endif");
		init_done = 1;
	}

	/*
	 * if there are no files currently open, prepare for a new one 
	 */
	if (!files_open) {
		while (mac = (struct Macro *) RemHead (&macros))
			FreeMacro (mac);
		ASSERT (!TEST (HEAD (&ifblocks)));
		ASSERT (!TEST (HEAD (&activemacs)));
		retok = NULL;
	}
	lf = NEW (struct LexFile);
	if (!lf) return 0;

	/*
	 * printf ("opening \"%s\"\n", name); 
	 */
	lf->fp = fopen (name, "r");
	if (!lf->fp) {
		FREI (lf);
		return 0;
	}
	lf->newln = 1;
	lf->nextchar = getc (lf->fp);
	AddHead (&files, lf);
	toplf = lf;

	if (feof (lf->fp)) {
		CloseLexFile ();
		return 0;
	}
	files_open = 1;
	return 1;
}


/*
 * Close top file returning 1 for the last file now closed.
 */
short CloseLexFile ()
{
	if (!files_open) return 1;

	fclose (toplf->fp);
	Remove (toplf);
	FREI (toplf);
	toplf = HEAD (&files);
	if (TEST (toplf)) return 0;
	files_open = 0;
	toplf = 0;
	return 1;
}


/*
 * free stuff up 
 */
LexCleanup ()
{
	struct Macro   *mac, *nmac;

	while (files_open) CloseLexFile ();

	FreeHash ();
	for (mac = HEAD (&macros); nmac = NEXT (mac); mac = nmac)
		FreeMacro (mac);
	ASSERT (!TEST (HEAD (&activemacs)));

	/*
	 * initialization can be done again 
	 */
	init_done = 0;
}


FreeMacro (mac)
	struct Macro   *mac;
{
	struct Token   *tok;
	struct StringElt *se;

	while (tok = (struct Token *) RemHead (&mac->tokens))
		FREI (tok);
	while (se = (struct StringElt *) RemHead (&mac->arguments))
		FREI (se);
	FREI (mac);
}


/*
 * Get next raw character from file dealing with backslash escape chars.
 * This is kind of wrong since these really ought to be recognized only
 * inside strings.  Doing it this way deals with backslashes before
 * newlines.
 */
short NextC1 (fp)
	FILE           *fp;
{
	short           c, k, radix;

	c = getc (fp);
	if (c == '\\') {
		c = getc (fp);
		if (isdigit (c)) {
			radix = 10;
			if (c == '0') radix = 8;
			k = (c - '0');
			while (isdigit (c = getc (fp)))
				k = k * radix + (c - '0');
			ungetc (c, fp);
			c = k;
		} else {
			switch (c) {
			    case '\\':
				break;
			    case '"':
				c = '.';
				break;
			    case 'n':
				c = '\n';
				break;
			    case 'b':
				c = '\b';
				break;
			    case 't':
				c = '\t';
				break;
			    case '\n':
				c = getc (fp);
			}
		}
	}
	return c;
}


/*
 * Get next character from Top Lex File, updating nextchar.
 */
short NextC ()
{
	short           c;

	if (!toplf) return EOF;
	c = toplf->nextchar;
	toplf->nextchar = NextC1 (toplf->fp);
	return c;
}


/*
 * Move the top lex file to the next newline.
 */
AdvanceEOL ()
{
	if (!toplf) return;
	while (toplf->nextchar != '\n' && !feof (toplf->fp))
		NextC ();
}


/*
 * Read a raw set of characters updating newline state.  Sets the
 * fields in rawtok to the type and string pointer, if any. 
 */
RawToken ()
{
	short           c;
	short           nochar = 1, comment, nl;

	bufpos = 0;
	rawtok.can_be_macro = 1;

	/*
	 * get a non-blank char 
	 */
	while (nochar) {

		/*
		 * get a character testing for end-of-file if EOF, just
		 * return that fact right away 
		 */
		c = NextC ();
		if (c == EOF) {
			rawtok.type = RT_EOF;
			return;
		}

		/*
		 * test next char: if it's white space other than newline,
		 * just clear the newline flag and continue looking. 
		 */
		if (isspace (c) && c != '\n')
			toplf->newln = 0;
		else {

			/*
			 * otherwise, this is potentially interesting, but it
			 * might be the start of a comment - if so, scan til
			 * the end of the comment (no nested comments ala
			 * "C").  If not, exit the loop. 
			 */
			if (c == '/' && toplf->nextchar == '*') {
				comment = 1;
				while (comment) {
					c = NextC ();
					if (c == '*'
					  && toplf->nextchar == '/') {
						c = NextC ();
						comment = 0;
					}
				}

			} else nochar = 0;
		}
	}

	nl = toplf->newln;
	toplf->newln = 0;
	bufpos = 0;

	/*
	 * otherwise, read out the cases id symbol starting with char 
	 */
	if (nl && c == '#')
		rawtok.type = RT_ESC;

	else if (c == '\'') {
		rawtok.chr = NextC ();
		c = NextC ();

		/*
		 * Skip past weird character constants, like 'FOOL',
		 * making no attempt to deal with them correctly.
		 */
		comment = 0;
		while (c != '\'' && comment < 6) {
			c = NextC ();
			comment++;
		}
		if (comment >= 6) printf ("error in character constant\n");
		rawtok.type = RT_CHRC;

	} else if (isalpha (c) || c == '_') {
		buf[bufpos++] = c;
		while (isalnum (toplf->nextchar) || toplf->nextchar == '_')
			buf[bufpos++] = NextC ();
		buf[bufpos] = 0;
		rawtok.type = RT_ID;
		rawtok.buf = FindString (buf);

	} else if (c == '\n') {
		toplf->newln = 1;
		rawtok.type = RT_NEWLN;

	} else if (isdigit (c)) {
		buf[bufpos++] = c;
		while (isdigit (toplf->nextchar)) buf[bufpos++] = NextC ();
		buf[bufpos] = 0;
		rawtok.buf = FindString (buf);
		rawtok.type = RT_NUM;

	} else if (c == '"') {
		while ((c = NextC ()) != '"')
			if (bufpos < BUFLEN)
				buf[bufpos++] = c;

		if (bufpos + 1 == BUFLEN) printf ("string overflow\n");
		buf[bufpos] = 0;
		rawtok.buf = FindString (buf);
		rawtok.type = RT_STR;

	} else {
		rawtok.chr = c;
		rawtok.type = RT_CHR;
	}
}


/*
 * Main function -- returns string pointer for next token makes include files
 * and macros transparent reads from top open file and returns pointer to
 * string of chars could be a symbol, could be a number, could be a character 
 */
short NextToken (tbuf)
	char          **tbuf;
{
	struct Token   *tok;
	struct Macro   *mac;
	struct ActiveMacro *amac;
	short           i;
	char            c;

	while () {

		/*
		 * get raw token from file or active macro 
		 */
		amac = HEAD (&activemacs);
		if (retok) {
			tok = retok;
			retok = NULL;
		} else if (TEST (amac)) {
			tok = amac->curtok;
			if (!TEST (tok)) {
				amac->mac->active = 0;
				for (i = 0; i < amac->mac->nargs; i++)
					FreeMacro (RemHead (&macros));
				Remove (amac);
				FREI (amac);
				continue;
			}
			amac->curtok = NEXT (amac->curtok);
		} else {
			RawToken ();
			tok = &rawtok;
		}
		switch (tok->type) {
		    case RT_EOF:
			if (CloseLexFile ()) {
				lasttok = tok;
				return RT_EOF;
			}
			break;
		    case RT_ESC:
			RawToken ();
			if (rawtok.type != RT_ID) {
				printf ("real problem with lexer directive\n");
				AdvanceEOL ();
				break;
			}
			if (rawtok.buf == includestr)		Include ();
			else if (rawtok.buf == definestr)	Define ();
			else if (rawtok.buf == ifdefstr)	IfDef (1);
			else if (rawtok.buf == ifndefstr)	IfDef (0);
			else if (rawtok.buf == elsestr)		Else ();
			else if (rawtok.buf == endifstr)	EndIf ();
			else {
				printf ("unknown directive \"%s\"\n", rawtok.buf);
				AdvanceEOL ();
			}
			break;

		    case RT_ID:

			/*
			 * test for known macros (if this token can be one) 
			 */
			if (tok->can_be_macro) {
				mac = FindMacro (tok->buf);
				if (mac) {
					InstantiateMacro (mac);
					break;
				}
			}
		    case RT_STR:
		    case RT_NUM:
			*tbuf = tok->buf;
			lasttok = tok;
			return tok->type;
		    case RT_CHR:
		    case RT_CHRC:
			*tbuf = &tok->chr;
			lasttok = tok;
			return tok->type;
		}
	}
}


/*
 * The given macro has occured in the text, cause it to come into existence
 * with argument substitution. 
 */
InstantiateMacro (mac)
	struct Macro   *mac;
{
	struct ActiveMacro *amac;
	struct StringElt *arg;
	struct Macro   *smac;
	struct Token   *tok;
	char           *buf;
	short           vtok, level, endarglist;

	if (mac->active) {
		printf ("recursive macro ignored\n");
		return;
	}

	/*
	 * read arguments, if any, and make them macros in their own right
	 * arguments are read recursively using NextToken (right? I'm not
	 * sure...) 
	 */
	arg = HEAD (&mac->arguments);
	if (TEST (arg)) {

		/*
		 * get first open paren 
		 */
		vtok = NextToken (&buf);
		if (vtok != RT_CHR || *buf != '(')
			goto argfail;

		/*
		 * loop through args separated with commas 
		 */
		endarglist = 0;
		while (TEST (arg)) {
			smac = NEW (struct Macro);
			if (!smac) goto argfail;
			smac->name = arg->buf;
			smac->active = 0;
			smac->nargs = 0;
			NewList (&smac->arguments);
			NewList (&smac->tokens);

			/*
			 * scan out tokens allowing for nested commas 
			 */
			level = 0;
			while () {
				vtok = NextToken (&buf);
				if (vtok == RT_CHR) {
					if (*buf == '(' || *buf == '{')
						level++;
					if (*buf == '}')
						level--;
					if (*buf == ')') {
						if (level == 0) {
							endarglist = 1;
							break;
						}
						level--;
					}
					if (*buf == ',' && level == 0)
						break;
				}
				tok = CopyToken (lasttok);
				if (!tok) goto argfail;
				tok->can_be_macro = 0;
				AddTail (&smac->tokens, tok);
			}
			AddHead (&macros, smac);
			if (endarglist) break;
			arg = NEXT (arg);
		}
		if (vtok != RT_CHR || *buf != ')') goto argfail;
	}

	/*
	 * Macro does not become active in the global list until all
	 * arguments have been parsed and interpreted. 
	 */
	amac = NEW (struct ActiveMacro);
	if (!amac) return;		/* what the fuck do I do here? */
	amac->mac = mac;
	amac->curtok = HEAD (&mac->tokens);
	mac->active = 1;
	AddHead (&activemacs, amac);
	return;

argfail:
	printf ("bad problem with arguments\n");
}


/*
 * locate a specified macro in the available macros list 
 */
struct Macro * FindMacro (buf)
	char           *buf;
{
	struct Macro   *mac;

	for (mac = HEAD (&macros); TEST (mac); mac = NEXT (mac))
		if (mac->name == buf)
			return mac;

	return NULL;
}


/*
 * Do an "include" directive 
 */
Include ()
{
	short           pos, ok, global;
	char            c;

	/*
	 * get filename 
	 */
	RawToken ();
	ok = 0;
	global = 0;

	if (rawtok.type == RT_STR) {
		ok = 1;
		strcpy (basnam, rawtok.buf);
	} else if (rawtok.type == RT_CHR && rawtok.chr == '<') {
		ok = 1;
		pos = 0;
		while () {
			c = NextC ();
			if (c == '>') break;
			basnam[pos++] = c;
		}
		basnam[pos] = 0;
		global = 1;
	}
	AdvanceEOL ();
	if (!ok) {
		printf ("no file to include\n");
		return;
	}
	if (!global) if (OpenLexFile (basnam)) return;
	strcpy (libnam, libbase);
	strcat (libnam, basnam);
	if (OpenLexFile (libnam)) return;
	printf ("error open include file <%s>\n", libnam);
}


/*
 * Do the "define" directive.
 */
Define ()
{
	struct Macro   *mac = NULL;
	struct Token   *tok;
	struct StringElt *se;

	/*
	 * get identifier to define 
	 */
	RawToken ();
	if (rawtok.type != RT_ID) {
		printf ("error in define - no identifier\n");
		goto escape;
	}
	mac = NEW (struct Macro);
	if (!mac) goto escape;
	mac->name = rawtok.buf;
	mac->active = 0;
	mac->nargs = 0;
	NewList (&mac->arguments);
	NewList (&mac->tokens);

	/*
	 * Look for parenthized argument list.
	 */
	if (toplf->nextchar == '(') {
		RawToken ();
		while () {
			RawToken ();

			/*
			 * deal with special case of "#define MACRO()" 
			 */
			if (!mac->nargs && rawtok.type == RT_CHR
			  && rawtok.chr == ')')
				break;
			if (rawtok.type != RT_ID) {
				printf ("macro argument not an identifier\n");
				goto escape;
			}
			se = NEW (struct StringElt);
			if (!se) goto escape;
			se->buf = rawtok.buf;
			AddTail (&mac->arguments, se);
			mac->nargs++;
			RawToken ();
			if (rawtok.type != RT_CHR) {
				printf ("macro arg list delimiter not a character\n");
				goto escape;
			}
			if (rawtok.chr == ')') break;
			if (rawtok.chr != ',') {
				printf ("macro arg list delimiter not ',' or ')'\n");
				goto escape;
			}
		}
	}

	/*
	 * get the sequence of tokens which make up this definition 
	 */
	while () {
		RawToken ();
		if (rawtok.type == RT_NEWLN || rawtok.type == RT_EOF) break;
		tok = CopyToken (&rawtok);
		if (!tok) goto escape;
		tok->can_be_macro = 1;
		AddTail (&mac->tokens, tok);
	}

	AddHead (&macros, mac);
	return;

escape:
	if (mac) FreeMacro (mac);
	AdvanceEOL ();
}


/*
 * Skip over a defined-out section.  Returns 1 if it ends with #else, 0
 * if it ends with #endif, -1 for EOF.  Keeps track of nested if's
 * being skipped.
 */
short SkipRemovedSec ()
{
	short           level = 0;

	while () {
		RawToken ();
		if (rawtok.type == RT_EOF) return -1;
		if (rawtok.type == RT_ESC) {
			RawToken ();
			if (rawtok.type == RT_ID) {
				if (rawtok.buf == ifdefstr
				  || rawtok.buf == ifndefstr) level++;
				if (rawtok.buf == elsestr)
					if (!level) return 1;
				if (rawtok.buf == endifstr)
					if (!level--) return 0;
			}
		}
	}
}


/*
 * Does the "ifdef"/"ifndef" - "else" - "endif" directive.  Type is 1
 * for ifdef and 0 for ifndef.
 */
IfDef (type)
	short           type;
{
	struct Macro   *mac;
	struct IfBlock *ib;
	short           els;

	RawToken ();
	mac = FindMacro (rawtok.buf);

	/*
	 * If condition is true, add a node to say "skip the next #else
	 * section" if false, skip this section.  If section ends with an
	 * else, add a junk node to pop off for consistency at next #endif.
	 */
	if ((mac && type) || (!mac && !type)) {
		ib = NEW (struct IfBlock);
		if (!ib) return;
		ib->skip_else = 1;
		AddHead (&ifblocks, ib);
	} else {
		els = SkipRemovedSec ();
		if (els == 1) {
			ib = NEW (struct IfBlock);
			if (!ib) return;
			ib->skip_else = 0;
			AddHead (&ifblocks, ib);
		}
	}
}


Else ()
{
	struct IfBlock *ib;
	short           els;

	ib = HEAD (&ifblocks);
	ASSERT (TEST (ib));
	Remove (ib);
	if (!ib->skip_else) {
		printf ("strange \"else\" block found\n");
	} else {
		els = SkipRemovedSec ();
		if (els == 1 && ib->skip_else) {
			printf ("strange \"else\" block found\n");
		}
	}
	FREI (ib);
}


EndIf ()
{
	struct IfBlock *ib;

	ib = HEAD (&ifblocks);
	ASSERT (TEST (ib));
	Remove (ib);
	FREI (ib);
}


/*
 * Backs up one token 
 */
Backspace ()
{
	retok = lasttok;
}


struct Token * CopyToken (tok)
	struct Token   *tok;
{
	struct Token   *ntok;

	ntok = NEW (struct Token);
	if (ntok) *ntok = *tok;
	return ntok;
}



/* HASH TABLE STUFF */


InitHash ()
{
	short           i;

	for (i = 0; i < NHASH; i++)
		NewList (&hashTab[i]);
}


/*
 * Simple but (I hope) effective hash function.
 */
short HashVal (buf)
	char           *buf;
{
	unsigned long   hash;

	hash = 0;
	while (*buf) hash = hash * 3 + *buf++;
	return hash % NHASH;
}


/*
 * Finds (or creates) a stored string matching the given one return pointer
 * which acts as unique ident for this string.
 */
char * FindString (buf)
	char           *buf;
{
	short           hash;
	struct Lst     *hlist;
	struct StringElt *se;
	char           *sto;

	hash = HashVal (buf);
	hlist = &hashTab[hash];

	for (se = HEAD (hlist); TEST (se); se = NEXT (se)) {
		if (!strcmp (se->buf, buf))
			return se->buf;
	}
	se = NEW (struct StringElt);
	if (!se) return NULL;
	sto = NEW_N (char, strlen (buf) + 1);
	if (!sto) {
		FREI (se);
		return NULL;
	}
	AddTail (hlist, se);
	strcpy (sto, buf);
	se->buf = sto;
	return sto;
}


FreeHash ()
{
	short           i;
	struct StringElt *se, *nse;

	for (i = 0; i < NHASH; i++) {
		for (se = HEAD (&hashTab[i]); nse = NEXT (se); se = nse) {
			FREE_N (se->buf, char, strlen (se->buf) + 1);
			FREI (se);
		}
	}
}
SHAR_EOF
cat << \SHAR_EOF > lex.h
#define RT_EOF 0
#define RT_ESC 1
#define RT_STR 2
#define RT_NUM 3
#define RT_ID  4
#define RT_CHR   5
#define RT_CHRC  6
#define RT_NEWLN 7
SHAR_EOF
cat << \SHAR_EOF > listmac.h
/* My MinNode definitions, basically, but with names
 * that I like better for C applications
 */
struct Nod {
   struct Nod *next,*prev;
};

struct Lst {
   struct Nod *head,*tail,*tailprev;
};

/* some macros for dealing with Exec-like lists
 *    HEAD(lst)   struct Lst *lst:  gives first node
 *    TAIL(lst)   struct Lst *lst:  gives last node
 *    NEXT(nod)   struct Nod *nod:  gives next element
 *    PREV(nod)   struct Nod *nod:  gives previous element
 *    TEST(nod)   struct Nod *nod:  is non-zero for valid nodes
 *    OFF1(nod)   struct Nod *nod:  offsets backward one node
 */
#define HEAD(lst)    (void *)(((struct Lst*)(lst))->head)
#define TAIL(lst)    (void *)(((struct Lst*)(lst))->tailprev)
#define NEXT(nod)    (void *)(((struct Nod *)(nod))->next)
#define PREV(nod)    (void *)(((struct Nod *)(nod))->prev)
#define TEST(nod)    NEXT(nod)
#define OFF1(nod)    (void *)((char *)(nod)-sizeof(struct Nod))
SHAR_EOF
cat << \SHAR_EOF > macros.r
/*
 * Standard set of macros for Modeler requesters.  Gives them a 
 * consistent look (if nothing else).
 */

/* Ok/Cancel gadgets, numbers "ok" and "can"
 * It's an hbox with the gadgets pressed apart with a filler.
 * Also has the extended gadget data for these gadgets.
 */
#define OKCAN(ok,can) \
	(h|(v-(v(b 0 3)(h(b 5 0)(t 2"Ok")(b 5 0))(b 0 2)):ok-)| \
	 (b 5 0) f (b 5 0) \
	  |(v-(v(b 0 3)(h (b 5 0)(t 3"Cancel")(b 5 0))(b 0 2)):can-)|)
#define OKCAN_EXT(ok,can) \
	ok:"OK_ID""ev" can:"CAN_ID""ev"

/* An underlined title string centered in an hbox.
 */
#define TITLE(str)	(h f(v(t 3 str)(-1 3))f)

/* Text centered in a vbox -- for labeling larger boxes.
 */
#define LABEL(txt)	(v f(t txt)f)

/* String gadget with a more complex box around it.  Starting text is
 * "txt", gadget number is "num."
 */
#define STRGAD(txt,num) \
	(h|(v-(-1 2)(h(|1 2)(|1 2)(t txt):num(|1 2)(|1 2))(-1 2)-)|)

/* Scale gadgets.  The +/- buttons and scale indicator.
 */
#define SCALE(up,down) \
	(v f \
	  (h f (t 3"Scale") f) \
	  (h (t 1"+"):up f (t 1"mm") f (t 1"-"):down) \
	 f)
#define SCALE_EXT(up,down)	up:"SCLU_ID""v" down:"SCLD_ID""v"

/* Does the boilerplate to start and end a requester.  The boxes between
 * start and end will lie within a vbox with a nice border around it.
 */
#define START_REQ	(h | (v - (h (b 8 0) (v (b 0 8)
#define END_REQ		(b 0 8))(b 8 0)) - ) | )

/* Layer gadgets centered in an hbox.
 * Always numbered 10-17.
 */
#define LAYGAD \
	(h f (v f(t 1"Layer")f) (b 5 0) | \
	 (v - (b 0 3) (h (b 2 0) \
	  (t"1"):10(b 2 0)(t"2"):11(b 2 0)(t"3"):12(b 2 0)(t"4"):13(b 2 0) \
	  (t"5"):14(b 2 0)(t"6"):15(b 2 0)(t"7"):16(b 2 0)(t"8"):17(b 2 0) \
	 ) (b 0 2) -) \
	| f)
#define LAYGAD_EXT \
	10:"BUF_ID""ti"   11:"BUF_ID+1""ti"\
	12:"BUF_ID+2""ti" 13:"BUF_ID+3""ti"\
	14:"BUF_ID+4""ti" 15:"BUF_ID+5""ti"\
	16:"BUF_ID+6""ti" 17:"BUF_ID+7""ti"
SHAR_EOF
cat << \SHAR_EOF > pro.c
#include <stdio.h>
#include <functions.h>
#include <exec/types.h>
#include <intuition/intuition.h>

#define OK_ID  0x80
#define CAN_ID 0x81
#define STR_ID 0x82
#define NUMCHR 20
struct TextAttr ta = { (UBYTE*)"topaz.font",8,0,0 };
UBYTE undo[NUMCHR];

#define REQ col_req
#include "prototype.h"


struct NewWindow nwin = {
   0,0,300,150,
   -1,-1,GADGETUP|GADGETDOWN|REQCLEAR|MOUSEMOVE,
   WINDOWDEPTH|WINDOWDRAG|SMART_REFRESH,
   NULL,NULL,(UBYTE*)"Blocks",NULL,
   NULL,0,0,0,0,WBENCHSCREEN
};

struct IntuitionBase *IntuitionBase;


main(argc,argv)
   int argc;
   char *argv[];
{
   struct Window *win;

   IntuitionBase=(struct IntuitionBase *)OpenLibrary("intuition.library",0L);
   if (IntuitionBase) {
      nwin.Width = REQ.Width + 10;
      nwin.Height = REQ.Height + 14;
      win = OpenWindow (&nwin);
      if (win) {
         Body (win);
         CloseWindow (win);
      }
      CloseLibrary (IntuitionBase);
   }
}



Body(win)
   struct Window *win;
{
   struct IntuiMessage *im;
   BOOL looping=TRUE;
   ULONG class;

/* set up proportional gadgets
   scol_pinfo[0].HorizBody = ((long)0xffff)/16;
   scol_pinfo[1].HorizBody = ((long)0xffff)/16;
   scol_pinfo[2].HorizBody = ((long)0xffff)/16;
 */
   REQ.LeftEdge = 5;
   REQ.TopEdge = 12;
   Request (&REQ,win);

   while (looping) {
      im = (struct IntuiMessage *) GetMsg(win->UserPort);
      if (!im) WaitPort (win->UserPort);
       else {
         printf ("Message: %lx\n", im);
         class = im->Class;
         ReplyMsg (im);
         if (class == REQCLEAR) looping = FALSE;
       }
   }
}
SHAR_EOF
cat << \SHAR_EOF > std.h
#define NEW(typ) (typ*)AllocMem((long)sizeof(typ),0L)
#define FREE(p,typ) FreeMem(p,(long)sizeof(typ))

#define NEW_N(typ,n) (typ*)AllocMem((long)((n)*sizeof(typ)),0L)
#define FREE_N(p,typ,n) FreeMem(p,(long)((n)*sizeof(typ)))

#define NEW_X(typ,x) (typ*)AllocMem((long)(sizeof(typ)+(x)),0L)
#define FREE_X(p,typ,x) FreeMem(p,(long)(sizeof(typ)+(x)))

#define FREI(p) FreeMem(p,(long)sizeof(*p))

#ifndef FINAL_VERSION
#define ASSERT(c) if (!(c)) printf ("assert failure in %s (%s : %d)\n", \
   __FUNC__,__FILE__,__LINE__)
#else
#define ASSERT(c)
#endif

#define MAKE_ID(a,b,c,d) (((long)a<<24)|((long)b<<16)|((long)c<<8)|d)
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.