/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*   fp [[-|+]options] [[lmargin [imargin]] rmargin] [.P]                       *
*   Fp [[-|+]options] [[lmargin [imargin]] rmargin] [.P]                       *
*   fh [[-|+]options] [[lmargin [imargin]] rmargin] [.P]                       *
*   fc [[-|+]options] [[lmargin [imargin]] rmargin] [.P]                       *
*                                                                              *
* Paragraph formatter.  This program  reads  stdin,  breaking  the  text  into *
* paragraphs,  and  writing  output  that  has  the  text betweens the columns *
* [lmargin,rmargin], which default to [0,69].  As a special goodie, if  fp  is *
* called  with an upper-case letter at the start of its name, the margins will *
* default to [0,77], thus almost filling a standard-width line.                *
*                                                                              *
* If the imargin is included, the output will have  the  first  line  of  each *
* paragraph  indented by that amount.  If there is no imargin, first lines are *
* indented the same as the rest of the text,  and  blank  lines  are  used  to *
* separate paragraphs.                                                         *
*                                                                              *
* If  there  is  a  single  non-alphameric char at the left margin, it will be *
* noted and propogated to the output.  If the same character  appears  at  the *
* right  margin  of  a line, it will be propogated to subsequent lines, so you *
* only need put the margin char on the first line of  the  input  to  have  it *
* appear on all lines of the output.  Both of these must be separated from the *
* text by at least one whitespace char to be noticed.                          *
*                                                                              *
* The text you are now reading was formatted by the command "fp 78".           *
*                                                                              *
* See fp.d for documentation.  See also box.d for how to box  text  like  this *
* without doing any formatting.                                                *
** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* This program was written by John Chambers in Aug-Sept 1991.
*	<jc@eddie.mit.edu>
*	<jc@trillian.mit.edu>
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
:1#include "V.h"
#include "fp.h"
/*
* Special kinds of input:
*/
#define EOI -2	/* End of input */
#define EOP -3	/* End of paragraph */
#define EOS -4	/* End of sentence */
#define EOW -5	/* End of word */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
* The isspace() macro might not include '\n' or '\r' on your system; if  this
* is  a  problem,  we  use  the following macro, which you can modify to work
* correctly.
*/
#define Isspace(c) isspace(c)
/*efine Isspace(c) (isspace(c)||(c=='\n')||(c=='\r'))*/
/*
* We note four special columns for alignment:
*/
int   indent =0;	/* Indent margin (in input line) */
int   lmargin=0;	/* Left  margin column (in output line) */
int   imargin=0;	/* Indentation  column (in output line) */
int   rmargin=69;	/* Right margin column (in output line) */
/*
* Assorted global variables:
*/
Flag  adafl   = 0;	/* -- Ada-style comments */
Flag  cc_fl   = 0;	/* // C++-style comments */
Flag  adjust  = 1;	/* Adjust right margin */
Flag  blanksep= 0;	/* Add blank lines between paragraphs */
byte  Edgelc  = 0;	/* Wanted left-edge character */
byte  Edgerc  = 0;	/* Wanted right-edge character */
byte  edgelc  = 0;	/* Found left-edge character */
byte  edgerc  = 0;	/* Found right-edge character */
Flag  edgelf  = 1;	/* Look for left edge in input */
Flag  edgerf  = 1;	/* Look for right edge in input */
Flag  follow  = 1;	/* Indentation follows input */
Flag  lastline= 0;	/* True if last line of paragraph */
Flag  nulline = 0;	/* Input line contains no words */
Flag  fmtI    = 0;	/* Accept formatter ".I" commands */
Flag  fmtP    = 1;	/* Accept formatter ".P" commands */
/*
* Assorted global variables:
*/
int   ilines = 0;	/* Lines read so far */
int   ind = 0;		/* Current line's input indentation */
char  li[BUFSIZ];	/* Input line buffer */
char  lo[BUFSIZ];	/* Output line */
char* lp = li;		/* Current byte in li[] */
char* lz = li;		/* Last byte in li[] */
Flag  nl = 0;		/* Just read new line? */
int   num;	 		/* Scratch numeric value */
int   numbers = 0;	/* Number of numbers on command line */
int   olines = 0;	/* Lines written so far */
char *tt[2] = {"false","true"};
char  wi[100];		/* Input word buffer */
int   wo = 0;		/* Output width */
int   words = 0;	/* Words in current line */
char* wp = wi;		/* Current byte in wi[] */

extern char* lastfld();
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Here is where we come to life.  There are lots of options to parse.
*/
main(ac,av)
	int   ac;
	char**av;
{	int   a, i, n;
	int   c0, c1, c2, cz;
	int   r = 0;	/* Return value */
	char *pgname;
	char *p;

:1	ac = Vinit(ac,av);
	pgname = (char*)lastfld('/',av[0]);
	if (ISupper(*pgname)) rmargin = 77;
	switch (cz = pgname[strlen(pgname)-1]) {
	case 'c':
	case 'C': cc_fl = 1; break;		/* C++-style // comments */
	case 'h': rmargin = 59; break;	/* Help file: 60-char width.
	case 'H': rmargin = 60; break;	/* With the \n, it's one more */
	}
	for (a=1; a<ac; a++) {
:2		V3 "Arg%3d: \"%s\"",a,av[a] D;
		switch (c0 = av[a][0]) {
		case '-':
		case '+':
:2			V3 "Opt%3d: \"%s\"",a,av[a] D;
			switch (c1 = av[a][1]) {
			case '-':	/* -- flags Ada-style comments */
				adafl = 1;
:3				V3 "-- Looking for Ada comments." D;
				break;
			case 'a': case 'A':
				adjust = (av[a][0] == '+');
:2				V3 "Adjust: %s.",tt[adjust] D;
				break;
			case 'b': case 'B':
				blanksep = (c0 == '+');
:2				V3 "Blank separators: %s.",tt[blanksep] D;
				break;
			case 'e': case 'E':
				switch(c2 = av[a][2]) {
				case 'l': case 'L':
					edgelf = (c0 == '+');
					Edgelc = edgelc = av[a][3];
					break;
				case 'r': case 'R':
					edgerf = (c0 == '+');
					Edgerc = edgerc = av[a][3];
					break;
				}
:2				V3 "%c edge: %s.",c2,tt[follow] D;
				break;
			case 'f': case 'F':
				follow = (c0 == '+');
:2				V3 "Follow: %s.",tt[follow] D;
				break;
			case 'i': case 'I':
				fmtI = (c0 == '+');
:2				V3 "Accept .I commands: %s.",tt[fmtI] D;
				break;
:1			case 'd': case 'D':
:1				Vopt(av[a]+2);
:2				V3 "Debug level: %d.",Vlvl D;
:1				break;
			case 'p': case 'P':
				fmtP = (c0 == '+');
:2				V3 "Accept .P commands: %s.",tt[fmtP] D;
				break;
:2			default:
:2				V3 "Unknown option \"%s\" idnored.",av[a] D;
			}
			break;
		case '.':
:2			V3 "Arg%3d: \"%s\"",a,av[a] D;
			switch (c1 = av[a][1]) {
			case 'i': case 'I':
				fmtI = 1;
:2				V2 "Accept .i and .I commands." D;
				break;
			case 'p': case 'P':
				fmtP = 1;
:2				V2 "Accept .p and .P commands." D;
				break;
:2			default:
:2				V3 "Unknown option \"%s\" idnored.",av[a] D;
			}
			break;
		case '7': case '8': case '9':
		case '4': case '5': case '6':
		case '1': case '2': case '3':
		case '0':
			if ((i=sscanf(av[a],"%d",&num)) < 1) {
:8				V8 "### sscanf matched %d items! [Err %d=%s]",i,Erreason D;
				num = 60;
			}
:2			V2 "num = %d.",num D;
			switch (numbers) {
			case 0:
				rmargin = num;
:2				V2 "lmargin=%d imargin=%d rmargin=%d.",lmargin,imargin,rmargin D;
				break;
			case 1:
				lmargin = imargin = rmargin;
				rmargin = num;
				follow = 0;
:2				V2 "lmargin=%d imargin=%d rmargin=%d.",lmargin,imargin,rmargin D;
				break;
			case 2:
				imargin = rmargin;
				rmargin = num;
				follow = 0;
:2				V2 "lmargin=%d imargin=%d rmargin=%d.",lmargin,imargin,rmargin D;
				break;
:2			default:
:2				V1 "Too many numbers, %d ignored.",num D;
:2				V2 "lmargin=%d imargin=%d rmargin=%d.",lmargin,imargin,rmargin D;
			}
			++numbers;
			break;
		default:
			if (!Strcmp(av[a],"//")) {
				cc_fl = 1;
:3				V3 "// Looking for C++ comments." D;
				break;
			}
:2			V1 "Unknown arg \"%s\" ignored.",av[a] D;
		}
:2		V3 "Arg%3d: \"%s\" done.",a,av[a] D;
	}
	/*
	* Now for some sanity checks:
	*/
	if (rmargin > BUFSIZ) {
		rmargin = BUFSIZ;
:2		V1 "Adjusted right margin to m=%d.",rmargin D;
	}
	if (imargin >= rmargin) {
		imargin = rmargin / 2;
:2		V1 "Adjusted indent margin to %d.",imargin D;
	}
	if (lmargin >= rmargin) {
		lmargin = rmargin / 2;
:2		V1 "Adjusted left margin to %d.",lmargin D;
	}
	if (numbers < 3)
		imargin = lmargin;
	ind = imargin;
	/*
	* Our outer loop parses the input, word at a time, and feeds the words
	* to the putword routine for formatting and writing. This is basically
	* a standard sort of buffering scheme.   We  distinguish  two  special
	* returns:  End-Of-Paragraph and End-Of-Input.
	*/
	initline();
:5	V5 "Get the first word..." D;
loop:
	i = getword();
	if (i > 0) {
:5		V6 "word=\"%s\"",wi D;
		putword();
		Loop;
	}
:5	V5 "getword() failed, result=%d.",i D;
	switch (i) {
	case 0:
	case EOI:
	case EOP:
:2		V3 "End of paragraph." D;
		lastline = 1;	/* To suppress adjusting */
		if (words) {	/* Anything in current line? */
:2			V3 "Line has %d words.",words D;
			putline();	/* If so, force it out */
			initline();
		}
		if (i == EOI)
			Done;
		if (lmargin == imargin) {	/* Which paragraph style? */
			if (blanksep || nulline) {
:2				V3 "Blank line." D;
				edgelc = 0;
:6				V6 "edgelc=%d='%c'",edgelc,Dsp(edgelc) D;
				putline();		/* Blank line between paragraphs */
			}
		} else {
			ind = imargin;	/* First-line indentation is special */
		}
		initline();
		lastline = 0;		/* Will re-enable adjusting */
		break;
:2	default:
:2		V2 "Unknown value %d from getword.",i D;
	}
	Loop;
done:
:2	V3 "End of input." D;
	Exit(r);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
* This is where we insert blanks into the line to try  to  adjust  the  right *
* margin to where it should be. First we scan for assorted punctuation, after *
* which it is traditional to add extra spaces, and  pad  the  line  at  those *
* points.   Then  we  scan  the  line  from  one  end or the other (alternate *
* directions on alternate lines) and add more spaces, until we get the length *
* we want.  Bug: We don't recognize punctuation before parens, and perhaps we *
* should.  Maybe we'll do this in a future release.                           *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
adjline()
{	int  i, col1, col2, dir, ww;
	char c1, c2;
	int  side;

:1	Fenter("adjline");
:2	V4 "Adjust [%d,%d] to [%d,%d]",ind,wo,ind,rmargin D;
	if (side = olines & 1)
		 {col1 = ind+1; dir =  1; col2 =  wo-1;}	/* Left to right */
	else {col1 =  wo-1; dir = -1; col2 = ind+1;}	/* Right to left */
:2	V4 "col1=%d dir=%d col2=%d",col1,dir,col2 D;

	for (i = col1; ind < i && i < wo; i += dir) {
:5		V7 "Consider col %d.",i D;
		if ((c1=lo[i-1]) && (c1=='.' || c1=='?')	/* Deleted "|| c1==':')" */
		&&  (c2=lo[i]) && Isspace(c2)) {
:5			V6 "Shift right at col %d.",i D;
			shiftr(i);
			if (wo >= rmargin)
				break;
		}
	}
	if (lastline) {
:5		V5 "lastline=%d, done.",lastline D;
		Done;
	}
/*
* It isn't the last line of the paragraph.  We really want the  right  margin
* adjusted,  so we now run through the line inserting spaces until we get the
* desired right margin.  We had some interesting problems here avoiding loops
* for various degenerate cases.
*/
	while (wo < rmargin) {
:5		V7 "loop: wo=%d < rmargin=%d",wo,rmargin D;
		if (side = olines & 1)
			 {col1 = ind+1; dir =  1; col2 =  wo-1;}	/* Left to right */
		else {col1 =  wo-1; dir = -1; col2 = ind+1;}	/* Right to left */
		ww = wo;
		for (i = col1; ind < i && i < wo; i += dir) {
:5			V7 "Consider col %d.",i D;
			if ((c1=lo[i-1]) && !Isspace(c1) && (c2=lo[i]) && Isspace(c2)) {
:5				V6 "Shift right at column %d.",i D;
:5				V6 "Tail: \"%s\"",lo+i D;
				shiftr(i);
				if (wo >= rmargin)
					break;
			}
		}
		if (wo <= ww) {		/* If that didn't widen the line, we give up */
:3			V3 "Failed to adjust line." D;
			break;
		}
	}
done:
:1	Fexit;
	return(wo);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Shift the line right one at column c.
*/
shiftr(c)
{	int i;

:1	Fenter("shiftr");
	for (i=wo; i>c; i--)
		lo[i] = lo[i-1];
	lo[c] = ' ';
	lo[++wo] = 0;
:5	V7 "line: \"%s\"",lo D;
:1	Fexit;
	return(wo);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Extract the next "word" from the input.  The return value is the length of
* the word, or a code < 0 for various problems:
*   EOP  = end of paragraph.
*   EOI  = end of input.
*/
getword()
{	int  r=0;	/* Return value */
	int  c, i, j, k, n;

:1	Fenter("getword");
:5	V6 "Get new word." D;
loop:
	while ((lp < lz) && (c = *lp) && Isspace(c)) {
		++lp;
	}
	if (lp >= lz) {
:5		V5 "Get new line." D;
		if ((r = getaline()) < 0) {
:2			V3 "No more lines." D;
:1			Fexit;
			return(EOI);
		}
		if (r == 0) {
:2			V3 "Blank line." D;
:1			Fexit;
			return(EOP);
		}
		while ((lp < lz) && (c = *lp) && Isspace(c)) {
			++lp;
		}
		if (lp == li && li[0] == '.') {
:2			V3 "Possible formatter command \"%s\"",li D;
			if (fmtI && li[1] == 'I') {	/* Formatter .I command? */
:2				V3 ".I command \"%s\"",li D;
				n = sscanf(li+2,"%d %d %d",&i,&j,&k);
				switch(n) {
				 case 3: lmargin = i; imargin = j; rmargin = k; break;
				 case 2: lmargin = i; imargin = j; break;
				 case 1: lmargin = i; break;
				 case 0: break;
:1				 default:
:1					P1 "### Bad command \"%s\"",li D;
				}
				lp = lz;
:1				Fexit;
				return(EOP);
			}
			if (fmtP && li[1] == 'P') {		/* Formatter .P command? */
:2				V3 ".P command \"%s\"",li D;
				lp = lz;
				nulline = 1;
:1				Fexit;
				return(EOP);
			}
		}
:5		V5 "First nonspace at col %d.",lp-li D;
		if (follow && (i = lp - li) != ind) {
:2			V3 "Indent changed from %d to %d.",ind,i D;
			if (i >= rmargin) {
:2				P2 "%s	Line %d indented %d > rmargin=%d.",ilines,i,rmargin D;
				i = lmargin;
			}
			imargin = lmargin = i;
			if (words) {
:2				V3 "Line has %d words.",words D;
			/*	putline();	/* If so, force it out */
			/*	initline(); */
:1				Fexit;
				return(EOP);
			} else {
:5				V5 "Re-initialize current line." D;
				initline();
:1				Fexit;
				return(EOP);
			}
		}
	}
	wp = wi;
	while (lp < lz && (c = *lp++) && !Isspace(c))
		*wp++ = c;
	*wp = 0;
:5	V5 "Word: \"%s\"",wi D;
:5	V6 "Left: \"%s\"",lp D;
	r = wp - wi;
done:
fail:
:1	Fexit;
	return(r);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Fill the input buffer li[] with a new  line,  and  set  up  various  global
* variables  that  deal  with  it.   This  routine also makes note of various
* interesting things about the line, such as whether it has any  recognizable
* comment or box borders that should be propogated to the formatted output.
*/
int getaline()
{	int  c, i, n;
:1	Fenter("getaline");
:5	V5 "Read a line..." D;
	if (!(lp = Fgets(li,BUFSIZ,stdin))) {
:5		V5 "End of input." D;
:1		Fexit;
		return(EOI);
	}
	nl = 1;					/* Note new line just read */
	n = Strlen(li);
	while (n > 0 && (c = li[n-1]) && Isspace(c)) li[--n] = 0;
:5	V5 "edgelf=%d n=%d edgelc=%d='%c'",edgelf,n,edgelc,Dsp(edgelc) D;
	if (edgelf && n > 0) {	/* Checking for left edge of box? */
		if (Edgelc && (Edgelc == li[0])) {
			edgelc = lp[0];
:6			V6 "edgelc=%d='%c'",edgelc,Dsp(edgelc) D;
:5			V5 "Found edgelc=%02X='%c'",edgelc,Dsp(edgelc) D;
			++lp;
		} elsif (n < 2 || Isspace(li[1])) {
			if ((c=lp[0]) && !isalnum(c)) {
				edgelc = lp[0];
:6				V6 "edgelc=%d='%c'",edgelc,Dsp(edgelc) D;
:5				V5 "Found edgelc=%02X='%c'",edgelc,Dsp(edgelc) D;
				++lp;
			}
		} elsif (adafl && lp[0] == '-' && lp[1] == '-' && (n<3 ||Isspace(lp[2]))) {
			edgelc = lp[0];
:6			V6 "edgelc=%d='%c'",edgelc,Dsp(edgelc) D;
:5			V5 "Found Ada comment." D;
			lp += 2;
		} elsif (cc_fl && lp[0] == '/' && lp[1] == '/' && (n<3 ||Isspace(lp[2]))) {
			edgelc = lp[0];
:6			V6 "edgelc=%d='%c'",edgelc,Dsp(edgelc) D;
:5			V5 "Found C++ comment." D;
			lp += 2;
		} else {
			edgelc = 0;
:6			V6 "edgelc=%d='%c' [No left edge]",edgelc,Dsp(edgelc) D;
		}
		if (edgerf && n > 2) {	/* Checking for right edge of box? */
			if (Edgerc && (li[n-1] == Edgerc)) {	/* Right-edge char found at right */
				edgerc = Edgerc;
:5				V5 "Found edgerc=%02X='%c'",edgerc,Dsp(edgerc) D;
				li[n-1] = ' ';
			} elsif (edgerc && (li[n-1] == edgerc)) {	/* Right-edge char found at right */
:5				V5 "Found edgerc=%02X='%c'",edgerc,Dsp(edgerc) D;
				li[n-1] = ' ';
			} elsif (li[n-1] == edgelc) {	/* Left-edge char found at right edge */
				edgerc = edgelc;
:5				V5 "Found edgerc=edgelc=%02X='%c'",edgerc,Dsp(edgerc) D;
				li[n-1] = ' ';
			}
		}
		while (n > 0 && (c = li[n-1]) && Isspace(c))
			li[--n] = 0;	/* Trim away trailing white stuff */
	}
	/*
	* The above should have advanced lp past the left edge char, if any.
	*/
	lz = li + n;
	while (lp < lz && (c = *lp) && Isspace(c)) ++lp;
	indent = lp - li;
:2	V3 "Got %d-indented %d-byte line.",indent,n D;
	nulline = (lp >= lz);
:2	if (nulline) V3 "Null input line." D;
	if (indent >= rmargin) {
:2		P2 "%s	Line %d indented %d > rmargin=%d.",ilines,indent,rmargin D;
		indent = lmargin;
	}
:5	V5 "li: \"%s\"",li D;
	++ilines;
:1	Fexit;
	return(n);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Set up the output line buffer lo[] according to the current  format.
* This  routine  does  no  output; it just fills in the output buffer,
* making it look like a (formatted but) empty line.
*/
initline()
{
:1	Fenter("initline");
:5	V6 "Init line." D;
	for (wo=0; wo<ind; wo++)
		lo[wo] = ' ';	/* Indentation */
	lo[wo] = 0;			/* For debug output */
:5	V5 "Line: \"%s\"",lo D;
:5	H6(lo,wo+1,"initline");
	ind = lmargin;		/* Left margin for next line */
	words = 0;			/* Note line is empty */
:1	Fexit;
	return(wo);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Given a separator (usually '/'), return a pointer to the last field.  When *
* there  are no instances of the separator, we return a pointer to the first *
* char of the str, which will make filename scans work as you  expect.   You *
* might  expect  that  the  most  efficient  way to do this would be to scan *
* backwards from the end of the string, but with null-terminated strings, we *
* must first scan from the start to locate the end, so we might as well just *
* look for the sep and produce the result as a side-effect of this scan.     *
*                                                                            *
* A common mistake is to omit the first arg.  We can catch this, but there's *
* also the problem of getting a bogus str pointer. This is where it would be *
* really helpful if there were some way that we could  test  a  pointer  for *
* validity.  Unfortunately, this is totally impossible on most current cpus. *
* All we can do is hope that the caller sends us a valid  pointer,  and  die *
* ignominiously if str is garbage.                                           *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char* lastfld(sep,str)
	char  sep;
	char *str;
{	char  c, *p;
:2	if (B8(sep) != (int)sep) {	/* Kludge to try to spot pointer as 1st arg */
:2		V2 "### lastfld called with sep=%08X.",sep D;
:2		return((char*)((int)sep));
:2	}
	for (p = str; c = *str; ++str)
		if (c == sep)
			p = str + 1;
	return(p);
}
/*  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Write the current line buffer to output.  If we are adjusting the right
* margin, this is where it happens.  The return value is the line length.
*/
putline()
{	int c;

:1	Fenter("putline");
	while (wo > 0 && Isspace(lo[wo-1]))	/* Trim away final space(s) */
		lo[--wo] = 0;
:5	V5 "edgelf=%d edgelc=%d='%c'",edgelf,edgelc,Dsp(edgelc) D;
	if (edgelf && edgelc) {			/* Was there a left-edge char? */
		if (wo < 1) {				/* Blank line? */
:5			V5 "Add edgelc=%02X='%c' to blank line.",edgelc,Dsp(edgelc) D;
			lo[wo++] = edgelc;		/* If so, propogate the left edge */
			if (adafl && edgelc == '-')
				lo[wo++] = edgelc;	/* Do it again for Ada */
			if (cc_fl && edgelc == '/')
				lo[wo++] = edgelc;	/* Do it again for C++ */
		} elsif (Isspace(lo[0])) {	/* Left edge only if initial space */
:5			V5 "Add edgelc=%02X='%c' to %d-byte line.",edgelc,Dsp(edgelc),wo D;
			lo[0] = edgelc;
			if (adafl && edgelc == '-')
				lo[1] = edgelc;	/* Do it again for Ada */
			if (cc_fl && edgelc == '/')
				lo[1] = edgelc;	/* Do it again for C++ */
		}
	}
	if (adjust && wo < rmargin)		/* Adjust right margin */
		adjline();
:2	if (adjust && wo < rmargin)		/* Just curious */
:2		V2 "Line %d is still only %d bytes long.",olines,wo D;
	if (edgerf && edgerc) {			/* Is right edge wanted? */
		while (wo <= rmargin)		/* Pad to rmargin+1 */
			lo[wo++] = ' ';
:5		V5 "Add edgerc=%02X='%c'",edgerc,Dsp(edgerc) D;
#ifdef NOTNOW
		if (!Isspace(lo[wo-1]))
			lo[wo++] = ' ';
#endif
		lo[wo++] = edgerc;
		if (adafl && edgelc == '-')	/* Once more for Ada */
			lo[wo++] = edgerc;
		if (cc_fl && edgelc == '/')	/* Once more for C++ */
			lo[wo++] = edgerc;
	}
	while (wo > 0 && Isspace(lo[wo-1]))
		--wo;
	lo[wo++] = '\n';		/* Always add a linefeed */
:5	lo[wo] = '\0';			/* Null for debug output */
:5	V5 "Line: \"%s\"",lo D;
:5	H6(lo,wo+1,"Line");
	if (Write(1,lo,wo) > 0)	/* <====== OUTPUT */
		++olines;
:1	Fexit;
	return(wo);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Add one word to the oubput.  If it won't fit, this routine  triggers
* the  formatting  and  writing  of the line, and then initializes the
* output buffer to a new (empty) line.
*/
putword()
{	int  i, n;

:1	Fenter("putword");
	n = Strlen(wi);
:5	V5 "Put %d-byte word \"%s\" at col %d.",n,wi,wo D;
	if (wo + n > rmargin) {
:2		V4 "Put line of %d+%d bytes [> rmargin=%d]",wo,n,rmargin D;
		putline();
		initline();
	}
	for (i=0; i<n; i++)
		lo[wo++] = wi[i];	/* Copy word to line */
	lo[wo++] = ' ';
	lo[wo] = 0;		/* For debug output */
	++words;
:5	V6 "Line: \"%s\"",lo D;
:1	Fexit;
}
