[net.sources] ncode.c

good@pixar (Life is hard. And then you drive. -- Ferrari billboard) (08/25/86)

Here it is, the program that lets me choose all those goofy $NAMEs that show
up after my name.  It's also good for generating poetry, rumors, lightbulb
jokes, and even the names of high-tech companies.  Enjoy.

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	README
#	ncode.c
#	ncode.1
#	ncode.sample
# This archive created: Sun Aug 24 23:44:55 1986
# By:	pixar!good ()
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'README'
then
	echo shar: "will not over-write existing file 'README'"
else
cat << \SHAR_EOF > 'README'
You should now have README, ncode.c, ncode.1 and ncode.sample.

This source is known to compile and run on 4.2 BSD.  With the possible
exception of the calls to srandom() and random() I suspect the rest is
very portable.  Compile it "cc ncode.c -O -o ncode" and then try

	ncode ncode.sample

For more fun, try

	ncode -n 10 ncode.sample

And for a good education say

	ncode -g "I work for CODE\"

Then you might want to read the man page, ncode.1, to learn about how it
works.  You can make it pretty with "nroff -man ncode.1".  If all of this
leaves you hopelessly confused, send mail to {sun,ucbvax}!pixar!good and
I'll send you more confusing sample files.  If I feel like it.


		--Craig
		...{ucbvax,sun}!pixar!good

SHAR_EOF
fi
if test -f 'ncode.c'
then
	echo shar: "will not over-write existing file 'ncode.c'"
else
cat << \SHAR_EOF > 'ncode.c'
/*
 *	ncode.c --  a random text constructor
 *	pixar!good
 *	based on a story by pixar!mark
 */

#include <stdio.h>

#define DATALEN		4096	/* max number of lines allowed in input file */
#define BUFSIZ		2048	/* max number of chars allowed in a line */

char *data[DATALEN]; /* array of pointers to strings from file, one line each */

char *malloc();
char *rindex();
long random();

struct gstruct {
	char *name;	/* points to name of a group in data[] */
	int count;	/* how many elements of data[] belong to this group */
	int index;	/* index of element of data[] where group starts */
};
struct gstruct groups[DATALEN];
int ngroups;			/* number of elements in groups[] */
int findex;			/* number of elements loaded into data[] */

main (ac,av)
int ac;
char *av[];
{
	char *prog, *fname = 0;
	int howmany = 1;	/* times through main loop */
 	char *groupname = "CODE";

	prog = rindex(*av,'/');			/* name of program */
	prog = ( prog == NULL ) ? *av : ++prog ;
	ac--;av++;
	while(ac && **av == '-'){
		if (strcmp(*av,"-n") == 0){
			ac--;av++;
			howmany = atoi(*av);
			if (howmany <= 0){
				fprintf(stderr,
					"%s: -n: need positive integer\n",prog);
				exit(1);
			}
			ac--;av++;
		} else if (strcmp(*av,"-g") == 0){
			ac--;av++;
			groupname = *av;	/* use instead of "CODE" */
			if (! groupname ){
				fprintf(stderr,
					"%s: -g: need group name\n",prog);
				exit(1);
			}
			ac--;av++;
		} else {
			printf(
			"Usage %s [-n n ] [-g groupname] codefile\n",prog);
			exit(0);
		}
	}
	if (!ac){
		fprintf(stderr,
			"Usage %s [-n n ] [-g groupname] codefile\n",prog);
		exit(1);
	}
	fname = *av;

	findex = 0;
	if( init(fname) != 0 ){
		fprintf(stderr,"%s: init error\n",prog);
		exit(1);
	}
	if ( scan() != 0 ){
		fprintf(stderr,"%s: scan error\n",prog);
		exit(1);
	}
	srandom(getpid());	/* seed the number generator */
	while ( howmany ){
		expand(groupname,strlen(groupname));
		howmany--;
	}
}

init(fname)
char *fname;
{
	FILE *fp;
	char buf[BUFSIZ];
	char *s;

	/*
	 *	Read whole file into data[]
	 */
	fp = fopen(fname,"r");
	if ( fp == NULL ) {
		perror(fname);
		return 1;
	}
	while ( fgets(buf,BUFSIZ,fp) != NULL ){
		if ( findex > DATALEN ){
			fprintf(stderr,"init: findex reached %d\n",findex);
			return 1; /* no point in going on */
		}
		for(s=buf;*s!='\0';s++)		/* nuke newline */
			if(*s=='\n')
				 *s='\0';
		if( buf[0] == '#' && (strncmp(buf,"#include",8)==0) ){
			for(s= &buf[8]; *s!='\0' && (*s==' '||*s=='\t');s++)
				;	/* skipping white space */
			if (init(s) != 0){ /* expects a path name */
				return 1;
			}
			continue;	/* don't put in the #include line! */
		}
		data[findex]=(char *)malloc(strlen(buf)+1);
		if (data[findex] != NULL)
			strcpy(data[findex], buf);
		else {
			printf("init: bad malloc\n");
			return 1;	/* something went wrong with malloc */
		}
		findex++;
	}
	fclose(fp);
	return 0;
}

/*
 *	Scan data[] marking and counting groups
 */
scan()
{
	register i, gcnt, gindex;

	/* special case: first line always a group name */
	groups[0].name = data[0];
	groups[0].index = 0;
	ngroups = 1;
	i = 1;
	gindex = 0;
	gcnt = 0;
	while ( i < findex ){
		if ( data[i][0] == '%' ){
			groups[gindex].count = gcnt;
			gcnt = 0;		/* close out prev group */
			ngroups++;
			i++;		/* start next group */
			gindex++;
			groups[gindex].name = data[i];
			groups[gindex].index = i;
		}else{
			gcnt++;
		}
		i++;
	}
	return 0;
}

expand(s,lim)
char s[];
int lim;
{
	register i, j, k, done, n, r;

	i = j = 0;
	while ( s[i] != 0 && i < lim ){
		done = 0;
		while ( ! done && j <= lim ){
			if ( isawhite(s[j]) ){
				/* chase down remaining white space */
				for (k=j; k<=lim && s[k] && isawhite(s[k]);k++){
					;
				}
				n = isagroup(&s[i], j-i);
				if ( n >= 0 ){
					r = (groups[n].index + 1
						+ rnd(groups[n].count));
					expand( data[r], strlen(data[r]));
					outstring(&s[j], k-j);
				} else {
					outstring(&s[i], k-i);
				}
				done++;
				i = j = k; /* should be on next word, if any */
			}
			j++;
		}
	}


}

/*
 *	Return index into groups[] array if a group name, -1 if just a word
 */
isagroup(s,lim)
char s[];
int lim;
{
	register i;
	static char gbuf[BUFSIZ];

	strncpy(gbuf,s,lim);
	gbuf[lim] = '\0';	/* strncpy might not do this */
	for(i=0; i<=ngroups; i++ ){
		if (groups[i].name && strcmp(gbuf,groups[i].name) == 0){
			return i;	/* hit */
		}
	}
	return -1;	/* fail */
}

/*
 * 	Output string, handling splices
 */
outstring(s,lim)
char s[];
int lim;
{
	register i = 0;

	while ( s[i] != '\0' && i < lim ){
		switch (s[i]){
		case '|':
				break;	/* splice: no output */
		case '\\':
				putchar('\n');
				break;
		default:
				putchar(s[i]);
				break;
		}
		i++;
	}
}

/*
 *     Return random number 0 to limit
 */
rnd(limit)
int limit;
{
	int i;

	if (limit > 0){
		return (random() % limit);
	}
	return 0;	/* better than a floating exception if lim == 0 */
}

/*
 *	Return 1 if one of our "white" characters.  A white character is
 *	any character which can bound a group name, so punctuation marks
 *	are included.
 */
isawhite(c)
char c;
{
	if (	c == '\0' ||		/* traditional white space */
		c == ' ' ||
		c == '\t' ||
		c == '|' ||		/* "splice" character */
		c == '\\' ||		/* becomes a newline */
		c == '.' ||		/* common punctuation */
		c == '-' ||
		c == ':' ||
		c == ';' ||
		c == ',' ||
		c == '!' ||
		c == '?' ||
		c == '[' ||
		c == ']' ||
		c == '{' ||
		c == '}' ||
		c == '(' ||
		c == ')' ||
		c == '\'' ||
		c == '\"' ||
		c == '`'
	)
		return 1;
	return 0;
}
SHAR_EOF
fi
if test -f 'ncode.1'
then
	echo shar: "will not over-write existing file 'ncode.1'"
else
cat << \SHAR_EOF > 'ncode.1'
.TH NCODE 1 "Pixar"	
.SH NAME
ncode  - stochastic text construction
.SH SYNOPSIS
.B ncode [-n number] [-g groupname] codefile
.SH DESCRIPTION
.I Ncode
reads in a file of a certain format and randomly constructs text based on
the organization of the file.  Other files may be recursively included by
putting

#include pathname

on any line of the file.  This is useful when
you want to use a file of basic definitions, or groups, in different
configurations.

The -n flag is used to run the program through the main loop multiple times.

A "group" name is defined as the word on the first line of the file and, more
commonly, the word on each line following a line starting with "%".  The members
of a group are all lines between the group name and the next "%".  When a
group name is encountered, surrounded by any of a set of characters called
"white space" in this context, it is randomly expanded into one of its members.
Group names are not allowed to contain any white space, to prevent terminal
confusion on the part of the program.

The -g flag allows you to start the expanding process on a group name other
than the default, which is "CODE".  The argument may be a group name, or an
entire string including group names, just as if it were a line in the file.
It is legal to start on any group in the file, and groups may be referenced
before or after the place in the file where they are defined.  In the case of
duplicate group definitions, the first one occurring is the only one used.

For example, here is a sample group definition:

.nf
	NOUN
	lamp
	house
	car
	%
.fi

The line "See the NOUN." could be randomly expanded to say "See the lamp."

The characters considered "white" for the purpose of bounding a group name,
besides what is normally considered white space, are currently: 

	| \\ .  - : ; , ! ? [ ] { } () ' " `

Two of those characters have special meanings to
.I ncode.
The "|" symbol allows you to "splice" things to a group name.  When it is
encountered, no character is printed on output.  The "\\" causes a newline
to be printed on output.

The simplest application would be for a "fortune" program, but
.I ncode
could also be used for more complex things such as a rumor generating file.
The group definitions will be left as an exercise for the reader, but the
following example should prove illuminating:

.nf
CODE
It was rumored today that COMPANY will be bought by COMPANY for PRICE\\.
PERSON, POSITION of COMPANY, said that PRODUCT will be announced DATE\\.
.fi

Note that every string to be expanded must be on only one line of the file.
Very long lines, currently to 2048 characters, are allowed.  The maximum number
of total lines in the file and all #include files is currently 4096.  If you
want to randomly generate the Great American Novel in one pass you'll have
to change two #define lines in the source.
.SH BUGS
No bugs.  Only features that you haven't figured out how to use yet.
.SH DIAGNOSTICS
Standard perror() stuff.  Pretty self explanatory.  A bogus input file might
benignly yield cryptic results.
.SH AUTHOR
Craig Good
SHAR_EOF
fi
if test -f 'ncode.sample'
then
	echo shar: "will not over-write existing file 'ncode.sample'"
else
cat << \SHAR_EOF > 'ncode.sample'
PRE
Zy
Cryo
Iso
Isa
Vidi
Tera
Micro
Video
Dyna
Amphi
Omni
Tele
Graphi
Uni
Multi
Pixa
Mega
Nano
Techni
Giga
Para
Geo
Compu
Xeno
Digi
Intel
Meta
Robo
Intelli
Ray
Ortho
Electro
Aero
Pyra
Centi
Deci
Milli
Data
Termi
%
SUF
tex
tron
tronic
meter
science
test
systems
sonic
sonics
sys
fonics
phon
phonics
thon
theon
con
sat
vac
matics
dyne
mips
matic
tek
tech
corp
plex
tel
set
vision
point
%
CORP
Corp.
Corp.
Inc.
Inc.
Inc.
Ltd.
Ltd.
GmBH
%
THINGS
Scientific
Industries
Systems
Information Systems
Graphics
Technologies
High Technology
Advanced Technologies
Products
Manufacturing
Propulsion
Cryogenics
%
LTR
A
A
A
A
A
B
C
D
E
E
E
E
E
F
G
G
H
I
I
I
I
I
J
K
L
M
N
O
O
O
O
O
P
P
Q
R
S
T
U
U
U
U
U
V
W
X
X
X
X
X
X
Y
Y
Y
Y
Z
Z
Z
%
CODE
LTR|LTR|LTR, CORP\
LTR|LTR|LTR PRE|SUF, CORP\
LTR|LTR|LTR THINGS, CORP\
PRE|SUF\
PRE|SUF\
PRE|SUF THINGS\
PRE|SUF THINGS, CORP\
%
SHAR_EOF
fi
exit 0
#	End of shell archive
-- 
		--Craig
		...{ucbvax,sun}!pixar!good