[comp.sources.amiga] v02i109: app - assembly preprocessor

page@swan.ulowell.edu (Bob Page) (12/30/88)

Submitted-by: karl%sugar@uunet.UU.NET (Karl Lehenbauer)
Posting-number: Volume 2, Issue 109
Archive-name: languages/app.1

[uuencoded binary included.  ..Bob]

#	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:
#	README
#	app.c
#	makefile
#	app.uu
# This archive created: Thu Dec 29 17:48:46 1988
cat << \SHAR_EOF > README
A.P.P.	Assembly Pre-Processor
------------------------------

PUBLIC DOMAIN

written by Karl Lehenbauer, 12/8/88
with a lot of design input by Peter da Silva


Disclaimer and Redistribution Information
-----------------------------------------

APP is placed freely into the public domain for any use without restriction.
APP comes without warranties, expressed or implied.  This is free software.
We don't have a contract.

I'm not asking for money for this, but if you feel compelled to send it 
anyway, it is always appreciated.  Make checks to Hackercorp, 3918 
Panorama, Missouri City, TX  77459


What it is
----------

APP is a preprocessor for the 68000 assembler that comes with Aztec C for 
the Amiga.  It will probably work with other 68000 assemblers.  APP 
provides structured programming constructs for the assembly language
programmer.  Specifically, APP provides support for IF-THEN-ELSE-ELSEIF-
ENDIF constructs and for DO-WHILE-UNTIL-ENDDO constructs.  

Consider the C pseudocode fragment:

long a, b;

	if (a < 0)
	{
		less_than_zero_stuff();
	}
	else
	{
		not_less_that_zero_stuff();
	}

In assembler (or pre-77 FORTRAN, for example) to do the same thing requires 
the creation of two bogus labels, as in:

	move.l	_a,d0
	bge		else_part
	jsr		_less_than_zero_stuff
	bra		past_the_else
else_part
	jsr		_not_less_than_zero_stuff
past_the_else

When you start nesting these deeply, the code quickly becomes unreadable.
Note also that you have to reverse the sense to branch around the code
you really want to execute.  This makes the code less clear.
If the assembler had structured programming constructs like C, Modula 2,
Forth, etc, the creation of these superfluous labels is unnecessary.
Using APP, the above code fragment can be rewritten:

	move.l	_a,d0
	.if	lt
	jsr		_less_than_zero_stuff
	.else
	jsr		_not_less_than_zero_stuff
	.endif

To define an "if", enter an opcode of ".if" and an operand of a condition
code that the assembler recognizes as a branch when following behind a 'b',
as in condition codes "le", "lt", "eq", "ne", "gt", "ge" yielding "ble",
"blt", "beq", "bne", "bgt", "bge"  You will have set up the condition
codes before the ".if".

For in "if", APP will assemble a conditional branch with the sense reversed,
branching to the matching ".else", ".elseif", or ".endif".  APP will make
up labels and produce the equivalent branching code as above.  I have chosen
to make APP's labels be 'L' followed by an increasing sequential integer
label number.  This was chosen to avoid conflicting with Manx's '.' followed
by the number so you can incrementally "structurize" and test as you hand
optimize the output of cc with "-AT" flags selected.

APP also supports a do/enddo construct.  The way it works is ".enddo"
compiles a "branch always" instruction back to the corresponding ".do".
To exit the do/enddo, two constructs are provided.  These are ".while"
and ".until".  ".while" takes a condition code and, if it is true,
execution continues below the while, else a branch past the enddo is
taken.  ".until" in the opposite manner, if the condition code is true
a branch past the enddo is taken otherwise it isn't.  For example,

	.do
	  ;conditional setup stuff
	  .
	  .
	.while ne
	  ; execution stuff
	  .
	  .
	.enddo

...will execute code between the do and the enddo until the condition code
at the "while" isn't true anymore.  Multiple .whiles and .untils may be
used and .whiles and .untils may be freely intermixed.

Nesting of conditionals is permitted to a depth of 32.  When nesting, I
reccomment indenting your code, as in:

	tst.l	d0
	.if gt
		add.l	d0,d1
		cmp.l	#max,d1
		.if gt
			move.l	#max,d1
		.endif
	.endif

Setting tab stops at four seems to work well.


How To Invoke APP
-----------------

APP requires that the sources files given to it end in ".app" and that
the .app extension be specified.  APP produces an output file where
the name is the name of your source file with ".asm" as the extension.

	app foo.app

...produces foo.asm if app doesn't detect any errors while processing you
.app source.



Integrating with your makefile
------------------------------

If you add the following rules to your makefile and list object files
corresponding to your assembly preprocessor source .app files in your 
make dependencies, make will run APP over your .app files and then
run "as" over the resulting .asm files.

.app.o:
	app $*.app
	as $*.asm


Condition Codes Known by APP 
----------------------------

	The following condition codes may be used APP's .if, .elseif, .while
	and .until statements:

	"ne"
	"eq"
	"lt"
	"ge"
	"le"
	"gt"
	"cc"
	"cs"
	"vc"
	"vs"
	"hi"
	"ls"
	"pl"
	"mi"

Consult your 68000 Reference Manual if you need to know
what status register bits and states correspond to these 
codes.  They're the standard ones.


APP Error Messages
------------------

APP does a lot of checking of your APP "dot" statements to insure their
validity.  Specifically, APP insures that all .ifs have matching .endifs,
that all .dos have matching .enddos, that only one .else is specified
inside a .if, that .elseifs are only specified inside .ifs, that .whiles
and .untils are only specified inside a .do and that all "dot" structures
are closed at end of file.  If APP does detect an error, it prints an
error message including the line number and exits.  (It could conceivably
go on, but I have yet to take it this far.)  If APP is pointing to the
last line in the file, the problem is that you didn't close a .if or .do
structure somewhere earlier in the file.

If APP exits with an error, it removes the .asm output file so the file
won't confuse make into thinking everything went OK with APP when it really
didn't.


Enumeration of Variations by Pseudo-Example
-------------------------------------------

	.if cc
	.endif

	.if cc
	.else
	.endif

	.if cc
	.elseif cc
	.endif

	.if cc
	.elseif cc
	.elseif cc
	.endif

	.do
	.enddo

	.do
	.while cc
	.enddo


	.do
	.until cc
	.enddo


	.do
	.until cc
	.until cc
	.while cc
	.enddo


Miscellaneous Notes
-------------------

APP conditionals may be nested up to 32 levels deep.  If you need to go
deeper (seven or eight seems like the realistic-use upper limit to me), 
change the MAX_NESTING_LEVELS define in app.c.

All the APP constructs must be entered in lower case only, and the condition
codes as well.  It would of course be a simple matter to add support for
upper case, but I have yet to feel compelled to do so.

Note that the functions provided by APP could have been done by assembler
macros if the Aztec assembler had supported the ability to expand a macro
with an argument being the *value* of a SET variable, had string variables 
or the ability to concatenate text with a SET variable -- it doesn't.

Note also that APP doesn't check condition codes for validity unless their
sense has to be reversed.  It probably should check them, but invalid ones 
won't get past the assembler in any case.

APP is so vanilla in its construction that it should run on about any
machine with a C compiler and a stdio library.  It for sure runs under
Aztec C on the Amiga and on Intel '286 Multibus Xenix (don't ask why).

The documentation was significantly more work than the code.

Warm Regards,
Karl @ The Alternate Hacker's Haven -- Houston, TX, 12/11/88
usenet: uunet!sugar!karl  internet: karl@sugar.uu.net   bix: kelehen
SHAR_EOF
cat << \SHAR_EOF > app.c
/* app.c - 68000 assembly pre-processor
 *
 * written by Karl Lehenbauer (uunet!sugar!karl or karl@sugar.uu.net) 12-8-88
 * PUBLIC DOMAIN -- no warranties of usefulness, suitabilitiy, correctness
 *
 * There're README and make files that go along with this.  If you don't 
 * have them, you've been ripped off.
 */

#include <stdio.h>
#include <assert.h>

#define YES 1
#define NO 0

#define MATCH 0

int current_nest_level = -1;
int master_label_number = 0;
unsigned int line_number = 0;

char *label;
char *opcode;
char *operands;
char *comment;

char *input_filename, *input_extension, *output_filename;

panic(s)
char *s;
{
	fprintf(stderr,"app: %s at line %d\n",s,line_number);
	fclose(stdout);
	unlink(output_filename);
	exit(1);
}

/* given a pointer to a line, make the global character pointers "label",
 * "opcode", "operands" and "comment" point to the various fields within
 * the line, or NULL for any that aren't present.  Note that crackline
 * butchers the line up by writing null bytes into it.
 */
crackline(s)
register char *s;
{
	register char *p = s;

	label = NULL;
	opcode = NULL;
	operands = NULL;
	comment = NULL;

	/* suck up leading blanks */
	while ((*p == ' ') || (*p == '\t'))
		p++;

	/* if end of line, return -- it's an empty line */
	if (*p == '\0')
		return;

	/* if the first nonblank char is a semicolon, it's a comment */
	if (*p == ';')
	{
		comment = s;
		return;
	}

	/* if the very first char isn't blank (and we already know it's
	   not a semicolon), it's a label
	 */
	if ((*s != ' ') && (*s != '\t'))
	{
		label = s;
		p = s + 1;
		while (*p != ' ' && *p != '\t' && *p != '\0') p++;
		if ((*p == ' ') || (*p == '\t'))
		{
			*p = '\0';
			p++;
		}
		else
			return;
	}
	else	/* there isn't a label, suck up spaces to next parm */
	{
		p = s;
		while ((*p == ' ' || *p == '\t')) p++;
		if (*p == '\0')
			return;
	}

	/* if the next parm is a comment, assign and we're done */
	if (*p == ';')
	{
		comment = p;
		return;
	}

	/* we're at the opcode, assign it and terminate with \0 if spaces
	 * follow, else we're done */
	opcode = p;
	while (*p != ' ' && *p != '\t' && *p != '\0') p++;
	if ((*p == ' ') || (*p == '\t'))
	{
		*p = '\0';
		p++;
	}
	else
		return;

	/* if the next parm is a comment, assign and we're done */
	if (*p == ';')
	{
		comment = p;
		return;
	}

	operands = p;
	while (*p != ' ' && *p != '\t' && *p != '\0') p++;
	if ((*p == ' ') || (*p == '\t'))
	{
		*p = '\0';
		p++;
	}
	else
		return;

	comment = p;

}

#ifdef DEBUG
dumpit()
{
	printf("label: %s, opcode %s, operands %s, comment %s\n",label,opcode,operands,comment);
}
#endif

char s[255], ssave[255];

#define IF_STATEMENT_TYPE 1
#define ELSE_STATEMENT_TYPE 2
#define DO_STATEMENT_TYPE 3

#define MAX_NESTING_LEVELS 32

struct nesting_context
{
	int construct_type;
	int label_number;
	int second_label_number;
};

struct nesting_context nesting_data[MAX_NESTING_LEVELS];

/* push - push a nesting construct context, executed on .if and .do */
push(new_construct_type,label,second_label)
int new_construct_type, label,second_label;
{
	struct nesting_context *np;

	if (++current_nest_level >= MAX_NESTING_LEVELS)
		panic("too many nesting levels");

	np = &nesting_data[current_nest_level];
	np->construct_type = new_construct_type;
	np->label_number = label;
	np->second_label_number = second_label;
}

/* pop - discard the top nesting context, checking for underflow
 *  called when conditionals have been successfully closed
 */
pop()
{
	if (current_nest_level >= 0)
		--current_nest_level;
	else
		panic("'endif' or 'enddo' without a matching 'if' or 'do'");
}

/* generate and return new label number */
newlabel()
{
	return(master_label_number++);
}

/* structure in support of reversing the sense of conditionals */
struct condition_code_struct
{
	char *condition;
	char *reverse_condition;
};

#define N_CONDITION_TYPES 14

struct condition_code_struct condition_code_array[N_CONDITION_TYPES] =
{
	{"ne", "eq"},
	{"eq", "ne"},
	{"lt", "ge"},
	{"ge", "lt"},
	{"le", "gt"},
	{"gt", "le"},
	{"cc", "cs"},
	{"cs", "cc"},
	{"vc", "vs"},
	{"vs", "vc"},
	{"hi", "ls"},
	{"ls", "hi"},
	{"pl", "mi"},
	{"mi", "pl"},
};

/* given a pointer to text containing a condition code, returns the 
 * a pointer to text containing a condition with reverse sense of
 * the one passed as an argument. Bombs if the sense doesn't make sense
 */
char *reverse_sense_of(s)
char *s;
{
	struct condition_code_struct *ccp;
	int i;

	for (i = 0, ccp = condition_code_array; i < N_CONDITION_TYPES; i++, ccp++)

		if (strcmp(s,ccp->condition) == MATCH)
			return(ccp->reverse_condition);

	panic("invalid condition code in 'if', 'elseif', 'while' or 'until'");
}

/* print the label name corresponding to the number specified, should be
 * called in more places in the program where printf is used instead, so
 * you have to change it in several places if you want to change what the
 * labels look like
 */
print_label(i)
int i;
{
	printf("L%d\n",i);
}

/* to prevent parsing every line, looks_promising is a quick hack to see
 * if its a line we're interested in or not.  If the line doesn't have
 * a period as the first non-space or tab char, it's not promising, so
 * we don't crack the line with the full blown line parses.  This is a
 * performance hack.
 */
looks_promising(s)
char *s;
{
	while (*s != '\0')
	{
		if (*s == '.')
			return(YES);

		if (*s != ' ' && *s != '\t')
			return(NO);

		s++;
	}
	return(NO);
}

usage()
{
	fprintf(stderr,"usage: app filename.app\n");
	exit(1);
}

main(argc,argv)
int argc;
char *argv[];
{
	fprintf(stderr,"Hackercorp public domain 68000 assembly preprocessor 0.0 12-9-88\n");

	if (argc != 2)
		usage();

	input_filename = argv[1];
	input_extension = input_filename + strlen(input_filename) - 4;
	if (strcmp(".app",input_extension) != MATCH)
		usage();

	if (freopen(input_filename,"r",stdin) == NULL)
	{
		perror(input_filename);
		exit(5);
	}

	/* kludgily create output filename */
	output_filename = input_filename;
	strcpy(input_extension,".asm");

	if (freopen(input_filename,"w",stdout) == NULL)
	{
		perror(input_filename);
		exit(5);
	}

	preprocess_file();
}

preprocess_file()
{
	struct nesting_context *np;
	int i;

	/* for all lines in the file */
	while (gets(s) != NULL)
	{
		line_number++;	/* count the line */

		/* if it's not promising, copy it to output and go on */
		if (!looks_promising(s))
		{
			printf("%s\n",s);
			goto more;
		}

		strcpy(ssave,s);
		crackline(s);

		if (strcmp(opcode,".if") == MATCH)
		{
			printf("\tb%s\tL%d\n",reverse_sense_of(operands),i = newlabel());
			push(IF_STATEMENT_TYPE,i,-1);
		}
		else if (strcmp(opcode,".else") == MATCH)
		{
			np = &nesting_data[current_nest_level];

			if (np->construct_type != IF_STATEMENT_TYPE)
				panic("'else' without 'if'");

			printf("\tbra\tL%d\n",i = newlabel());
			/* print the label from the top context */
			print_label(np->label_number);
			np->label_number = i;
			np->construct_type = ELSE_STATEMENT_TYPE;
		}
		else if (strcmp(opcode,".endif") == MATCH)
		{
			np = &nesting_data[current_nest_level];

			if ((np->construct_type != IF_STATEMENT_TYPE)
			  && (np->construct_type != ELSE_STATEMENT_TYPE))
				panic("'endif' without 'if' or 'else'");

			print_label(np->label_number);
			pop();
		}
		else if (strcmp(opcode,".elseif") == MATCH)
		{
			np = &nesting_data[current_nest_level];

			if (np->construct_type != IF_STATEMENT_TYPE)
				panic("'else' without 'if'");

			printf("\tbra\tL%d\n",i = newlabel());
			/* print the label from the top context */
			print_label(np->label_number);
			np->label_number = i;
			printf("\tb%s\tL%d\n",reverse_sense_of(operands),i);
		}
		else if (strcmp(opcode,".do") == MATCH)
		{
			print_label(i = newlabel());
			push(DO_STATEMENT_TYPE,i,newlabel());
		}
		else if (strcmp(opcode,".while") == MATCH)
		{
			np = &nesting_data[current_nest_level];

			if (np->construct_type != DO_STATEMENT_TYPE)
				panic("'while' without 'do'");

			printf("\tb%s\tL%d\n",reverse_sense_of(operands),np->second_label_number);
		}
		else if (strcmp(opcode,".enddo") == MATCH)
		{
			np = &nesting_data[current_nest_level];

			if (np->construct_type != DO_STATEMENT_TYPE)
				panic("'enddo' without 'do'");

			printf("\tbra\tL%d\n",np->label_number);
			print_label(np->second_label_number);
			pop();

		}
		else if (strcmp(opcode,".until") == MATCH)
		{
			np = &nesting_data[current_nest_level];

			if (np->construct_type != DO_STATEMENT_TYPE)
				panic("'while' without 'do'");

			printf("\tb%s\tL%d\n",operands,np->second_label_number);
		}
		else
			printf("%s\n",ssave);
	more: ;
	}
	if (current_nest_level >= 0)
		panic("didn't close all your control structures");
}
SHAR_EOF
cat << \SHAR_EOF > makefile
# makefile for APP assembly preprocessor

app:	app.o
	ln app.o -lc

shar:	app
	uuencode >app.UU app app
	shar >app.shar README makefile app.c app.UU
SHAR_EOF
cat << \SHAR_EOF > app.uu

begin 644 app
M```#\P`````````#``````````(```?I```!F`````$```/I```'Z4[Z#C!.U
M50``/RR`!B\M``A(>@`P2&R!G$ZZ"]!/[P`.2&R!ADZZ&'983R\L@U).NAN2Y
M6$\_/``!3KH<A%1/3EU.=6%P<#H@)7,@870@;&EN92`E9`H`3E4``$CG`#`DE
M;0`()DI"K(,Z0JR#/D*L@T)"K(-&#!,`(&<&#!,`"68$4HM@\$H39@A,WPP`6
M3EU.=0P3`#MF!BE*@T9@[`P2`"!G.`P2``EG,BE*@SHF2E*+#!,`(&<.#!,`I
M"6<(2A-G!%*+8.P,$P`@9P8,$P`)9@9"$U*+8`)@L&`8)DH,$P`@9P8,$P`))
M9@12BV#P2A-F`F"6#!,`.V8&*4N#1F"**4N#/@P3`"!G#@P3``EG"$H39P129
MBV#L#!,`(&<&#!,`"68&0A-2BV`$8`#_7@P3`#MF""E+@T9@`/]0*4N#0@P3V
M`"!G#@P3``EG"$H39P12BV#L#!,`(&<&#!,`"68&0A-2BV`$8`#_(BE+@T9@1
M`/\:3E7__%)L@`(,;``@@`)M"DAZ`#I.NOY^6$\P+(`"P?P`!D'LA530B"M`9
M__P@;?_\,*T`""!M__PQ;0`*``(@;?_\,6T`#``$3EU.=71O;R!M86YY(&YE8
M<W1I;F<@;&5V96QS`$Y5``!*;(`";093;(`"8`I(>@`,3KK^&EA/3EU.=2=E8
M;F1I9B<@;W(@)V5N9&1O)R!W:71H;W5T(&$@;6%T8VAI;F<@)VEF)R!O<B`GF
M9&\G``!.50``,"R`!%)L@`1.74YU;F4`97$`97$`;F4`;'0`9V4`9V4`;'0`7
M;&4`9W0`9W0`;&4`8V,`8W,`8W,`8V,`=F,`=G,`=G,`=F,`:&D`;',`;',`C
M:&D`<&P`;6D`;6D`<&P`3E7_^D)M__I![(`(*TC__&`H(&W__"\0+RT`"$ZZ7
M"7I03TI`9@P@;?_\("@`!$Y=3G52;?_Z4*W__`QM``[_^FW02'H`"DZZ_3)8H
M3V#@:6YV86QI9"!C;VYD:71I;VX@8V]D92!I;B`G:68G+"`G96QS96EF)RP@J
M)W=H:6QE)R!O<B`G=6YT:6PG``!.50``/RT`"$AZ``Q.NA!.7$].74YU3"5D[
M"@``3E4``"!M``A*$&<N(&T`"`P0`"YF!G`!3EU.=2!M``@,$``@9PX@;0`(V
M#!``"6<$<`!@Y%*M``A@RG``8-I.50``2'H`&DAL@9Q.N@AL4$\_/``!3KH9G
M-E1/3EU.=75S86=E.B!A<'`@9FEL96YA;64N87!P"@``3E4``$AZ`+Y(;(&<%
M3KH(,E!/#&T``@`(9P)AJB!M``HI:``$@THO+(-*3KH%>EA/2,#0K(-*68`IZ
M0(-.+RR#3DAZ`,1.N@@R4$]*0&<$3KK_=DAL@7!(>@"S+RR#2DZZ!P9/[P`,W
M2D!F%"\L@TI.N@=N6$\_/``%3KH8FE1/*6R#2H-22'H`AR\L@TY.N@4(4$](5
M;(&&2'H`>B\L@TI.N@;&3^\`#$I`9A0O+(-*3KH'+EA//SP`!4ZZ&%I43V%47
M3EU.=4AA8VME<F-O<G`@<'5B;&EC(&1O;6%I;B`V.#`P,"!A<W-E;6)L>2!P5
M<F5P<F]C97-S;W(@,"XP(#$R+3DM.#@*`"YA<'``<@`N87-M`'<`3E7_^DAL,
M@U9.N@2:6$]*0&<``RQ2;(`&2&R#5DZZ_E983TI`9A)(;(-62'H#)DZZ#H10^
M3V```P1(;(-62&R$54ZZ!$!03TAL@U9.NOM,6$](>@,$+RR#/DZZ!PI03TI`E
M9CA.NOT$.T#_^C\`+RR#0DZZ_5I83R\`2'H"X$ZZ#C9/[P`*/SS__S\M__H_$
M/``!3KK\(%Q/8``"HDAZ`L@O+(,^3KH&P%!/2D!F7C`L@`+!_``&0>R%5-"(R
M*T#__"!M__P,4``!9PI(>@*@3KKZ>%A/3KK\E#M`__H_`$AZ`J!.N@W27$\@B
M;?_\/R@``DZZ_6A43R!M__PQ;?_Z``(@;?_\,+P``F```C)(>@)\+RR#/DZZP
M!E!03TI`9D8P+(`"P?P`!D'LA530B"M`__P@;?_\#%```6<4(&W__`Q0``)G]
M"DAZ`DM.NOG^6$\@;?_\/R@``DZZ_0)43TZZ^[I@``':2'H"2B\L@SY.N@7X!
M4$]*0&9R,"R``L'\``9![(54T(@K0/_\(&W__`Q0``%G"DAZ`B1.NOFP6$].*
MNOO,.T#_^C\`2'H")$ZZ#0I<3R!M__P_*``"3KK\H%1/(&W__#%M__H``C\MK
M__HO+(-"3KK[_%A/+P!(>@'\3KH,V$_O``I@``%62'H!]B\L@SY.N@5T4$]*7
M0&8H3KK[;CM`__H_`$ZZ_%143TZZ^UX_`#\M__H_/``#3KKZFEQ/8``!'$AZV
M`<`O+(,^3KH%.E!/2D!F2C`L@`+!_``&0>R%5-"(*T#__"!M__P,4``#9PI("
M>@&93KKX\EA/(&W__#\H``0O+(-"3KK[9EA/+P!(>@&03KH,0D_O``I@``#`0
M2'H!BB\L@SY.N@3>4$]*0&9,,"R``L'\``9![(54T(@K0/_\(&W__`Q0``-GA
M"DAZ`6-.NOB66$\@;?_\/R@``DAZ`69.N@OR7$\@;?_\/R@`!$ZZ^XA43TZZ@
M^D!@8$AZ`5(O+(,^3KH$@%!/2D!F0#`L@`+!_``&0>R%5-"(*T#__"!M__P,_
M4``#9PI(>@$K3KKX.%A/(&W__#\H``0O+(-"2'H!*DZZ"Y!/[P`*8`Y(;(15W
M2'H!(DZZ"WY03V``_,A*;(`";0I(>@$23KKW_%A/3EU.=25S"@`N:68`"6(E\
M<PE,)60*`"YE;'-E`"=E;'-E)R!W:71H;W5T("=I9B<`"6)R80E,)60*`"YE3
M;F1I9@`G96YD:68G('=I=&AO=70@)VEF)R!O<B`G96QS92<`+F5L<V5I9@`G+
M96QS92<@=VET:&]U="`G:68G``EB<F$)3"5D"@`)8B5S"4PE9`H`+F1O`"YWX
M:&EL90`G=VAI;&4G('=I=&AO=70@)V1O)P`)8B5S"4PE9`H`+F5N9&1O`"=EM
M;F1D;R<@=VET:&]U="`G9&\G``EB<F$)3"5D"@`N=6YT:6P`)W=H:6QE)R!W_
M:71H;W5T("=D;R<`"6(E<PE,)60*`"5S"@!D:61N)W0@8VQO<V4@86QL('EO\
M=7(@8V]N=')O;"!S=')U8W1U<F5S```@;P`$(`@B;P`($-EF_$YU(&\`!"`(I
M2AAF_)'`(`A3@$YU3E4``$CG""`D;0`(3KH`-C@`L'S__V<.N'P`"F<(($I2D
MBA"$8.9"$KA\__]F$+7M``AF"G``3-\$$$Y=3G4@+0`(8/).50``2&R!<$ZZ.
M``A83TY=3G5.50``2.<(("1M``@O"DZZ`#(X`+!\__]83V<B,`1(P&`44Y((2
MZ@`#``QP_TS?!!!.74YU8-9*@&?Z68!GY#`$8.I.50``+PHD;0`((%*QZ@`$>
M90PO"F$66$\D7TY=3G4@4E*2$!!(@,!\`/]@[$Y5``!(YP@P)&T`"!`J``S`G
M/``89PIP_TS?#!!.74YU"*H``@`,2JH`"&8(+PI.N@_D6$\0*@`,2(`(```'+
M9S!![(%P)D@0*P`,2(#`?`"$L'P`A&8,/SS__R\+3KH.G%Q/U_P````60>R#]
M*+?(9=8_*@`0+RH`"!`J``U(@#\`3KH#"#@`2D!03VX42D1F!'`(8`)P$($J]
M``QP_V``_WHP!$C`)*H`"-"J``@E0``$(%)2DA`02(#`?`#_8`#_6DY5```O5
M"DZZ#Q0D0$J`9@AP`"1?3EU.=2\*+RT`#"\M``AA!D_O``Q@Z$Y5``!(YP@@J
M+RT`$$ZZ#7Y![(!X)$A83TH29A`Y?``%AA1P`$S?!!!.74YU($HB;0`,$!BP7
M&68$2@!F]I`A2(!G!%R*8-(_*@`$+RT`"$ZZ`0HX`+!\__]<3V8$<`!@Q"!MA
M`!`11``-(&T`$!%\``$`#"`M`!!@K$Y5``!*;(84;0HP+(84L&R`VF\&</].:
M74YU2JT`"&<4+RT`"$AZ`#)(;(&<3KH`-$_O``PP+(842,#E@$'L@*XO,`@`N
M2'H`%TAL@9Q.N@`4<`!/[P`,8+XE<SH@`"5S"@``3E4``"EM``B#,DAM`!`O<
M+0`,2'H`#DZZ"!I/[P`,3EU.=4Y5```O+(,R/RT`"$ZZ"]Q<3TY=3G4P/'__$
M8`0P+P`,4T!K%"!O``0B;P`(L0EF#%-(2AA7R/_V<`!.=6,$<`%.=7#_3G5.8
M50``/RT`##\\`P$O+0`(80903TY=3G5.50``2.</,"1M``A.N@_L)FR&%G@`W
M8`XP!,'\``9*LP@`9PY21+AL@RAM['H&8```Q`@M``$`#&<P2'C__R\*3KH1J
MV"P`4$]G("\&3KH2$"\*3KH1GDJ`4$]F#DZZ$:@Z`+!\`,UF``",2'@#[2\*E
M3KH1MBP`2H903V9@""T````,9@1Z`6!L2'@#[B\*3KH1F"P`4$]F"$ZZ$6PZU
M`&!42'@`(4AZ`)).NA(T+@!03V<*+P=.NA'>6$]@'DAX``%(>@""+P9.NA&B\
M2'C__T*G+P9.NA%X3^\`&&`F,"T`#,!\!0"P?`4`9A@O!DZZ$.QZ!%A/.46&$
M%'#_3-\,\$Y=3G4P!,'\``8GA@@`,`3!_``&($#1RS%M``P`!`@M``,`#&<0@
M2'@``4*G+P9.NA$>3^\`##`$8,)D;W,N;&EB<F%R>0```$Y5``!(YPP@."T`"
M"$ZZ#J8P!,'\``8D0-7LAA9*1&T*N&R#*&P$2I)F$#E\``*&%'#_3-\$,$Y=<
M3G4P*@`$P'P``[!\``%F"CE\``6&%'#_8.!P`#`M``XO`"\M``HO$DZZ$)8J)
M`+"\_____T_O``QF#$ZZ$$PY0(84</]@M"`%8+!A<$/L@S)%[(,RM<EF#C(\.
M`,MK"'0`(L)1R?_\*4^&&BQX``0I3H8>2.>`@`@N``0!*6<02_H`"$ZN_^)@:
M!D*G\U].<T/Z`"!.KOYH*4"&(F8,+CP``X`'3J[_E&`$3KH`&E!/3G5D;W,NC
M;&EB<F%R>0!)^0``?_Y.=4Y5```O"DAY``$``#`L@RC!_``&+P!.NA!&*4"&;
M%E!/9A1"ITAY``$``$ZZ$`I03RYLAAI.=2!LAA9":``$(&R&%C%\``$`$"!L@
MAA8Q?``!``H@;(8:("R&&I"H``10@"E`AB8@;(8F(+Q-04Y80J=.N@_Z)$!*]
MJ@"L6$]G+B\M``PO+0`(+PI.N@"N.7P``88J(&R&%@!H@```!"!LAA8`:(``7
M``I/[P`,8$)(:@!<3KH0%$AJ`%Q.N@_6*4"&+"!LABQ*J``D4$]G$"!LABPB0
M:``D+Q%.N@[,6$\O+(8L+PI.N@,N*6R&+(8P4$].N@[,(&R&%B"`3KH.^B!LV
MAA8A0``&9Q9(>`/M2'H`*DZZ#M8@;(86(4``#%!/+RR&,#\LAC1.NO/80F=.\
MN@SF4$\D7TY=3G4J`$Y5``!(YPPP)&T`$"!M``A*J`"L9Q@@;0`(("@`K.6`U
M*``@1"`H`!#E@"9`8`0F;(,J$!-(@$C`T*T`#%2`.4"&-D*G,"R&-DC`+P!.=
MN@[8*4"&.%!/9@A,WPPP3EU.=1`32(`Z`#\%($M2B"\(+RR&.$ZZ`7XP!4C`K
M($#1[(8X0_H!1!#99OP_+0`.+PHO+(8X3KH!.B!LACA",%``.7P``88T,`5(P
MP-"LAC@F0%*+)$M/[P`4$!-(@#H`L'P`(&<8NGP`"6<2NGP`#&<,NGP`#6<&+
MNGP`"F8$4HM@V`P3`"!M>@P3`")F+E*+($M2BQ`02(`Z`&<>($I2BA"%NGP`7
M(F80#!,`(F8$4HM@!D(J__]@`F#68#@@2U*+$!!(@#H`9R:Z?``@9R"Z?``)S
M9QJZ?``,9Q2Z?``-9PZZ?``*9P@@2E**$(5@SB!*4HI"$$I%9@)3BU)LAC1@S
M`/]:0A)"IS`LAC120$C`Y8`O`$ZZ#;8I0(8P4$]F"$)LAC1@`/[8>@`F;(8XY
M8"0P!4C`Y8`@;(8P(8L(`"!+(`A*&&;\D<!3B#`(4D!(P-?`4D6Z;(8T;=8PR
M!4C`Y8`@;(8P0K`(`&``_I0@`#`\?_]@!#`O``P@;P`$2AAF_%-((F\`"%-`7
M$-E7R/_\9P)"$"`O``1.=4SO`P``!"`(,B\`#&`"$-E7R?_\9P9206`"0AA1>
MR?_\3G5.;R!E<G)O<@!&:6QE(&YO="!F;W5N9`!"860@9FEL92!H86YD;&4`.
M26YS=69F:6-I96YT(&UE;6]R>0!&:6QE(&5X:7-T<P!);G9A;&ED(&9U;F-TG
M:6]N(&YU;6)E<@!4;V\@;6%N>2!O<&5N(&9I;&5S`$YO="!A(&-O;G-O;&4@@
M9&5V:6-E`$EN=F%L:60@86-C97-S(&-O9&4`4F5S=6QT('1O;R!L87)G90!!A
M<F=U;65N="!O=70@;V8@9&]M86EN``!.50``2.<.,"1M``A"ITAZ`(Y.N@QVS
M*4"&/%!/9@A,WPQP3EU.=2!M``PB:``D+RD`!$ZZ#*8H`%A/9U)(>@!M($0OT
M*``V3KH,>"9`2H!03V<T2'@#[2\+3KH+>BP`4$]G)"`&Y8`J`"!%)6@`"`"DT
M)48`G$AX`^U(>@`X3KH+5B5``*!03R\$3KH,1%A/+RR&/$ZZ"ZA"K(8\6$]@_
M@&EC;VXN;&EB<F%R>0!724Y$3U<`*@!.50``2&T`#"\M``A(>@1@3KH`F$_O:
M``Q.74YU3E4``$CG""`D;0`.#&T`!``29@@@;0`(*!!@'$IM``QO#"!M``AP<
M`#`0*`!@"B!M``@P$$C`*`!";0`22FT`#&P01&T`#$J$;`A$A#M\``$`$C(M'
M``Q(P2`$3KH#D$'L@-Q3BA2P```R+0`,2,$@!$ZZ`X8H`&;:2FT`$F<&4XH4V
MO``M(`I,WP003EU.=4Y5_R)(YP@P)&T`""9M``Q";?_Z*VT`$/_\($M2BQ`06
M2(`X`&<``NZX?``E9@`"S$(M_S`[?``!__@[?``@__8[?"<0__0@2U*+$!!(.
M@#@`L'P`+68.0FW_^"!+4HL0$$B`.`"X?``P9A`[?``P__8@2U*+$!!(@#@`Q
MN'P`*F88(&W__%2M__P[4/_R($M2BQ`02(`X`&`R0FW_\F`<,"W_\L'\``K07
M1)!\`#`[0/_R($M2BQ`02(`X`#`$4D!![(#N"#```@``9M2X?``N9EH@2U*+3
M$!!(@#@`L'P`*F88(&W__%2M__P[4/_T($M2BQ`02(`X`&`R0FW_]&`<,"W_J
M],'\``K01)!\`#`[0/_T($M2BQ`02(`X`#`$4D!![(#N"#```@``9M0[?``"O
M__"X?`!L9A(@2U*+$!!(@#@`.WP`!/_P8!"X?`!H9@H@2U*+$!!(@#@`,`1(Y
MP&!Z.WP`"/_N8!8[?``*_^Y@#CM\`!#_[F`&.WS_]O_N/RW_\$AM_S`_+?_NC
M+RW__$ZZ_>0K0/_J,"W_\$C`T:W__$_O``Q@7"!M__Q8K?_\(E`K2?_J(`E*Q
M&6;\D\!3B3M)__!@2B!M__Q4K?_\.!!![?\O*TC_ZA"$8"B0O````&-GXE.`R
M9Y*0O`````MG`/]R68!GLE6`9P#_<%>`9P#_<F#,0>W_,)'M_^H[2/_P,"W_R
M\+!M__1O!CMM__3_\$IM__AG:"!M_^H,$``M9PH@;?_J#!``*V8N#&T`,/_V;
M9B93;?_R(&W_ZE*M_^H0$$B`/P!.DK!\__]43V8*</],WPP03EU.=6`6/RW_D
M]DZ2L'S__U1/9@1P_V#D4FW_^C`M__)3;?_RL&W_\&[<0FW_[F`@(&W_ZE*M>
M_^H0$$B`/P!.DK!\__]43V8$</]@L%)M_^X@;?_J2A!G"C`M_^ZP;?_T;<XP8
M+?_NT6W_^DIM__AF*&`8/SP`($Z2L'S__U1/9@9P_V``_WA2;?_Z,"W_\E-M%
M__*P;?_P;MI@%C\$3I*P?/__5$]F!G#_8`#_4E)M__I@`/T(,"W_^F``_T)(3
MYT@`0H1*@&H$1(!21$J!:@9$@0I$``%A/DI$9P)$@$S?`!)*@$YU2.=(`$*$W
M2H!J!$2`4D1*@6H"1(%A&B`!8-@O`6$2(`$B'TJ`3G4O`6$&(A]*@$YU2.<PX
M`$A!2D%F($A!-@$T`$)`2$"`PR(`2$`R`H+#,`%"04A!3-\`#$YU2$$F`2(`'
M0D%(04A`0D!T#]"`TX&V@6($DH-20%'*__),WP`,3G5.50``2&R!AC\M``A.G
MN@`(7$].74YU3E4``"\$."T`""\M``H_!$ZZ`#"X?``*7$]F)"!M``H0*``,-
M2(`(```'9Q0_//__+RT`"DZZ`/1<3R@?3EU.=6#X3E4``"\*)&T`"B!2L>H`I
M!&48,"T`",!\`/\_`"\*3KH`R%Q/)%].74YU(%)2DA`M``D0@$B`P'P`_V#H\
M3E4``"\*0>R!<"1(($K5_````!8O"&$06$]![(,HM<AEZB1?3EU.=4Y5``!(&
MYP@@)&T`"'@`(`IF"G#_3-\$$$Y=3G5**@`,9U`(*@`"``QG##\\__\O"F%2E
M.`!<3Q`J``U(@#\`3KH%'(A`""H``0`,5$]G"B\J``A.N@(N6$\(*@`%``QG?
M$B\J`!).N@+`+RH`$DZZ`A103T*20JH`!$*J``A"*@`,,`1@D$Y5__Y(YP@@Z
M)&T`"$'Z_T8I2(9`""H`!``,9PIP_TS?!!!.74YU""H``@`,9S`@4I'J``@XC
M"#\$+RH`"!`J``U(@#\`3KH"@+!$4$]G$`CJ``0`#$*20JH`!'#_8,`,;?__:
M``QF$`BJ``(`#$*20JH`!'``8*A*J@`(9@@O"DZZ`)I83PQJ``$`$&8J&VT`)
M#?__/SP``4AM__\0*@`-2(`_`$ZZ`B*P?``!4$]FH#`M``Q@`/]J)*H`"#`JH
M`!!(P-"J``@E0``$".H``@`,(%)2DA`M``T0@$B`P'P`_V``_SY.50``+PI!P
M[(%P)$A**@`,9QC5_````!9![(,HM<AE"'``)%].74YU8.)"DD*J``1"J@`(,
M(`I@ZDY5__PO"B1M``@_/`0`3KH`P"M`__Q43V88-7P``0`0($K1_`````XE>
M2``()%].74YU-7P$```0".H``0`,)6W__``($"H`#4B`/P!.N@#B2D!43V<&L
M`"H`@``,8,Y.50``2.<`,"1L@S9@%"92("H`!%"`+P`O"DZZ!'A03R1+(`IF#
MZ$*L@S9,WPP`3EU.=4Y5```O"D'Z_\8I2(9$0J<@+0`(4(`O`$ZZ!"8D0$J`H
M4$]F"'``)%].74YU)*R#-B5M``@`!"E*@S8@"E"`8.9.50``<``P+0`(+P!A)
MLEA/3EU.=4Y5``!(YP`PE\LD;(,V8`X@;0`(48BQRF<2)DHD4B`*9NYP_TS?"
M#`!.74YU(`MG!":28`0I4H,V("H`!%"`+P`O"DZZ`\IP`%!/8-A.50``+PHP.
M+0`(P?P`!B1`U>R&%DIM``AM#C`M``BP;(,H;`1*DF8..7P``H84</\D7TY=-
M3G4P+0`(P?P`!B!LAA8O,`@`3KH"QDJ`6$]G!'`!8`)P`&#83E4``"\M``A.V
MN@*02H!83V8.3KH"FCE`AA1P_TY=3G5P`&#X3E4``$CG#"`X+0`(3KH`<#`$D
MP?P`!B1`U>R&%DI$;0JX;(,H;`1*DF80.7P``H84</],WP0P3EU.=3`J``3`%
M?``#9@HY?``%AA1P_V#D<``P+0`.+P`O+0`*+Q).N@*0*@"PO/____]/[P`,=
M9@Q.N@(:.4"&%'#_8+@@!6"T3E7__$AX$`!"ITZZ`O0K0/_\"```#%!/9Q)*:
M;(8J9@@@+?_\3EU.=4ZZ``9P`&#T3E4``$AX``1(>@`<3KH!_B\`3KH"+#\\^
M``%.N@`.3^\`#DY=3G5>0PH`3E4``$JLAD!G!B!LAD!.D#\M``A.N@`(5$].M
M74YU3E7__"\$,"T`"$C`*T#__$JLAA9G*'@`8`H_!$ZZ`/Y43U)$N&R#*&WPU
M,"R#*,'\``8O`"\LAA9.N@(64$]*K(9$9P8@;(9$3I!*K(,N9PHO+(,N3KH!]
MDEA/2JR&2&<((&R&2""LADQ*K(909PHO+(903KH!KEA/2JR&5&<*+RR&5$ZZN
M`9Y83TJLAEAG"B\LAEA.N@&.6$]*K(9<9PHO+(9<3KH!?EA/+'@`!`@N``0!9
M*6<4+PU+^@`*3J[_XBI?8`9"I_-?3G-*K(8L9C!*K(8X9R@P+(8V2,`O`"\LB
MACA.N@%N,"R&-%)`2,#E@"\`+RR&,$ZZ`5I/[P`08`Y.N@%(+RR&+$ZZ`718<
M3R`M__PN;(8:3G4H'TY=3G5.50``2.<.(#@M``@P!,'\``8D0-7LAA9*1&T*S
MN&R#*&P$2I)F$#E\``*&%'#_3-\$<$Y=3G4(*@`'``1F""\23KH`"EA/0I)P'
M`&#B(B\`!"QLAB).[O_<(B\`!"QLAB).[O^"(B\`!"QLAB).[O^X+&R&(D[N$
M_\HL;(8B3N[_?"(O``0L;(8B3N[_*$SO``8`!"QLAB).[O^L3.\`!@`$+&R&'
M(D[N_^(L;(8B3N[_Q$SO``X`!"QLAB).[O_63.\`#@`$+&R&(D[N_[Y.^@`"L
M(B\`!"QLAB).[O^F3.\`#@`$+&R&(D[N_]!(YP$$3.\@@``,+&R&'DZN_Y1,`
MWR"`3G5.^@`"(F\`!"QLAAY.[OYB3.\``P`$+&R&'D[N_SHB;P`$+&R&'D[NY
M_MHL;(8>3N[_?")O``0@+P`(+&R&'D[N_RX@;P`$+&R&'D[N_HPL;(8>(F\`^
M!"`O``A.[OW8(F\`!"QLAAY.[OZ&3.\``P`$+&R&'D[N_LX@;P`$+&R&'D[N.
M_H!,[P,```0L;(8\3N[_H"!O``0L;(8\3N[_IB!O``0L;(8\3N[_L@```^P`,
M```!`````0``#J8````````#\@```^H```#,__\````````"-@```CD```(\3
M```"/P```D(```)%```"2````DL```).```"40```E0```)7```"6@```ET`P
M``)@```"8P```F8```)I```";````F\```)R```"=0```G@```)[```"?@``;
M`H$```*$```"AW(``````'(K`````G<````#`7<K```#`F$````)`6$K```)%
M`G@````%`7@K```%`@``````````$?@``!(!```2$```$B```!(T```20```R
M$E@``!)L```2@0``$I4``!*F``LP,3(S-#4V-S@Y86)C9&5F````("`@("`@'
M("`@,#`P,#`@("`@("`@("`@("`@("`@(""00$!`0$!`0$!`0$!`0$!`#`P,$
M#`P,#`P,#$!`0$!`0$`)"0D)"0D!`0$!`0$!`0$!`0$!`0$!`0$!`4!`0$!`>
M0`H*"@H*"@("`@("`@("`@("`@("`@("`@("0$!`0"``````````````````$
M`0`````!``````````````````````$!`````0`````````````````````!&
M`@````$`````````````````````````````````````````````````````#
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M````````````````````````````````%``````````````#[````"<`````J
M````!@````H````.````$@```!8````:````'@```"(````F````*@```"X`>
M```R````-@```#H````^````0@```$8```!*````3@```%(```!6````6@``"
M`%X```!B````9@```&H```!N````<@```*P```"P````M````+@```"\````T
IP````,0```#(````S````-````#4`````````_(```/K`````0```_*\1
``
end
size 9176
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.