LIFE - fancy new version (part 2 of 4)

David I. Bell dbell at daisy.UUCP
Sun Feb 3 14:49:33 AEST 1985


#---Cut here and place in it's own directory, then feed to Bourne shell---
# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
# This archive contains:
#   alloc.c (2055 chars)
#   cell.c (3720 chars)
#   cmd.c (14613 chars)
#   cmdl.c (11895 chars)
#   debug.c (1295 chars)
#   gen.c (4492 chars)
#   io.c (16638 chars)
#
echo x - alloc.c
sed -e 's/^X//' > "alloc.c" << '//E*O*F alloc.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)alloc.c	1.5	2/1/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#include "life.h"
X
X
X/*
X * Allocate a new object structure.  It contains one row element, the termrow.
X */
Xstruct object *
Xallocobject()
X{
X	register struct	object	*obj;
X
X	obj = freeobjects;
X	if (obj)
X		freeobjects = obj->o_next;
X	else {
X		obj = newobjects;
X		if (obj >= endobjects) {	/* allocate new storage */
X			obj = (struct object *)
X				sbrk(sizeof(struct object) * ALLOCOBJ);
X			if ((int)obj == -1) {
X				perror("sbrk");
X				exit(1);
X			}
X			newobjects = obj;
X			endobjects = obj + ALLOCOBJ;
X		}
X		newobjects++;
X	}
X	obj->o_next = NULL;
X	obj->o_firstrow = termrow;
X	obj->o_lastrow = NULL;
X	obj->o_reserved = reserve;
X	obj->o_name[0] = '\0';
X	obj->o_count = 0;
X	obj->o_gen = 0;
X	obj->o_lock = 0;
X	obj->o_currow = 0;
X	obj->o_curcol = 0;
X	obj->o_prow = 0;
X	obj->o_pcol = 0;
X	obj->o_mark = 0;
X	obj->o_autoscale = 0;
X	setscale(obj, 1);
X	return(obj);
X}
X
X
X/*
X * Allocate a new row structure.  It contains one row element, the termcell.
X */
Xstruct row *
Xallocrow()
X{
X	register struct	row	*rp;
X
X	rp = freerows;
X	if (rp)
X		freerows = rp->r_next;
X	else {
X		rp = newrows;
X		if (rp >= endrows) {		/* allocate new storage */
X			rp = (struct row *) sbrk(sizeof(struct row) * ALLOCROW);
X			if ((int)rp == -1) {
X				perror("sbrk");
X				exit(1);
X			}
X			newrows = rp;
X			endrows = rp + ALLOCROW;
X		}
X		newrows++;
X	}
X	rp->r_next = NULL;
X	rp->r_firstcell = termcell;
X	rp->r_lastcell = NULL;
X	rp->r_count = 0;
X	return(rp);
X}
X
X
X/* Allocate a new cell structure */
Xstruct cell *
Xalloccell()
X{
X	register struct	cell	*cp;
X
X	cp = freecells;
X	if (cp) {
X		freecells = cp->c_next;
X		cp->c_next = NULL;
X		cp->c_marks = MARK_ANY;
X		return(cp);
X	}
X	cp = newcells;
X	if (cp >= endcells) {		/* allocate new storage */
X		cp = (struct cell *) sbrk(sizeof(struct cell) * ALLOCCELL);
X		if ((int)cp == -1) {
X			perror("sbrk");
X			exit(1);
X		}
X		newcells = cp;
X		endcells = cp + ALLOCCELL;
X	}
X	newcells++;
X	cp->c_next = NULL;
X	cp->c_marks = MARK_ANY;
X	return(cp);
X}
//E*O*F alloc.c//
echo x - cell.c
sed -e 's/^X//' > "cell.c" << '//E*O*F cell.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)cell.c	1.3	2/1/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#include "life.h"
X
X
X/*
X * Return a row structure for a given row number, returning NULL if not there.
X */
Xstruct row *
Xfindrow(obj, row)
X	struct	object	*obj;
X	register int	row;
X{
X	register struct	row	*rp;
X
X	for (rp = obj->o_firstrow; (row > rp->r_row); rp = rp->r_next) ;
X	if (rp->r_row != row) return(NULL);
X	return(rp);
X}
X
X
X/*
X * Return a row structure for a given row number, creating it if needed.
X */
Xstruct row *
Xgetrow(obj, row)
X	register struct	object	*obj;
X	register int	row;
X{
X	register struct	row	*rp;		/* current row */
X	register struct	row	*nrp;		/* next row */
X	register struct	row	*prp;		/* previous row */
X
X	rp = obj->o_firstrow;
X	if (row < rp->r_row) {			/* at front */
X		nrp = allocrow();
X		nrp->r_row = row;
X		nrp->r_next = obj->o_firstrow;
X		obj->o_firstrow = nrp;
X		if (nrp->r_next == termrow) obj->o_lastrow = nrp;
X		return(nrp);
X	}
X	if (row >= obj->o_lastrow->r_row) rp = obj->o_lastrow;
X	while (row > rp->r_row) {
X		prp = rp;
X		rp = rp->r_next;
X	}
X	if (row == rp->r_row) {
X		return(rp);
X	}
X	nrp = allocrow();
X	nrp->r_row = row;
X	nrp->r_next = rp;
X	prp->r_next = nrp;
X	if (nrp->r_next == termrow) obj->o_lastrow = nrp;
X	return(nrp);
X}
X
X
X/*
X * Find a cell given its coordinates, returning NULL if not found.
X */
Xstruct cell *
Xfindcell(obj, row, col)
X	register struct object	*obj;
X	register int	col;
X{
X	register struct	row	*rp;
X	register struct	cell	*cp;
X
X	rp = findrow(obj, row);
X	if (rp == NULL) return(NULL);
X	for (cp = rp->r_firstcell; col > cp->c_col; cp = cp->c_next) ;
X	if (col != cp->c_col) return(NULL);
X	return(cp);
X}
X
X
X/*
X * Create a cell at a given row and column.  Returns nonzero if the cell
X * already existed.  If the cell is new, it is marked with the current
X * mark value of the object.
X */
Xaddcell(obj, row, col)
X	struct	object	*obj;
X	register int	col;
X{
X	register struct	row	*rp;
X	register struct	cell	*cp;		/* current cell */
X	register struct	cell	*ncp;		/* next cell */
X	register struct	cell	*pcp;		/* previous cell */
X
X	rp = getrow(obj, row);
X	cp = rp->r_firstcell;
X	if ((cp != termcell) && (col >= rp->r_lastcell->c_col)) {
X		pcp = rp->r_lastcell;			/* at end */
X		if (col == pcp->c_col) return(1);
X		ncp = alloccell();
X		ncp->c_col = col;
X		ncp->c_marks |= obj->o_mark;
X		ncp->c_next = termcell;
X		pcp->c_next = ncp;
X		rp->r_lastcell = ncp;
X		rp->r_count++;
X		obj->o_count++;
X		return(0);
X	}
X	if (col < cp->c_col) {
X		ncp = alloccell();			/* at front */
X		ncp->c_col = col;
X		ncp->c_marks |= obj->o_mark;
X		ncp->c_next = cp;
X		rp->r_firstcell = ncp;
X		if (cp == termcell) rp->r_lastcell = ncp;
X		rp->r_count++;
X		obj->o_count++;
X		return(0);
X	}
X	while (col > cp->c_col) {			/* in middle */
X		pcp = cp;
X		cp = pcp->c_next;
X	}
X	if (col == cp->c_col) {
X		return(1);
X	}
X	ncp = alloccell();
X	ncp->c_col = col;
X	ncp->c_marks |= obj->o_mark;
X	ncp->c_next = cp;
X	pcp->c_next = ncp;
X	if (cp == termcell) rp->r_lastcell = ncp;
X	rp->r_count++;
X	obj->o_count++;
X	return(0);
X}
X
X
X/*
X * Delete a cell at a given coordinate.  Returns nonzero if it did not exist.
X */
Xdelcell(obj, row, col)
X	register struct	object	*obj;
X	register int	col;
X{
X	register struct	row	*rp;
X	register struct	cell	*pcp;		/* previous cell */
X	register struct	cell	*cp;		/* current cell */
X
X	rp = findrow(obj, row);
X	if (rp == NULL) return(1);
X	pcp = NULL;
X	cp = rp->r_firstcell;
X	while (col > cp->c_col) {
X		pcp = cp;
X		cp = cp->c_next;
X	}
X	if (col != cp->c_col) return(1);
X	if (pcp)
X		pcp->c_next = cp->c_next;
X	else
X		rp->r_firstcell = cp->c_next;
X	if (cp->c_next == termcell) rp->r_lastcell = pcp;
X	cp->c_next = freecells;
X	freecells = cp;
X	rp->r_count--;
X	obj->o_count--;
X	return(0);
X}
//E*O*F cell.c//
echo x - cmd.c
sed -e 's/^X//' > "cmd.c" << '//E*O*F cmd.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)cmd.c	1.21	2/2/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#include "life.h"
X
X
X/*
X * Read commands if available, and execute them.  Since we call scanchar for
X * our characters, the code after the setjmp can be reentered many times in
X * order to finish any command.  This allows commands to be typed without
X * stopping the computation of generations of an object, and allows editing
X * of partially completed commands.
X */
Xdocommand()
X{
X	register int	defarg;		/* first argument defaulted to one */
X	register int	ch;		/* character read */
X	register struct	object	*obj;	/* object being manipulated */
X	int	arg1;			/* first command argument */
X	int	arg2;			/* second command argument */
X	int	got1;			/* got first argument flag */
X	int	got2;			/* got second argument flag */
X	char	*saveloopptr;		/* crock for loop definitions */
X
X	switch (setjmp(ttyjmp)) {
X		case SCAN_EDIT:		/* command edited before completion */
X			curinput->i_endptr = saveloopptr;
X			break;
X		case SCAN_EOF:		/* not yet enough chars for a command */
X			curinput->i_endptr = saveloopptr;
X			return;
X		case SCAN_ABORT:	/* normal command completion */
X		default:		/* normal entry point */
X			saveloopptr = curinput->i_endptr;
X			break;
X	}
X	if (stop) error("Command aborted");
X	obj = curobj;
X	cmark = 0;
X	arg2 = 0;
X	got2 = 0;
X	ch = readvalue(&arg1, &got1);
X	if (ch == ',') ch = readvalue(&arg2, &got2);
X	defarg = 1;
X	if (got1) defarg = arg1;
X	switch (ch) {
X#ifdef DEBUG
X		case '\005':		/* ^E - show debugging info */
X			dumpdata();
X			break;
X#endif DEBUG
X		case '\004':		/* ^D - end terminal input */
X			curinput->i_term(curinput);
X			break;
X		case '\014':		/* refresh screen */
X			dpyredraw();
X			redraw = 1;
X			break;
X		case '\n':		/* move to next "line" */
X			crow += defarg;
X			ccol = pcol;
X			update = 1;
X			break;
X		case '\t':		/* move to next tab stop */
X			while ((++ccol - pcol) % 8) ;
X			update = 1;
X			break;
X		case ESC:		/* execute a macro command */
X			ch = scanchar();
X			if (ch == ESC) break;	/* ignore double escape */
X			backup();
X			if (setmacro(arg1, arg2, ch)) error("Undefined macro");
X			update = 1;
X			break;
X		case ' ':		/* move to right */
X		case '.':
X			ccol += defarg;
X			update = 1;
X			break;
X		case ':':		/* execute line style command */
X		case ';':
X			dolinecommand(arg1, arg2, got1, got2);
X			break;
X		case '<':		/* begin loop or macro definition */
X			if (got1 || got2) {
X				if (got2) setloop(defarg, arg2, NULL);
X				else setloop(1, defarg, NULL);
X				update = 1;
X				break;
X			}
X			ch = scanchar();	/* defining macro */
X			if ((ch < 'a') || (ch > 'z')) {
X				error("Bad macro character");
X			}
X			setloop(1, 1, ch);
X			update = 1;
X			break;
X		case '>':		/* end loop */
X			endloop();
X			break;
X		case '+':		/* increment single char variable */
X			ch = scanchar();
X			if (ch == '$') ch = scanchar();
X			setvariable1(ch, getvariable1(ch) + defarg);
X			break;
X		case 'b':		/* move lower left with action */
X			domove(defarg, -defarg);
X			break;
X		case 'B':		/* shift to lower left */
X			doshift(defarg, -defarg);
X			break;
X		case 'c':		/* pick cell as current location */
X			crow = prow + arg1;
X			ccol = pcol + arg2;
X			update = 1;
X			break;
X		case 'd':		/* delete selection */
X			doselect(ch);
X			backup();
X			movemarkedobject(obj, deleteobject, MARK_CMD);
X			redraw = 1;
X			break;
X		case 'f':		/* flip selection */
X			doselect(ch);
X			cmark = MARK_USR;
X			flipmarkedobject(obj, MARK_CMD);
X			redraw = 1;
X			break;
X		case 'g':		/* compute generations */
X			if (obj->o_lock) error("Object locked");
X			if (genleft <= 0) backup();
X			genleft = (genleft ? arg1 : defarg);
X			freqcount = frequency;
X			update = 1;
X			break;
X		case 'G':		/* compute infinite generations */
X			if (obj->o_lock) error("Object locked");
X			if (genleft <= 0) backup();
X			genleft = INFINITY;
X			freqcount = frequency;
X			update = 1;
X			break;
X		case 'h':		/* move left with action */
X			domove(0, -defarg);
X			break;
X		case 'H':		/* shift left lots */
X			doshift(0, -defarg);
X			break;
X		case 'i':		/* toggle insert mode */
X			checkrun();
X			if (mode == M_MOVE) mode = M_INSERT;
X			else if (mode == M_INSERT) mode = M_DELETE;
X			else mode = M_MOVE;
X			update = 1;
X			break;
X		case 'j':		/* move down with action */
X			domove(defarg, 0);
X			break;
X		case 'J':		/* shift down lots */
X			doshift(defarg, 0);
X			break;
X		case 'k':		/* move up with action */
X			domove(-defarg, 0);
X			break;
X		case 'K':		/* shift up lots */
X			doshift(-defarg, 0);
X			break;
X		case 'l':		/* move right with action */
X			domove(0, defarg);
X			break;
X		case 'L':		/* shift right lots */
X			doshift(0, defarg);
X			break;
X		case 'm':		/* mark current object */
X			doselect(ch);
X			copymarks(obj, MARK_CMD, MARK_USR);
X			redraw = 1;
X			break;
X		case 'M':		/* remove all marks */
X			checkrun();
X			clearmarks(obj, MARK_SEE);
X			redraw = 1;
X			break;
X		case 'n':		/* move lower right with action */
X			domove(defarg, defarg);
X			break;
X		case 'N':		/* shift down and right */
X			doshift(defarg, defarg);
X			break;
X		case 'o':		/* insert new cells */
X		case 'O':
X			checkrun();
X			backup();
X			while ((stop == 0) && (defarg-- > 0)) {
X				addcell(obj, crow, ccol++);
X			}
X			redraw = 1;
X			break;
X		case 'p':		/* place deleted object */
X			checkrun();
X			backup();
X			cmark = MARK_USR;
X			addobject(deleteobject, obj, RELATIVE);
X			redraw = 1;
X			break;
X		case 'r':		/* rotate selection */
X			doselect(ch);
X			cmark = MARK_USR;
X			rotatemarkedobject(obj, MARK_CMD);
X			redraw = 1;
X			break;
X		case 's':		/* set scale factor and center object */
X			obj->o_autoscale = 0;
X			if (got1 == 0) arg1 = obj->o_scale;
X			setscale(obj, arg1);
X			break;
X		case 'S':		/* perform auto-scaling */
X			if (got1 == 0) arg1 = obj->o_scale;
X			setscale(obj, arg1);
X			obj->o_autoscale = 1;
X			redraw = 1;
X			break;
X		case 't':		/* toggle current cell */
X			checkrun();
X			backup();
X			if (delcell(obj, crow, ccol))
X				addcell(obj, crow, ccol);
X			redraw = 1;
X			break;
X		case 'u':		/* move upper right with action */
X			domove(-defarg, defarg);
X			break;
X		case 'U':		/* shift to upper right */
X			doshift(-defarg, defarg);
X			break;
X		case 'x':		/* kill current cells */
X			checkrun();
X			backup();
X			while ((stop == 0) && (defarg-- > 0)) {
X				delcell(obj, crow, ccol++);
X			}
X			redraw = 1;
X			break;
X		case 'y':		/* move upper left with action */
X			domove(-defarg, -defarg);
X			break;
X		case 'Y':		/* shift to upper left */
X			doshift(-defarg, -defarg);
X			break;
X		case 'z':		/* clear or set generation number */
X			checkrun();
X			obj->o_gen = arg1;
X			obj->o_born = 0;
X			obj->o_died = 0;
X			update = 1;
X			break;
X		case '/':		/* search for next object */
X			{
X			int minr, maxr, minc, maxc;
X
X			checkrun();
X			if (searchobject(obj, defarg, 0)) error("Empty object");
X			clearmarks(obj, MARK_CMD);
X			markobject(obj, crow, ccol, MARK_CMD);
X			markminmax(obj, MARK_CMD, &minr, &maxr, &minc, &maxc);
X			positionview(minr, maxr, minc, maxc);
X			update = 1;
X			}
X			break;
X		case '@':		/* point at current location */
X			prow = crow;
X			pcol = ccol;
X			break;
X		case '!':		/* comment characters */
X		case '#':
X			while (scanchar() != '\n') ;
X			break;
X		default:		/* unknown commands */
X			error("Unknown command");
X	}
X	scanabort();			/* completed command */
X}
X
X
X/*
X * Read a numeric value (if any) to be used as an argument for a command.
X * Pointers to the returned value and returned flag are given.
X * The returned value is zero if no value is read.
X * The returned flag is nonzero if a value was read.
X * Return value is the first non-argument character read.
X */
Xreadvalue(valueptr, flagptr)
X	register int	*valueptr;	/* pointer to returned value */
X	register int	*flagptr;	/* pointer to got value flag */
X{
X	register int	ch;		/* character being read */
X	register struct	input	*ip;	/* input structure */
X	int	sign;			/* sign of result */
X
X	*valueptr = 0;
X	*flagptr = 0;
X	sign = 1;
X	ch = scanchar();
X	if (ch == '-') {			/* negative value */
X		sign = -1;
X		ch = scanchar();
X	}
X	if (ch == '$') {			/* get variable value */
X		*valueptr = sign * getvariable1(scanchar());
X		*flagptr = 1;
X		return(scanchar());
X	}
X	if (ch == '(') {			/* get expression */
X		*valueptr = sign * scanexpr();
X		*flagptr = 1;
X		return(scanchar());
X	}
X	while ((ch >= '0') && (ch <= '9')) {	/* get numeric value */
X		*valueptr = (*valueptr * 10) + ch - '0';
X		*flagptr = 1;
X		ch = scanchar();
X	}
X	if (ch == '%') {			/* get loop value */
X		ch = 1;
X		if (*flagptr) ch = *valueptr;
X		if (ch <= 0) error("Bad nest value");
X		ip = curinput + 1;
X		while (ch > 0) {
X			if (--ip < inputs) error("Bad nest value");
X			if (ip->i_type == INP_LOOP) ch--;
X		}
X		*valueptr = ip->i_curval;
X		*flagptr = 1;
X		ch = scanchar();
X	}
X	*valueptr *= sign;
X	return(ch);
X}
X
X
X/*
X * Routine called from above to scan and evaluate a parenthesized expression.
X * This routines knows that one parenthesis has already been read.  Stops
X * reading on the matching parenthesis.
X */
Xscanexpr()
X{
X	register char	*cp;		/* current character */
X	register int	nest;		/* nesting depth */
X	char	buf[100];		/* expression buffer */
X
X	cp = buf;
X	*cp++ = '(';			/* start with parenthesis */
X	nest = 1;
X	while (nest > 0) {
X		if (cp >= &buf[sizeof(buf)-2]) error("expression too long");
X		*cp = scanchar();
X		if (*cp == '(') nest++;
X		if (*cp == ')') nest--;
X		cp++;
X	}
X	*cp = '\0';
X	return(getexpression(buf));
X}
X
X
X/*
X * Select a set of cells to be used for some command.  This involves reading
X * the next character and marking cells based on that character.  Repeating
X * the command character is equivilant to selecting the current object.  On a
X * successful return, exactly those cells specified are marked with MARK_CMD.
X * If no cells are found, an error is generated.
X */
Xdoselect(cmd)
X{
X	register struct	object	*obj;	/* object to examine */
X	register long	minrow, maxrow, mincol, maxcol;	/* range for marks */
X	register struct	cell	*cp;	/* current cell */
X	int	ch;			/* character to select on */
X
X	checkrun();
X	ch = scanchar();
X	if (ch == cmd) ch = 'o';	/* repeated char is connected object */
X	minrow = -INFINITY;
X	maxrow = -minrow;
X	mincol = minrow;
X	maxcol = maxrow;
X	obj = curobj;
X	clearmarks(obj, MARK_CMD);
X	switch (ch) {
X		case 'a':		/* all of object */
X			break;
X		case 'b':		/* below and left of cursor */
X			minrow = crow;
X			maxcol = ccol;
X			break;
X		case 'c':		/* current cell */
X			cp = findcell(obj, crow, ccol);
X			if (cp == NULL) error("No cell at current location");
X			cp->c_marks |= MARK_CMD;
X			return;
X		case 'h':		/* left of cursor */
X			maxcol = ccol;
X			break;
X		case 'j':		/* below cursor */
X			minrow = crow;
X			break;
X		case 'k':		/* above cursor */
X			maxrow = crow;
X			break;
X		case 'l':		/* right of cursor */
X			mincol = ccol;
X			break;
X		case 'm':		/* marked cells */
X			if (copymarks(obj, MARK_USR, MARK_CMD))
X				error("No object marked");
X			return;
X		case 'n':		/* below and right of cursor */
X			minrow = crow;
X			mincol = ccol;
X			break;
X		case 'o':		/* connected object */
X			if (markobject(obj, crow, ccol, MARK_CMD))
X				error("No object at current location");
X			return;
X		case 'p':		/* rectangle to pointer */
X			minrow = crow;
X			maxrow = prow;
X			if (minrow > maxrow) {
X				minrow = prow;
X				maxrow = crow;
X			}
X			mincol = ccol;
X			maxcol = pcol;
X			if (mincol > maxcol) {
X				mincol = pcol;
X				maxcol = ccol;
X			}
X			break;
X		case 'u':		/* above and right of cursor */
X			maxrow = crow;
X			mincol = ccol;
X			break;
X		case 'v':		/* things visible in window */
X			minrow = obj->o_minrow;
X			maxrow = obj->o_maxrow;
X			mincol = obj->o_mincol;
X			maxcol = obj->o_maxcol;
X			break;
X		case 'y':		/* above and left of cursor */
X			maxrow = crow;
X			maxcol = ccol;
X			break;
X		default:		/* unknown */
X			error("Unknown selection command");
X	}
X	if (markregion(obj, MARK_CMD, minrow, maxrow, mincol, maxcol) == 0)
X		error("No cells in region");
X}
X
X
X/*
X * Move the current position by the indicated deltas, performing the
X * current action to the configuration.  The movement is scaled by
X * the current scaling factor.
X */
Xdomove(rowdelta, coldelta)
X	register int	rowdelta;	/* amount to change row by */
X	register int	coldelta;	/* amount to change column by */
X{
X	register int	row1;		/* increment for row */
X	register int	col1;		/* increment for column */
X
X	rowdelta *= curobj->o_scale;
X	coldelta *= curobj->o_scale;
X	if (mode == M_MOVE) {		/* just want to move */
X		crow += rowdelta;
X		ccol += coldelta;
X		update = 1;
X		return;
X	}
X	checkrun();
X	backup();
X	row1 = 0;			/* need to loop for insert or delete */
X	col1 = 0;
X	if (rowdelta > 0) row1 = 1;
X	if (rowdelta < 0) row1 = -1;
X	if (coldelta > 0) col1 = 1;
X	if (coldelta < 0) col1 = -1;
X	rowdelta += crow;
X	coldelta += ccol;
X	while ((stop == 0) && ((crow != rowdelta) || (ccol != coldelta))) {
X		crow += row1;
X		ccol += col1;
X		switch (mode) {
X			case M_INSERT:
X				addcell(curobj, crow, ccol);
X				break;
X			case M_DELETE:
X				delcell(curobj, crow, ccol);
X		}
X	}
X	redraw = 1;
X}
X
X
X/*
X * Shift the window lots in the indicated direction, and also shift the
X * cursor location the same amount so that the cursor location on the
X * screen doesn't change.  "Lots" is 1/4 of the screen width or height.
X * Special case: if both x and y are being shifted, we shift both by the
X * same amount, which is the minimum of the two shifts.
X */
Xdoshift(rowdelta, coldelta)
X	register int	rowdelta;	/* amount to change row by */
X	register int	coldelta;	/* amount to change column by */
X{
X	register struct	object	*obj;	/* current object */
X	register int	rowsign;	/* sign of row */
X	register int	colsign;	/* sign of column */
X
X	obj = curobj;
X	rowdelta *= ((rowradius * obj->o_scale) / 2);
X	coldelta *= ((colradius * obj->o_scale) / 2);
X	if (rowdelta && coldelta) {	/* take minimums of absolute values */
X		rowsign = 1;
X		colsign = 1;
X		if (rowdelta < 0) {
X			rowsign = -1;
X			rowdelta = -rowdelta;
X		}
X		if (coldelta < 0) {
X			colsign = -1;
X			coldelta = -coldelta;
X		}
X		if (rowdelta > coldelta) rowdelta = coldelta;
X		if (coldelta > rowdelta) coldelta = rowdelta;
X		rowdelta *= rowsign;
X		coldelta *= colsign;
X	}
X	obj->o_currow += rowdelta;
X	obj->o_minrow += rowdelta;
X	obj->o_maxrow += rowdelta;
X	obj->o_curcol += coldelta;
X	obj->o_mincol += coldelta;
X	obj->o_maxcol += coldelta;
X	redraw = 1;
X}
X
X
X/*
X * Perform a backup of the current object.  This is only done if reading
X * from the terminal, since it is from the point of view of the user.
X */
Xbackup()
X{
X	if (curinput->i_type == INP_TTY) copyobject(curobj, backupobject);
X}
X
X
X/*
X * Check to see that generations are not being computed before proceeding
X * with the current command.
X */
Xcheckrun()
X{
X	if (genleft > 0) error("Illegal while running");
X}
//E*O*F cmd.c//
echo x - cmdl.c
sed -e 's/^X//' > "cmdl.c" << '//E*O*F cmdl.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)cmdl.c	1.31	2/2/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#include "life.h"
X#include <sys/ioctl.h>
X
X/* list of line command routines */
Xint	c_create(), c_destroy(), c_edit(), c_dumpmacros(), c_quit();
Xint	c_frequency(), c_listobjects(), c_zero(), c_lock(), c_unlock();
Xint	c_read(), c_write(), c_ttyinput(), c_gridchar(), c_nogrid();
Xint	c_insert(), c_copy(), c_copyselect(), c_move(), c_moveselect();
Xint	c_help(), c_rules(), c_endinputlevel(), c_undo(), c_rename();
Xint	c_set(), c_variables(), c_type(), c_update(), c_wait();
X
X/* flags for line commands */
X#define	F_NONE	0x0		/* no special condition */
X#define	F_NOARG 0x1		/* must not have an argument */
X#define	F_ARG1	0x2		/* must have at least one argument */
X#define	F_NORUN	0x4		/* illegal if running generations */
X#define	F_UPDATE 0x8		/* update status if command completes */
X#define	F_REDRAW 0x10		/* redraw screen if command completes */
X#define	F_ABBR 0x20		/* abbreviation works even if ambiguous */
X
X
X/*
X * Dispatch table for line commands.  Those commands which are ambiguous
X * must be defined so as to be contiguous in the table.  A spaces delimits
X * the command itself from the help string for the command.
X */
Xstruct	cmdtab {
X	char	*c_name;		/* command name */
X	int	(*c_func)();		/* function to call */
X	int	c_flags;		/* flags for command */
X} cmdtab[] = {
X	"copy (current object to) obj", c_copy, F_ARG1|F_NORUN|F_ABBR,
X	"copyselection (to) obj", c_copyselect, F_ARG1|F_NORUN,
X	"create (object named) obj", c_create, F_ARG1|F_NORUN|F_REDRAW,
X	"destroy (object named) obj", c_destroy, F_ARG1|F_NORUN,
X	"dumpmacros (to) file", c_dumpmacros, F_ARG1|F_NORUN,
X	"edit (object named) obj", c_edit, F_NORUN|F_REDRAW|F_ABBR,
X	"endinputlevel", c_endinputlevel, F_NOARG|F_UPDATE,
X	"frequency (of typeout is) expr", c_frequency, F_UPDATE,
X	"gridcharacter (is) char", c_gridchar, F_REDRAW,
X	"help", c_help, F_NONE|F_ABBR,
X	"insert (object from) obj", c_insert, F_ARG1|F_NORUN|F_REDRAW|F_ABBR,
X	"list (all objects)", c_listobjects, F_NORUN|F_ABBR,
X	"lock (current object)", c_lock, F_NOARG|F_NORUN|F_UPDATE,
X	"move (current object to) obj", c_move, F_ARG1|F_NORUN|F_REDRAW|F_ABBR,
X	"moveselection (to) obj",c_moveselect,F_ARG1|F_NORUN|F_REDRAW,
X	"nogrid", c_nogrid, F_NOARG|F_REDRAW,
X	"quit (program)", c_quit, F_NOARG|F_ABBR,
X	"read (commands from) file", c_read, F_ARG1|F_NORUN|F_REDRAW|F_ABBR,
X	"rename (current object to) obj", c_rename, F_ARG1|F_UPDATE,
X	"rules (for life are) born live", c_rules, F_NORUN|F_UPDATE,
X	"set (variable) name (to) expr", c_set, F_ARG1|F_ABBR,
X	"ttyinput", c_ttyinput, F_REDRAW,
X	"type (value of expression) expr", c_type, F_ARG1|F_UPDATE|F_ABBR,
X	"undo (last change)", c_undo, F_NOARG|F_NORUN|F_REDRAW|F_ABBR,
X	"unlock (current object)", c_unlock, F_NOARG|F_NORUN|F_UPDATE,
X	"updateview (to be current)", c_update, F_NOARG,
X	"variables (are listed)", c_variables, F_NOARG|F_REDRAW|F_ABBR,
X	"wait (for computations)", c_wait, F_NOARG,
X	"write (current object to) file", c_write, F_ARG1|F_NORUN|F_ABBR,
X	"zero (current object)", c_zero, F_NOARG|F_NORUN|F_REDRAW|F_ABBR,
X	0					/* ends the table */
X};
X
X
X/*
X * Read and execute a command line style command.  This kind of command echoes,
X * and is terminated by an end of line.  Numeric arguments are available.
X */
Xdolinecommand(arg1, arg2, got1, got2)
X{
X	register char	*name;		/* command name */
X	register char	*args;		/* arguments for command */
X	register struct	cmdtab *cmd;	/* command structure */
X	register int	flag;		/* flags for command */
X
X	name = readstring("command: ");
X	if ((*name == '\0') || (*name == '\n')) return;
X	for (args = name; *args && (*args != ' ') && (*args != '\t'); args++) ;
X	while ((*args == ' ') || (*args == '\t')) *args++ = '\0';
X	for (cmd = cmdtab; ; cmd++) {
X		if (cmd->c_name == NULL) error("Unknown line command");
X		if (abbrev(name, cmd[0].c_name) == 0) continue;
X		if (cmd->c_flags & F_ABBR) break;
X		if (abbrev(name, cmd[1].c_name) == 0) break;
X		if (strcmp(name, cmd[0].c_name) == 0) break;
X		if (cmd[1].c_flags & F_ABBR) continue;
X		error("Ambiguous line command");
X	}
X	flag = cmd->c_flags;
X	if (flag & F_NORUN) checkrun();
X	if ((flag & F_ARG1) && (*args == '\0')) error("Missing argument");
X	if ((flag & F_NOARG) && *args) error("Argument not allowed");
X	cmd->c_func(args, arg1, arg2, got1, got2);
X	if (flag & F_UPDATE) update = 1;
X	if (flag & F_REDRAW) redraw = 1;
X}
X
X
X
X/* Copy the current object into another object */
Xc_copy(cp)
X	char	*cp;			/* destination object name */
X{
X	copyobject(curobj, getobject(cp));
X}
X
X
X/* Copy the current selection into another object */
Xc_copyselect(cp)
X	char	*cp;			/* destination object name */
X{
X	copymarkedobject(curobj, getobject(cp), MARK_USR);
X}
X
X
X/* Edit an object, creating it if necessary */
Xc_create(cp)
X	char	*cp;			/* object to create */
X{
X	setobject(getobject(cp));
X}
X
X
X/* Destroy an existing object */
Xc_destroy(cp)
X	char	*cp;			/* object name */
X{
X	register struct	object	*obj;	/* object to destroy */
X
X	obj = findobject(cp);
X	if (obj == NULL) error("No such object");
X	destroyobject(obj);
X}
X
X
X/* Dump list of macros to file */
Xc_dumpmacros(cp)
X	char	*cp;			/* file name */
X{
X	writemacros(cp);
X}
X
X
X/* Edit an existing object.  A null argument implies the previous object. */
Xc_edit(cp)
X	char	*cp;			/* object name */
X{
X	register struct	object	*obj;	/* object to edit */
X
X	obj = prevobj;
X	if (*cp) obj = findobject(cp);
X	if (obj == NULL) error("No such object");
X	setobject(obj);
X}
X
X
X/* Undo the last change made to the current object */
Xc_undo()
X{
X	moveobject(curobj, tempobject);
X	moveobject(backupobject, curobj);
X	moveobject(tempobject, backupobject);
X}
X
X
X/* End current input level */
Xc_endinputlevel()
X{
X	if (curinput <= inputs) error("Cannot end top level input");
X	curinput->i_term(curinput);
X}
X
X
X/* Update the view even if inside of a loop or macro */
Xc_update()
X{
X	update = 1;
X	updateview();
X}
X
X
X/* Wait until all outstanding generation computations are finished */
Xc_wait()
X{
X	while ((stop == 0) && (genleft > 0)) {
X		dogeneration(curobj);
X		updateview();
X	}
X}
X
X
X/* Set output frequency */
Xc_frequency(cp)
X	register char	*cp;		/* frequency string */
X{
X	register int	freq;		/* new frequency */
X
X	freq = 1;
X	if (*cp) freq = getexpression(cp);
X	if (freq <= 0) error("Illegal value");
X	frequency = freq;
X	freqcount = freq;
X}
X
X
X/* Select the character used for the background of the screen */
Xc_gridchar(cp)
X	register char	*cp;		/* grid character string */
X{
X	if (*cp == '\0') cp = ".";
X	if ((*cp < ' ') || (cp[1] != '\0')) error("Bad grid character");
X	gridchar = *cp;
X}
X
X
X/* Set so we don`t see a grid on the screen */
Xc_nogrid()
X{
X	gridchar = ' ';
X}
X
X
X/* Type list of line commands */
Xc_help()
X{
X	register struct	cmdtab *cmd;	/* command structure */
X	register int	count;
X
X	dpywindow(0, -1, 0, -1);
X	dpystr("\
XThe following table lists all line mode commands.  Unique abbreviations are\n\
Xallowed.  Commands shown with '*' can be abbreviated even when ambiguous.\n");
X	count = 0;
X	for (cmd = cmdtab; cmd->c_name; cmd++) {
X		if ((count++ % 2) == 0) dpychar('\n');
X		dpyprintf("%c %-35s", ((cmd->c_flags & F_ABBR) ? '*' : ' '),
X			cmd->c_name);
X	}
X	dpychar('\n');
X	spacewait();
X}
X
X
X/* Insert another object into this one */
Xc_insert(cp)
X	char	*cp;			/* object name */
X{
X	register struct	object	*obj;	/* object to insert */
X
X	obj = findobject(cp);
X	if (obj == NULL) error("No such object");
X	cmark = MARK_USR;
X	backup();
X	addobject(obj, curobj, RELATIVE);
X	cmark = MARK_ANY;
X}
X
X
X/* Show list of objects */
Xc_listobjects(cp)
X	register char	*cp;		/* option string */
X{
X	int	all;			/* true if want to see all objects */
X
X	all = ((*cp == '-') || (*cp == 'a'));
X	listobjects(all);
X}
X
X
X/* Lock object so it can't be run */
Xc_lock()
X{
X	curobj->o_lock = 1;
X	genleft = 0;
X	freqcount = frequency;
X}
X
X
X/* Unlock object so it can be run */
Xc_unlock()
X{
X	curobj->o_lock = 0;
X}
X
X
X/* Rename the current object to something else */
Xc_rename(cp)
X	register char	*cp;			/* new name */
X{
X	if (curobj->o_reserved) error("Cannot rename reserved object");
X	if (strlen(cp) > MAXNAME) error("Name too long");
X	if (findobject(cp)) error("Name already exists");
X	if (BADNAME(cp)) error("Cannot create reserved name");
X	strcpy(curobj->o_name, cp);
X}
X
X
X/* Move current object into another object */
Xc_move(cp)
X	char	*cp;			/* destination object name */
X{
X	moveobject(curobj, getobject(cp));
X}
X
X
X/* Move current selection into another object */
Xc_moveselect(cp)
X	char	*cp;			/* destination object name */
X{
X	movemarkedobject(curobj, getobject(cp), MARK_USR);
X}
X
X
X/* Set the value of a variable */
Xc_set(cp)
X	register char	*cp;		/* variable name */
X{
X	register char	*exp;		/* expression */
X
X	for (exp = cp; *exp && (*exp != ' ') && (*exp != '='); exp++) ;
X	if (*exp == ' ') {		/* skip spaces */
X		*exp++ = '\0';
X		while (*exp == ' ') exp++;
X	}
X	if (*exp == '\0') {		/* no expression, set to zero */
X		setvariable(cp, 0);
X		return;
X	}
X	if (*exp == '=') *exp++ = '\0';
X	setvariable(cp, getexpression(exp));
X}
X
X
X/* Type the value of an expression */
Xc_type(cp)
X	char	*cp;				/* expression */
X{
X	static	char	buf[20];		/* storage for string */
X
X	sprintf(buf, "%d\n", getexpression(cp));/* store value */
X	errorstring = buf;			/* make it seen */
X}
X
X
X/* Display the values of all the variables */
Xc_variables()
X{
X	listvariables();
X}
X
X
X/* Quit program */
Xc_quit()
X{
X	dpyclose();
X	exit(0);
X}
X
X
X/* Read commands or object from file, defaulting extension if needed */
Xc_read(cp)
X	register char	*cp;		/* file name */
X{
X	backup();
X	if (setfile(cp)) error("Cannot open input file");
X}
X
X
X/* Set new life rules */
Xc_rules(cp)
X	register char	*cp;		/* life rules string */
X{
X	register char	*bp;		/* born string */
X	register char	*lp;		/* live string */
X
X	if (*cp == '\0') cp = "3,23";
X	bp = cp;
X	while ((*cp >= '0') && (*cp <= '8')) cp++;
X	if (*cp == '\0') error("Missing rule string");
X	if ((*cp != ',') && (*cp != ' ') && (*cp != '\t')) {
X		error("Bad born string");
X	}
X	*cp++ = '\0';
X	while ((*cp == ',') || (*cp == ' ') || (*cp == '\t')) cp++;
X	lp = cp;
X	while ((*cp >= '0') && (*cp <= '8')) cp++;
X	if (*cp != '\0') error("Bad live string");
X	for (cp = rules; cp < &rules[18]; cp++) *cp = 0;
X	while (*bp) rules[*bp++ - '0'] = 1;
X	while (*lp) rules[*lp++ - '0' + LIFE] = 1;
X	bp = rulestring;
X	for (cp = rules; cp < &rules[LIFE]; cp++) {
X		if (*cp) *bp++ = '0' + (cp - rules);
X	}
X	*bp++ = ',';
X	for (cp = &rules[LIFE]; cp < &rules[18]; cp++) {
X		if (*cp) *bp++ = ('0' - LIFE) + (cp - rules);
X	}
X	*bp = '\0';
X}
X
X
X/*
X * Read commands from the terminal.  Useful in loops or command files.
X * If the -c argument is given, we don't do it if no input is ready.
X */
Xc_ttyinput(cp)
X	register char	*cp;		/* pointer to argument string */
X{
X	int	count;			/* character count */
X
X	if ((*cp == '-') || (*cp == 'c')) {	/* check for input */
X		count = 0;
X		ioctl(STDIN, FIONREAD, &count);
X		if (count == 0) return;
X	}
X	if (settty()) error("Nesting too deep");
X}
X
X
X/* Write object to file */
Xc_write(cp, arg1, arg2, got1, got2)
X	register char	*cp;		/* pointer to argument string */
X{
X	if (got1 == 0) arg1 = WRITECOLS;
X	writeobject(curobj, cp, arg1?WRITEROWS:0, arg1);
X}
X
X
X/* Zero out current object */
Xc_zero()
X{
X	register struct	object	*obj;	/* current object */
X
X	obj = curobj;
X	if (obj->o_lock) error("Object is locked");
X	backup();
X	zeroobject(obj);
X	obj->o_gen = 0;
X	obj->o_born = 0;
X	obj->o_died = 0;
X	obj->o_currow = 0;
X	obj->o_curcol = 0;
X	obj->o_prow = 0;
X	obj->o_pcol = 0;
X	obj->o_autoscale = 0;
X	setscale(obj, 1);
X	mode = M_MOVE;
X	frequency = 1;
X	freqcount = 1;
X}
X
X
X/*
X * See if one string is an abbreviation of another.  This knows that
X * the second string contains spaces which terminate the comparison.
X * Returns nonzero if first string is an abbreviation.
X */
Xabbrev(str1, str2)
X	register char	*str1, *str2;	/* strings */
X{
X	if ((str1 == NULL) || (str2 == NULL)) return(0);
X	while (*str1) if (*str1++ != *str2++) return(0);
X	return(1);
X}
//E*O*F cmdl.c//
echo x - debug.c
sed -e 's/^X//' > "debug.c" << '//E*O*F debug.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)debug.c	1.2	1/14/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#ifdef DEBUG
X#include "life.h"
X
X/*
X * Subroutine to dump out all object data structures for debugging.
X */
Xdumpdata()
X{
X	register struct	object	*obj;
X
X	printf("\ntermrow %x termcell %x\n", termrow, termcell);
X	for (obj = objects; obj; obj = obj->o_next) {
X		dumpobj(obj);
X	}
X	printf("done\n");
X}
X
X
X/*
X * Dump out an object.
X */
Xdumpobj(obj)
X	register struct	object	*obj;
X{
X	register struct	row	*rp;
X
X	printf("object %s (%x) firstrow %x lastrow %x\n", 
X		obj->o_name, obj, obj->o_firstrow, obj->o_lastrow);
X	rp = obj->o_firstrow;
X	while (1) {
X		dumprow(rp);
X		if (rp == termrow) break;
X		rp = rp->r_next;
X	}
X}
X
X
X/*
X * Dump out a row
X */
Xdumprow(rp)
X	register struct	row	*rp;
X{
X	register struct	cell	*cp;
X
X	if (rp == termrow) {
X		printf("  termrow\n");
X		return;
X	}
X	printf("  row %d (%x) firstcell %x lastcell %x\n",
X		rp->r_row, rp, rp->r_firstcell, rp->r_lastcell);
X	cp = rp->r_firstcell;
X	while (1) {
X		dumpcell(cp);
X		if (cp == termcell) break;
X		cp = cp->c_next;
X	}
X}
X
X
X/*
X * Dump out a cell
X */
Xdumpcell(cp)
X	register struct	cell	*cp;
X{
X	if (cp == termcell) {
X		printf("    termcell\n");
X		return;
X	}
X	printf("    cell %d (%x)\n", cp->c_col, cp);
X}
X
X#endif DEBUG
//E*O*F debug.c//
echo x - gen.c
sed -e 's/^X//' > "gen.c" << '//E*O*F gen.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)gen.c	1.4	2/2/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#include "life.h"
X
Xchar	rules[18] = {0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0};	/* life rules */
X
X
X/*
X * Compute one full generation of the current configuration.
X * This is done by calling computerow with each triple of rows which
X * contain any live cells, and remembering the result.  When all rows
X * are finished, we change the object.
X */
Xdogeneration(obj)
X	struct	object	*obj;			/* object to compute */
X{
X	register struct	row	*prp;		/* previous row */
X	register struct	row	*crp;		/* current row */
X	register struct	row	*nrp;		/* next row */
X	register struct	row	*newrp;		/* current row of new list */
X	register struct	row	*srp;		/* saved row pointer */
X	int	row;				/* current row number */
X
X	if (genleft <= 0) return;
X	if ((--genleft == 0) || (--freqcount <= 0)) redraw = 1;
X	obj->o_gen++;
X	obj->o_born = 0;
X	obj->o_died = 0;
X	newrp = &initrow;
X	prp = termrow;
X	crp = termrow;
X	nrp = obj->o_firstrow;
X	srp = nrp->r_next;
X	row = nrp->r_row - 1;
X	while (1) {			/* loop over each triple of rows */
X		if (row >= (INFINITY-1)) break;
X		prp = computerow(obj, prp, crp, nrp);
X		if (prp) {		/* have something in current row */
X			newrp->r_next = prp;
X			newrp = prp;
X			newrp->r_row = row;
X			obj->o_count += prp->r_count;
X		}
X		row++;
X		prp = crp;
X		crp = nrp;
X		nrp = srp;
X		if (nrp->r_row == row + 1) {
X			srp = nrp->r_next;
X			continue;
X		}
X		nrp = termrow;
X		if ((prp != termrow) || (crp != termrow)) continue;
X		nrp = srp;
X		srp = nrp->r_next;
X		row = nrp->r_row - 1;
X	}
X	zeroobject(obj);
X	if (newrp == &initrow) {			/* died off */
X		genleft = 0;
X		redraw = 1;
X		return;
X	}
X	obj->o_firstrow = initrow.r_next;
X	newrp->r_next = termrow;
X	obj->o_lastrow = newrp;
X	if ((obj->o_born == 0) && (obj->o_died == 0)) {	/* no change */
X		genleft = 0;
X		redraw = 1;
X	}
X}
X
X
X/*
X * Compute the result of three adjacent rows, and return a row structure
X * containing the new middle row, or NULL if no live cells are produced.
X * When determining if a cell is dead or alive, each live neighbor counts
X * as a 1, but the current cell counts as 9 when alive.  Indexing the rules
X * table with the sum then automatically produces the correct result.
X */
Xstruct row *
Xcomputerow(obj, prevrow, currow, nextrow)
X	struct	object	*obj;
X	struct	row	*prevrow, *currow, *nextrow;
X{
X	register struct	cell	*pcp;	/* head of previous row of cells */
X	register struct	cell	*ccp;	/* head of current row of cells */
X	register struct	cell	*ncp;	/* head of next row of cells */
X	register struct	cell	*tcp;	/* temporary cell */
X	register struct	cell	*newcp;	/* new row of cells */
X	register int	i;		/* sum of live cells and other uses */
X	struct	row	*rp;		/* new row pointer */
X	int	col;			/* current column being examined */
X	int	colp1;			/* one more than column */
X	int	count;			/* live cells */
X
X	pcp = prevrow->r_firstcell;
X	ccp = currow->r_firstcell;
X	ncp = nextrow->r_firstcell;
X	newcp = &initcell;
X	count = 0;
X	col = -INFINITY;
X	while (1) {			/* loop over cells of all 3 rows */
X		/*
X		 * Find next column where a cell exists on any row
X		 */
X		i = col - 1;
X		while (i > pcp->c_col) pcp = pcp->c_next;
X		while (i > ccp->c_col) ccp = ccp->c_next;
X		while (i > ncp->c_col) ncp = ncp->c_next;
X		i = ccp->c_col;
X		if (pcp->c_col < i) i = pcp->c_col;
X		if (ncp->c_col < i) i = ncp->c_col;
X		if (i == INFINITY) break;
X		i--;
X		if (col < i) col = i;
X		i = 0;
X		colp1 = col + 1;
X		if (pcp->c_col <= colp1) {	/* add cells in previous row */
X			i++;
X			tcp = pcp->c_next;
X			i += (tcp->c_col <= colp1);
X			tcp = tcp->c_next;
X			i += (tcp->c_col <= colp1);
X		}
X		if (ccp->c_col <= colp1) {	/* add cells on our row */
X			i++;
X			tcp = ccp->c_next;
X			if ((ccp->c_col == col) || (tcp->c_col == col)) {
X				i += (LIFE - 1);
X			}
X			i += (tcp->c_col <= colp1);
X			tcp = tcp->c_next;
X			i += (tcp->c_col <= colp1);
X		}
X		if (ncp->c_col <= colp1) {	/* add cells in next row */
X			i++;
X			tcp = ncp->c_next;
X			i += (tcp->c_col <= colp1);
X			tcp = tcp->c_next;
X			i += (tcp->c_col <= colp1);
X		}
X		if (rules[i]) {			/* cell is alive */
X			obj->o_born += (i < LIFE);
X			tcp = alloccell();
X			tcp->c_col = col;
X			newcp->c_next = tcp;
X			newcp = tcp;
X			count++;
X		} else				/* cell is dead */
X			obj->o_died += (i >= LIFE);
X		col++;
X	}
X	if (newcp == &initcell) return(NULL);
X	newcp->c_next = termcell;
X	rp = allocrow();
X	rp->r_firstcell = initcell.c_next;
X	rp->r_lastcell = newcp;
X	rp->r_count = count;
X	return(rp);
X}
//E*O*F gen.c//
echo x - io.c
sed -e 's/^X//' > "io.c" << '//E*O*F io.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)io.c	1.10	2/1/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#include "life.h"
X#include <errno.h>
X#include <sys/ioctl.h>
X
Xextern	int	errno;			/* error return value */
X
Xint	tty_char(), tty_term();		/* terminal routines */
Xint	file_char(), file_term();	/* file routines */
Xint	loop_char(), loop_term();	/* loop routines */
Xint	macro_char(), macro_term();	/* macro routines */
Xint	getdpychar();			/* for dpyread to call */
X
X
X/*
X * Read the next character from the appropriate input source.
X * Negative answer from source indicates end of file.
X * This routine is usually called indirectly by scanchar.
X */
Xreadchar()
X{
X	register struct	input	*ip;	/* input structure being used */
X	register int	ch;		/* current character */
X
X	while (1) {			/* continue until not end of file */
X		ip = curinput;
X		ch = ip->i_getchar(ip);	/* read next character */
X		if (ch >= 0) break;
X		ip->i_term(ip);		/* end of file, clean up */
X	}
X	return(ch);
X}
X
X
X/*
X * Determine if the next input character to be read is from the terminal.
X * Returns nonzero if so.
X */
Xttyisinput()
X{
X	register struct	input	*ip;	/* input structure being checked */
X
X	ip = curinput;
X	while ((ip->i_type == INP_LOOP) && (ip->i_first)) ip--;
X	return(ip->i_type == INP_TTY);
X}
X
X
X/*
X * Setup to read commands from the terminal.  The lowest level of input
X * never quits, and is unusual in that it doesn't usually block waiting for
X * input.  Returns nonzero if failed.
X */
Xsettty()
X{
X	register struct	input	*ip;		/* current input */
X
X	ip = curinput + 1;		/* allocate next structure */
X	if (ip >= &inputs[MAXINPUT]) {
X		return(1);
X	}
X	ip->i_getchar = tty_char;	/* set up for I/O */
X	ip->i_term = tty_term;
X	ip->i_type = INP_TTY;
X	update = 1;
X	curinput = ip;
X	return(0);
X}
X
X
X/*
X * Read next character from the terminal if it is ready.  If nothing is
X * going on we will wait for it anyway, to prevent excessive runtimes.
X * We set the interactive flag to indicate we are talking to user.
X */
Xtty_char()
X{
X	long	n;			/* char count */
X	int	ch;			/* char to return */
X
X	interact = 1;
X	if ((dowait == 0) && (redraw || update || (genleft > 0))) {
X		if ((ioctl(STDIN, FIONREAD, &n) == 0) && (n <= 0)) {
X			scaneof();		/* no char available now */
X		}
X	}
X	do {
X		if (stop) return('\0');		/* stop will be seen later */
X		errno = 0;
X		n = read(STDIN, &ch, 1);	/* read one char */
X	} while ((n < 0) && (errno == EINTR));
X	if (n <= 0) {
X		return(-1);			/* error or end of file */
X	}
X	if (errorstring) {			/* disable error message */
X		errorstring = NULL;
X		update = 1;
X	}
X	return(ch &= 0x7f);
X}
X
X
X/*
X * Terminate reading from the terminal.  If we are reading from the lowest
X * level of terminal input, this is a no-op.
X */
Xtty_term(ip)
X	register struct	input	*ip;	/* input structure */
X{
X	if (ip > inputs) ip--;
X	curinput = ip;
X	update = 1;
X}
X
X
X/*
X * Setup to read commands from a given file name.   When the file is complete,
X * some parameters (like the current cursor position) will be restored to their
X * original value.  Returns nonzero if cannot set up to read the file.
X */
Xsetfile(name)
X	register char	*name;		/* file name to read from */
X{
X	register struct	input	*ip;	/* current input structure */
X
X	ip = curinput + 1;			/* use next structure */
X	if (ip >= &inputs[MAXINPUT]) {
X		return(1);
X	}
X	ip->i_file = openlibfile(name, 0);	/* open the file */
X	if (ip->i_file < 0) {
X		return(1);
X	}
X	ip->i_begptr = (char *) malloc(FILESIZE); /* allocate data buffer */
X	if (ip->i_begptr == NULL) {
X		close(ip->i_file);
X		return(1);
X	}
X	ip->i_endptr = ip->i_begptr;		/* set up rest of io */
X	ip->i_curptr = ip->i_begptr;
X	ip->i_getchar = file_char;
X	ip->i_term = file_term;
X	ip->i_type = INP_FILE;
X	ip->i_obj = curobj;			/* save current state */
X	ip->i_row = crow;
X	ip->i_col = ccol;
X	ip->i_prow = prow;
X	ip->i_pcol = pcol;
X	prow = crow;
X	pcol = ccol;
X	update = 1;
X	curinput = ip;				/* this is now current input */
X	return(0);
X}
X
X
X/*
X * Open a life library file, trying various transformed names if necessary.
X * The order of the transformations which are tried is:
X *   1.	Name exactly as given.
X *   2.	Name with ".l" appended to it.
X *   3.	Name in the user's library directory.
X *   4.	Name with ".l" appended to it in the user's library.
X *   5  Name in the system's library directory.
X *   6.	Name with ".l" appended to it in the system library.
X * Returns the file descriptor if the open is successful, or -1 if all the
X * open attempts failed.
X */
Xopenlibfile(name, mode)
X	register char	*name;		/* original file name */
X	register int	mode;		/* desired open mode */
X{
X	register int	fd;		/* file descriptor */
X	char	buf[2000];		/* transformed names */
X
X	fd = open(name, mode);			/* try name straight */
X	if (fd >= 0) return(fd);
X	sprintf(buf, "%s.l", name);		/* try name with .l appended */
X	fd = open(buf, mode);
X	if (fd >= 0) return(fd);
X	if (*name == '/') return(-1);		/* quit now if absolute name */
X	if (userlib) {				/* look in user's lib area */
X		sprintf(buf, "%s/%s", userlib, name);
X		fd = open(buf, mode);
X		if (fd >= 0) return(fd);
X		strcat(buf, ".l");		/* try with .l */
X		fd = open(buf, mode);
X		if (fd >= 0) return(fd);
X	}
X	sprintf(buf, "%s/%s", LIFELIB, name);	/* look in general lib area */
X	fd = open(buf, mode);
X	if (fd >= 0) return(fd);
X	strcat(buf, ".l");			/* last try with .l */
X	return(open(buf, mode));
X}
X
X
X/*
X * Here to read next character from a file.  Called by readchar when
X * input source is a file.
X */
Xfile_char(ip)
X	register struct	input	*ip;		/* input structure */
X{
X	if (ip->i_curptr >= ip->i_endptr) {	/* get next chunk of file */
X		ip->i_curptr = ip->i_begptr;
X		ip->i_endptr = ip->i_begptr;
X		ip->i_endptr += read(ip->i_file, ip->i_begptr, FILESIZE);
X		if (ip->i_endptr <= ip->i_begptr) {
X			return(-1);		/* end of file */
X		}
X	}
X	return(*ip->i_curptr++ & 0x7f);
X}
X
X
X/*
X * Here on end of file or error to close the input file and restore some
X * of the previous state such as the cursor location.
X */
Xfile_term(ip)
X	register struct	input	*ip;	/* input structure */
X{
X	close(ip->i_file);
X	free(ip->i_begptr);
X	curobj = ip->i_obj;
X	crow = ip->i_row;
X	ccol = ip->i_col;
X	prow = ip->i_prow;
X	pcol = ip->i_pcol;
X	update = 1;
X	curinput = (ip - 1);
X}
X
X
X/*
X * Setup for execution of a loop.  This remembers the initial and final
X * loop values, and sets up to remember commands as they are given.
X * This is also used for defining a macro command.  The given character
X * will be assigned the string defined by the loop.
X * Returns nonzero if failed.
X */
Xsetloop(begval, endval, ch)
X{
X	register struct	input	*ip;		/* input structure */
X
X	ip = curinput + 1;		/* allocate next structure */
X	if (ip >= &inputs[MAXINPUT]) {
X		return(1);
X	}
X	ip->i_begptr = (char *) malloc(LOOPSIZE);	/* allocate buffer */
X	if (ip->i_begptr == NULL) {
X		return(1);
X	}
X	ip->i_endptr = ip->i_begptr + LOOPSIZE;		/* set up for I/O */
X	ip->i_curptr = ip->i_begptr;
X	ip->i_first = 1;
X	ip->i_getchar = loop_char;
X	ip->i_term = loop_term;
X	ip->i_type = INP_LOOP;
X	ip->i_curval = begval;
X	ip->i_endval = endval;
X	ip->i_macro = ch;
X	update = 1;
X	curinput = ip;
X	return(0);
X}
X
X
X/*
X * End the range of the currently defined loop.  At this point, all of
X * the characters of the loop have been read in and saved, and we can
X * just proceed to iterate over them.  The next read will find out that
X * the first iteration of the loop is over.
X */
Xendloop()
X{
X	register struct	input	*ip;	/* current input */
X
X	ip = curinput;
X	if (ip->i_type != INP_LOOP) error("Loop not being defined");
X	ip->i_endptr = ip->i_curptr - 1;	/* end before loop term cmd */
X	ip->i_first = 0;
X}
X
X
X/*
X * Read one character from a loop buffer.  If at the end of the buffer, the
X * pointer is reset so that the buffer is reread.  When enough iterations
X * have been processed, we are done.  A special case exists the first time
X * through the loop at the first nesting level, in that we don't yet have
X * the characters necessary for the loop, and so we have to read them by
X * ourself.
X */
Xloop_char(ip)
X	register struct	input	*ip;		/* input structure */
X{
X	register int	ch;
X
X	if (ip->i_first) {			/* collecting input chars */
X		if (ip->i_curptr >= ip->i_endptr) error("Loop too long");
X		ch = ip[-1].i_getchar(ip - 1);	/* char from previous level */
X		if (ch < 0) error("End of file in loop");
X		*ip->i_curptr++ = ch;
X		return(ch);
X	}
X	if (ip->i_curptr >= ip->i_endptr) {	/* done with one iteration */
X		if (ip->i_curval == ip->i_endval) {
X			return(-1);		/* end of file */
X		}
X		ip->i_curval += ((ip->i_curval < ip->i_endval) ? 1 : -1);
X		ip->i_curptr = ip->i_begptr;
X	}
X	return(*ip->i_curptr++);
X}
X
X
X/*
X * Terminate reading from a loop buffer.  If this was the definition of
X * a macro character, remember it for later.
X */
Xloop_term(ip)
X	struct	input	*ip;		/* input structure */
X{
X	register struct	macro	*mp;	/* macro being defined */
X
X	mp = &macros[ip->i_macro - 'a'];
X	if ((mp >= macros) && (mp < &macros[26])) {
X		if (mp->m_begptr) free(mp->m_begptr);
X		mp->m_begptr = ip->i_begptr;
X		mp->m_endptr = ip->i_endptr;
X	} else
X		free(ip->i_begptr);		/* or free buffer */
X	update = 1;
X	curinput = (ip - 1);
X}
X
X
X/* Set up to read a defined macro command.  Returns nonzero if failed. */
Xsetmacro(arg1, arg2, ch)
X{
X	register struct	input	*ip;	/* current input */
X	register struct	macro	*mp;	/* macro command */
X
X	mp = &macros[ch - 'a'];		/* verify macro character */
X	if ((mp < macros) || (mp >= &macros[26]) || (mp->m_begptr == NULL)) {
X		return(1);
X	}
X	ip = curinput + 1;		/* use next input structure */
X	if (ip >= &inputs[MAXINPUT]) {
X		return(1);
X	}
X	ip->i_getchar = macro_char;	/* set up for I/O */
X	ip->i_term = macro_term;
X	ip->i_begptr = mp->m_begptr;
X	ip->i_curptr = mp->m_begptr;
X	ip->i_endptr = mp->m_endptr;
X	ip->i_type = INP_MACRO;
X	ip->i_macro = ch;
X	ip->i_curval = arg1;
X	ip->i_endval = arg2;
X	update = 1;
X	curinput = ip;
X	return(0);
X}
X
X
X/* Here to read next character from macro definition. */
Xmacro_char(ip)
X	register struct	input	*ip;		/* input structure */
X{
X	if (ip->i_curptr >= ip->i_endptr) {
X		return(-1);		/* end of file */
X	}
X	return(*ip->i_curptr++);
X}
X
X
X/* Here to terminate reading from a macro definition. */
Xmacro_term(ip)
X	struct	input	*ip;			/* input structure */
X{
X	curinput = (ip - 1);
X	update = 1;
X}
X
X
X/*
X * Read a line from the user terminated by a newline (which is removed).
X * Editing of the input line is fully handled.  The prompt string and the
X * input line are only visible if the current input is from the terminal.
X * Returns a pointer to the null-terminated string.
X */
Xchar *
Xreadstring(prompt)
X	register char	*prompt;	/* prompt string */
X{
X	int	i;			/* number of characters read */
X
X	scanreset();				/* no more scan interference */
X	if (prompt == NULL) prompt = "";	/* set prompt if given NULL */
X	if (ttyisinput() == 0) prompt = NULL;	/* no window stuff if not tty */
X	if (prompt) dpywindow(0, 0, 0, -1);	/* show input in top line */
X	dowait = 1;				/* must wait for tty chars */
X	i = dpyread(prompt, getdpychar, stringbuf, FILESIZE);	/* read it */
X	dowait = 0;				/* back to normal */
X	stringbuf[i] = '\0';			/* terminate line */
X	update = 1;
X	return(stringbuf);
X}
X
X
X/*
X * Routine called by dpyread to read the next character of input.
X * We return end of input on the newline character.
X */
Xgetdpychar()
X{
X	register int	ch;		/* character just read */
X
X	ch = readchar();
X	if (stop || (ch == '\n')) return(-1);
X	return(ch);
X}
X
X
X/*
X * Routine called to wait until a space, newline, or escape character
X * is typed.  It is assumed that the screen contains a display which
X * we can append our message to.
X */
Xspacewait()
X{
X	register int	ch;			/* read character */
X
X	scanreset();				/* throw out stored chars */
X	dpystr("\nType <space> to return\n");	/* append our message */
X	dpyclearwindow();
X	dpyhome();
X	dpyupdate();				/* show the result */
X	redraw = 1;
X	dowait = 1;				/* must wait for chars */
X	do {
X		if (stop) break;
X		ch = readchar();
X	} while ((ch != ' ') && (ch != ESC) && (ch != '\n'));
X	dowait = 0;
X}
X
X
X/*
X * Write the current object out to the named file, with the given maximum
X * sizes for "pretty" output.  If the size is exceeded, the output is
X * compressed.  The file as written can be read in as commands which will
X * regenerate the object.
X */
Xwriteobject(obj, name, maxrows, maxcols)
X	struct	object	*obj;		/* object to write */
X	char	*name;			/* filename to use */
X{
X	register FILE	*fd;		/* file structure */
X	register struct	row	*rp;	/* current row structure */
X	register struct	cell	*cp;	/* current cell */
X	register int	row;		/* current row value */
X	register int	col;		/* current column value */
X	struct	row	*trp;		/* temporary row structure */
X	int	minrow;			/* minimum row of object */
X	int	maxrow;			/* maximum row of object */
X	int	mincol;			/* minimum column of object */
X	int	maxcol;			/* maximum column of object */
X	int	curmin;			/* current minimum column */
X	int	curmax;			/* current maximum column */
X	int	testmin;		/* test minimum column of rows */
X	int	testmax;		/* test maximum column of rows */
X
X	minmax(obj, &minrow, &maxrow, &mincol, &maxcol);
X	if (minrow > maxrow) error("Null object");	/* nothing to write */
X	fd = fopen(name, "w");
X	if (fd == NULL) error("Cannot open output file");
X	fprintf(fd, "! \"%s\" (cells %d length %d width %d generation %d)\n",
X		obj->o_name, obj->o_count, maxrow - minrow + 1,
X		maxcol - mincol + 1, obj->o_gen);
X	if (obj->o_currow > minrow) fprintf(fd, "%dk", obj->o_currow - minrow);
X	if (obj->o_currow < minrow) fprintf(fd, "%dj", minrow - obj->o_currow);
X	if (obj->o_curcol > mincol) fprintf(fd, "%dh", obj->o_curcol - mincol);
X	if (obj->o_curcol < mincol) fprintf(fd, "%dl", mincol - obj->o_curcol);
X	fprintf(fd, "@!\n");
X	curmin = INFINITY;
X	curmax = -INFINITY;
X	rp = obj->o_firstrow;
X	row = minrow;
X	for (; rp != termrow; rp = rp->r_next) {
X		/*
X		 * See if user wants to stop.
X		 */
X		if (stop) {
X			fclose(fd);
X			return;
X		}
X		/*
X		 * Skip down to the next row with something in it.
X		 */
X		if (rp->r_firstcell == termcell) continue;
X		if (rp->r_row > (row + maxrows)) {	/* skip to right row */
X			fprintf(fd, "%d\n", rp->r_row - row);
X			row = rp->r_row;
X		}
X		while (rp->r_row > row) {
X			fputs(".\n", fd);
X			row++;
X		}
X		/*
X		 * Output the current row, compressing if it is too wide.
X		 */
X		col = mincol;
X		cp = rp->r_firstcell;
X		if ((cp->c_col + maxcols) < rp->r_lastcell->c_col) {
X			while (cp != termcell) {
X				/*
X				 * Write all adjacent blanks.
X				 */
X				if (cp->c_col > col + 2) {
X					fprintf(fd, "%d.", cp->c_col - col);
X					col = cp->c_col;
X				}
X				while (cp->c_col > col) {
X					fputc('.', fd);
X					col++;
X				}
X				/*
X				 * Write all adjacent cells.
X				 */
X				while ((cp->c_col + 1) == cp->c_next->c_col) {
X					cp = cp->c_next;
X				}
X				if (cp->c_col >= col + 2) {
X					fprintf(fd, "%dO", cp->c_col - col + 1);
X					col = cp->c_col + 1;
X				}
X				while (col <= cp->c_col) {
X					fputc('O', fd);
X					col++;
X				}
X				cp = cp->c_next;
X			}
X			fputc('\n', fd);
X			row++;
X			curmin = INFINITY;
X			curmax = -INFINITY;
X			continue;
X		}
X		/*
X		 * Here if the row doesn't need compressing.  See if several
X		 * rows from this one on can all fit in the same range.  If
X		 * so, set things up so they will align themselves nicely.
X		 */
X		if ((cp->c_col < curmin) || (rp->r_lastcell->c_col > curmax)) {
X			curmin = cp->c_col;
X			curmax = rp->r_lastcell->c_col;
X			trp = rp;
X			for (; rp != termrow; rp = rp->r_next) {
X				if (rp->r_firstcell == termcell) continue;
X				testmin = rp->r_firstcell->c_col;
X				if (testmin > curmin) testmin = curmin;
X				testmax = rp->r_lastcell->c_col;
X				if (testmax < curmax) testmax = curmax;
X				if ((testmax - testmin) >= maxcols) break;
X				curmin = testmin;
X				curmax = testmax;
X			}
X			rp = trp;
X		}
X		/*
X		 * Type the row, with the initial shift if necessary.
X		 */
X		if (curmin != mincol) {
X			fprintf(fd, "%d.", curmin - mincol);
X			col = curmin;
X		}
X		for (cp = rp->r_firstcell; cp != termcell; cp = cp->c_next) {
X			while (cp->c_col > col) {
X				fputc('.', fd);
X				col++;
X			}
X			fputc('O', fd);
X			col++;
X		}
X		fputc('\n', fd);
X		row++;
X	}
X	fclose(fd);
X}
X
X
X
X/* Write all defined macros to a file so they can be read back in later. */
Xwritemacros(name)
X	char	*name;			/* file name to write to */
X{
X	register int	f;		/* file descriptor */
X	register struct	macro	*mp;	/* current macro */
X	int	ch;			/* character */
X
X	f = creat(name, 0666);
X	if (f < 0) error("Cannot create file");
X	for (mp = macros; mp < &macros[26]; mp++) {
X		if ((mp->m_begptr == NULL) || (mp->m_begptr >= mp->m_endptr)) {
X			continue;
X		}
X		ch = 'a' + (mp - macros);
X		write(f, "<", 1);
X		write(f, &ch, 1);
X		write(f, mp->m_begptr, mp->m_endptr - mp->m_begptr);
X		write(f, ">!\n", 3);
X	}
X	close(f);
X}
//E*O*F io.c//
echo done



More information about the Comp.sources.unix mailing list