/* ** tpr - text formatter ** Ken Yap, June 1981 */ #include <stdio.h> #include <ctype.h> #define min(a,b) (a<b?a:b) #define max(a,b) (a>b?a:b) #define skipbl(p) {while(*p == ' ' || *p == '\t')p++;} #define skipnbl(p) {while(*p != ' ' && *p != '\t' && *p != '\n')p++;} #define CHARNULL ((char *)NULL) #define NO 0 #define YES 1 #define COMMAND '.' #define PAGENUM '%' #define HUGE 10000 #define MAXIN 132 #define MAXOUT 132 #define MAXCHARS 14 #define MAXMAC 50 #define MAXPB 50 #define MAXTABS 20 #define PAGLEN 66 #define PAPERSIZE 65 #define M1DEF 3 #define M2DEF 1 #define M3DEF 1 #define M4DEF 3 #define PAGEWIDTH 60 #define ARABIC 0 #define ROMAN 1 struct linelink{ char *lineptr; struct linelink *lastline; }; struct macro{ char macnam[3]; struct linelink *macend; } macros[MAXMAC]; short maccnt = 0; /* counter for current macro */ char *pbptr[MAXMAC]; /* pointers to pushed back lines */ short pblev = 0; /* indicates level of macro nesting during collection */ char outbuf[MAXOUT]; /* lines to be filled collect here */ char *outp = outbuf; /* last char position in outbuf; init = 0 */ short outw = 0; /* width of text currenty in outbuf; init = 0 */ short outwds = 0; /* number of words in outbuf; init = 0 */ short curpag = 0; short newpag = 1; short lineno = 0; char blnkhdr[] = "\n"; struct envir{ short plval; short m1val; short m2val; short m3val; short m4val; short bottom; char *evenhdr,*oddhdr; char *evenftr,*oddftr; char comchr; char tabchr; char ubchr; short fill; short adjust; short numtyp; short lsval; short llval; short inval; short tival; short poval; short ceval; short ulval; short litval; short blval; short skpval; short tabpos[MAXTABS]; struct envir *lastenv; }; struct envir env = { PAGLEN, M1DEF, M2DEF, M3DEF, M4DEF, (PAGLEN-M3DEF-M4DEF), blnkhdr, blnkhdr, blnkhdr, blnkhdr, '.', '\t', ' ', YES, YES, ARABIC, 1, PAGEWIDTH, 0, 0, 0, 0, 0, 0, 0, 0, { 8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,0 }, NULL }; struct envir *curenv = NULL; struct cmdents{ char cmdname[3]; short notredefd; }; struct cmdents builtins[] ={ { "ad",YES }, { "ar",YES }, { "bl",YES }, { "bp",YES }, { "br",YES }, { "cc",YES }, { "ce",YES }, { "de",YES }, { "ef",YES }, { "eh",YES }, { "fi",YES }, { "fo",YES }, { "he",YES }, { "in",YES }, { "ix",YES }, { "li",YES }, { "ll",YES }, { "ls",YES }, { "m1",YES }, { "m2",YES }, { "m3",YES }, { "m4",YES }, { "na",YES }, { "ne",YES }, { "nf",YES }, { "of",YES }, { "oh",YES }, { "pl",YES }, { "po",YES }, { "re",YES }, { "ro",YES }, { "se",YES }, { "sk",YES }, { "sp",YES }, { "ta",YES }, { "tc",YES }, { "ti",YES }, { "ub",YES }, { "ul",YES } }; enum cmdnum {ADJ, ARA, BLN, BPG, BRE, CMC, CEN, DFN, EFO, EHD, FIL, FOT, HED, IND, INX, LIT, LNL, LNS, M1, M2, M3, M4, NAD, NED, NFL, OFO, OHD, PGL, POF, RNV, ROM, SNV, SKP, SPA, TCL, TCH, TMI, UBC, UDL, MAC, UNKNOWN}; short echodir = 0; char *filename = "stdin"; short fileline = 0; char *nomem = "Out of dynamic memory\n"; /* canned message */ /* ** main */ main(argc,argv) short argc; char **argv;{ char stdoutbuf[BUFSIZ]; FILE *file; for(argc--, argv++; argc > 0 && **argv == '-' && (*argv)[1] != '\0'; argc--, argv++) setoptions(*argv); setbuf(stdout,stdoutbuf); if(argc == 0) tpr(stdin,"stdin"); else for( ; argc > 0; argc--, argv++){ if(**argv == '-' && (*argv)[1] == '\0') tpr(stdin,"stdin"); else{ if((file = fopen(*argv,"r")) == NULL){ perror(*argv); continue; } tpr(file,*argv); fclose(file); } } if(lineno > 0) spc(HUGE); /* flush last output */ fflush(stdout); } setoptions(arg) char *arg;{ register char c; while((c = *++arg) != '\0') switch(c){ case 'd': /* echo directives */ echodir++; break; case 'e': /* divert errors */ close(2); if(creat(++arg,0600) < 0){ open("/dev/tty",1); perror(arg); exit(1); } return; default: fprintf(stderr,"Usage - see manual (ho, ho)\n"); exit(1); } } /* ** tpr - here is the main routine for each file ** the name is passed along so that the 'include' directive ** can call 'tpr' recursively */ tpr(file,name) FILE *file; char *name;{ register char *savename; register short saveline; char inbuf[MAXIN]; char *ngetl(); savename = filename; /* save old name */ filename = name; /* make new one available for error routine */ saveline = fileline; /* and line number */ fileline = 0; while(ngetl(inbuf,file) != CHARNULL){ ++fileline; if(*inbuf == env.comchr) /* it's a command */ cmd(inbuf,file); else /* it's text */ text(inbuf); } filename = savename; /* restore name and linenumber */ fileline = saveline; } /* ** error - prints filename and linenumber */ error(msg,arg) char *msg,*arg;{ /*ROB fprintf(stderr,"At line %d in file %s: ",fileline,filename); fprintf(stderr,msg,arg); */ } /* ** blnk - space n lines (to new page if necessary, cf spc) */ blnk(n) short n;{ register short i; linebreak(); while(n > 0){ if(lineno > env.bottom){ pfoot(); lineno = 0; } if(lineno == 0) phead(); i = min(n,env.bottom + 1 - lineno); skip(i); n -= i; lineno += i; } if(lineno > env.bottom) pfoot(); } /* ** linebreak - end current filled line */ linebreak(){ if(outp != outbuf){ *outp++ = '\n'; *outp = '\0'; put(outbuf); } outp = outbuf; outw = 0; outwds = 0; } /* ** centre - centre a line by setting tival */ center(buf) char *buf;{ short width(); env.tival = max((env.llval + env.tival - width(buf))/2,0); } /* ** cmd - perform formatting command */ cmd(buf,file) char *buf; FILE *file;{ enum cmdnum comtyp(),ct; register short spval,val; short macnum; char argtyp; short getval(),set(); char *gettl(); if(echodir) putdir(buf); ct = comtyp(buf,&macnum); val = 0; val = getval(buf,&argtyp); switch(ct){ case SPA: spval = set(0,val,argtyp,1,0,HUGE); spc(spval); break; case IND: case INX: if(ct == IND) linebreak(); env.inval = set(env.inval,val,argtyp,0,0,env.llval - 1); env.tival = env.inval; break; case TMI: linebreak(); env.tival = set(env.tival,val,argtyp,0,0,env.llval); break; case CEN: linebreak(); env.ceval = set(env.ceval,val,argtyp,1,0,HUGE); break; case UDL: env.ulval = set(env.ulval,val,argtyp,0,1,HUGE); break; case FIL: linebreak(); env.fill = YES; break; case NFL: linebreak(); env.fill = NO; break; case BRE: linebreak(); break; case BLN: env.blval = set(env.blval,val,argtyp,1,0,HUGE); blnk(env.blval); env.blval = 0; break; case NED: if(val > env.bottom - lineno + 1) spc(HUGE); break; case LNS: env.lsval = set(env.lsval,val,argtyp,1,1,HUGE); break; case LNL: env.llval = set(env.llval,val,argtyp,PAGEWIDTH,env.tival + 1,HUGE); break; case PGL: env.plval = set(env.plval,val,argtyp,PAGLEN, env.m1val + env.m2val + env.m3val + env.m4val + 1,HUGE); env.bottom = env.plval - env.m3val - env.m4val; break; case BPG: if(lineno > 0) spc(HUGE); curpag = set(curpag,val,argtyp,curpag + 1, - HUGE,HUGE); newpag = curpag; break; case HED: env.evenhdr = env.oddhdr = gettl(buf); break; case FOT: env.evenftr = env.oddftr = gettl(buf); break; case EHD: env.evenhdr = gettl(buf); break; case EFO: env.evenftr = gettl(buf); break; case OHD: env.oddhdr = gettl(buf); break; case OFO: env.oddftr = gettl(buf); break; case NAD: env.adjust = NO; break; case ADJ: env.adjust = YES; break; case ROM: env.numtyp = ROMAN; break; case ARA: env.numtyp = ARABIC; break; case LIT: env.litval = set(env.litval,val,argtyp,1,0,HUGE); break; case M1: env.m1val = set(env.m1val,val,argtyp,M1DEF, 0,env.plval - (env.m2val + env.m3val + env.m4val + 1)); break; case M2: env.m2val = set(env.m2val,val,argtyp,M2DEF, 0,env.plval - (env.m1val + env.m3val + env.m4val + 1)); break; case M3: env.m3val = set(env.m3val,val,argtyp,M3DEF, 0,env.plval - (env.m1val + env.m2val + env.m4val + 1)); env.bottom = env.plval - env.m3val - env.m4val; break; case M4: env.m4val = set(env.m4val,val,argtyp,M4DEF, 0,env.plval - (env.m1val + env.m2val + env.m3val + 1)); env.bottom = env.plval - env.m3val - env.m4val; break; case CMC: if(argtyp != '\n') env.comchr = argtyp; else env.comchr = COMMAND; break; case POF: env.poval = set(env.poval,val,argtyp,0,0,PAPERSIZE); break; case SKP: env.skpval = set(env.skpval,val,argtyp,1,0,HUGE); break; case DFN: getmac(buf,file); break; case TCH: if(argtyp != '\n') env.tabchr = argtyp; else env.tabchr = '\t'; break; case TCL: tabcol(buf); break; case UBC: if(argtyp != '\n') env.ubchr = argtyp; else env.ubchr = ' '; break; case RNV: if(val <= 0) val = 1; resenv(val); break; case SNV: savenv(); break; case MAC: expand(macnum); break; case UNKNOWN: error("Unrecognised directive:\n%s",buf); return; } } /* ** comtyp - decode command type */ enum cmdnum comtyp(buf,macnum) char *buf; short *macnum;{ register char a,b; register struct cmdents *p; register char *q; register short l,h,m; a = buf[1]; b = buf[2]; for(l = 0, h = (sizeof(builtins)/sizeof(builtins[0])) - 1; l <= h; ){ p = &builtins[m = (l + h) / 2]; if(a < p->cmdname[0] || a <= p->cmdname[0] && b < p->cmdname[1]) h = m - 1; else if(a > p->cmdname[0] || a <= p->cmdname[0] && b > p->cmdname[1]) l = m + 1; else break; } if(l <= h && p->notredefd) return((enum cmdnum)m); for(l = maccnt - 1; l >= 0; l--){ q = macros[l].macnam; if(a == *q++ && b == *q){ *macnum = l; return(MAC); } } return(UNKNOWN); } /* ** expand - pushback macro definition onto input */ expand(macnum) short macnum;{ register struct linelink *lp; for(lp = macros[macnum].macend; lp != NULL; lp = lp->lastline){ if(pblev > MAXPB){ error("Not enough pushback space\n",CHARNULL); break; /* to catch stack overflow */ } pbptr[pblev] = lp->lineptr; pblev++; } } /* ** getmac - collect macro */ getmac(buf,file) char *buf; FILE *file;{ register char *p; register struct macro *mp; register struct linelink *lp; register short dotlev; enum cmdnum ct; short macnum; char line[MAXIN]; enum cmdnum comtyp(); char *strcpy(); char *malloc(); skipnbl(buf); skipbl(buf); if(*buf == '\n'){ error("Missing macro name\n",CHARNULL); return; } if(maccnt >= MAXMAC){ error("Too many macro definitions",CHARNULL); return; } mp = ¯os[maccnt]; p = mp->macnam; if((ct = comtyp(buf - 1,&macnum)) != UNKNOWN && ct != MAC) builtins[(int)ct].notredefd = NO; *p++ = *buf++; /* record name */ *p++ = *buf++; *p = '\0'; mp->macend = NULL; dotlev = 1; do{ if((p = ngetl(line,file)) == CHARNULL) break; /* unexpected EOF */ ++fileline; if(*p++ == env.comchr){ if(*p == '.') dotlev--; else if(comtyp(line,&macnum) == DFN) dotlev++; /* included .de */ } if(dotlev > 0){ if((lp = (struct linelink *)malloc((unsigned)sizeof(*mp->macend))) == NULL){ error(nomem,CHARNULL); break; } lp->lastline = mp->macend; mp->macend = lp; if((lp->lineptr = malloc((unsigned)(strlen(line) + 1))) == NULL){ error(nomem,CHARNULL); break; } strcpy(lp->lineptr,line); } }while(dotlev > 0); maccnt++; } /* ** getseg - puts out part of header */ char *getseg(buf,copy,copyend,term,pageno) char *buf,*copy,*copyend,term; short pageno;{ register short i; register char *p; short itorom(); p = buf; for( ;copy != copyend && *p != term && *p != '\0' && *p != '\n'; p++){ if(*p == PAGENUM){ if(env.numtyp == ARABIC){ sprintf(copy, "%d", pageno); i=strlen(copy); } else i = itorom(pageno,copy,min(MAXCHARS,(short)(copyend - copy))); copy += i; } else *copy++ = *p; } if(*p == term) p++; *copy = '\0'; return(p); } /* ** gettl - copy title from buf to ttl */ char *gettl(buf) char *buf;{ register char *p,*q; char *strcpy(); char *malloc(); p = buf; skipnbl(p); skipbl(p); /* find argument */ if((q = malloc((unsigned)(strlen(p) + 1))) == NULL){ error(nomem,CHARNULL); return(q); } strcpy(q,p); return(q); } /* ** getval - evaluate optional numeric argument */ short getval(buf,argtyp) char *buf; char *argtyp;{ int atoi(); skipnbl(buf); skipbl(buf); /* find argument */ *argtyp = *buf; if(!isdigit(*buf)) buf++; return(atoi(buf)); } /* ** getwrd - get a non - blank word from instr(i) to out, increment i */ char *getwrd(instr,out) char *instr,*out;{ register char c; register char *p,*q; p = instr; q = out; while((*p == ' ' || *p == '\t') && *p != env.tabchr) p++; instr = p; if(*p == env.tabchr) *q++ = *p; else{ while((c = *p) != '\0' && c != ' ' && c != '\t' && c != '\n' && c != env.tabchr){ *q++ = *p++; } } *q = '\0'; return(p == instr ? NULL : p); } /* ** itorom - converts integer to roman numerals */ short itorom(num,str,flen) char *str; short num,flen;{ register short i,j; char *p; static short romval[] = { 1000,500,100,50,10,5,1,0 }; static short reltab[] = { 2,1,2,1,2,1,1,0 }; static char romlet[] = "mdclxvi0"; p = str; if(num < 0 && flen > 1){ num = -num; *p++ = '-'; } for(i = 0; num > 0; i++){ while(num >= romval[i]){ num -= romval[i]; *p++ = romlet[i]; } j = i + reltab[i]; if(num >= (romval[i] - romval[j])){ num -= (romval[i] - romval[j]); *p++ = romlet[j]; *p++ = romlet[i]; } } *p = '\0'; return((short)(p - str)); } /* ** leadbl - delete leading blanks, set tival */ leadbl(buf) char *buf;{ register char *p; linebreak(); p = buf; skipnbl(buf); if(*buf != '\n') env.tival = (buf - p); while(*buf != '\0') /* move line to left */ *p++ = *buf++; *p = '\0'; } /* ** nextab - returns position of next tab stop */ short nextab(pos) short pos;{ register short i,k; for(i = 0; i < MAXTABS; i++){ if(env.tabpos[i] == 0) break; if(env.tabpos[i] > pos){ k = env.tabpos[i]; return(k); } } k = pos + 1; return(k); } /* ** ngetl - gets line from input or pushback buffer */ char *ngetl(buf,file) char *buf; FILE *file;{ char *fgets(); char *strcpy(); if(pblev <= 0) return(fgets(buf,MAXIN,file)); else{ pblev--; strcpy(buf,pbptr[pblev]); } return(buf); } /* ** pfoot - put out page footer */ pfoot(){ skip(env.m3val); if(env.m4val > 0){ puttl(curpag % 2 ? env.oddftr : env.evenftr, curpag); skip(env.m4val - 1); } } /* ** phead - put out page header */ phead(){ curpag = newpag; newpag++; if(env.m1val > 0){ skip(env.m1val - 1); puttl(curpag % 2 ? env.oddhdr : env.evenhdr, curpag); } skip(env.m2val); lineno = env.m1val + env.m2val + 1; } /* ** put - put out line with proper spacing and indenting */ put(buf) char *buf;{ short nextab(); register char c; register short col,i; if(lineno == 0 || lineno > env.bottom) phead(); for(i = (env.tival + env.poval); i--; ) putchar(' '); col = env.tival; env.tival = env.inval; for(; (c = *buf) != '\0'; buf++){ if(c == env.ubchr) c = ' '; /* put blanks instead of blank replacement */ if(c == env.tabchr){ i = nextab(col); /* nextab wants last used column */ for(; col < i; col++) putchar(' '); continue; } else if(c == '\b') col--; else col++; putchar(c); } skip(min(env.lsval - 1,env.bottom - lineno)); lineno += env.lsval; if(lineno > env.bottom){ pfoot(); if(env.skpval > 0) skpage(env.skpval); env.skpval = 0; } } /* ** putdir - output a directive */ putdir(buf) char *buf;{ fprintf(stderr,"%.10s",buf); /* first 10 chars */ } /* ** puttl - put out title line with optional page number */ puttl(buf,pageno) char *buf; short pageno;{ char copy[MAXOUT],term; char *getseg(); register char *p; register short i,col,newcol; for(i = env.poval; i--; ) putchar(' '); term = *buf; if(term == '\n'){ putchar('\n'); return; } col = 1; p = buf + 1; p = getseg(p,copy,©[MAXOUT],term,pageno); printf("%s", copy); col += strlen(copy); buf = p; p = getseg(p,copy,©[MAXOUT],term,pageno); newcol = (env.llval - strlen(copy))/2 + 1; /* start of centre */ for(; col < newcol; col++) putchar(' '); printf("%s", copy); col += strlen(copy); p = getseg(p,copy,©[MAXOUT],term,pageno); newcol = env.llval - strlen(copy) + 1; /* start of right */ for(; col < newcol; col++) putchar(' '); printf("%s",copy); putchar('\n'); } /* ** putwrd - put a word in outbuf; includes margin justification */ putwrd(wrdbuf) char *wrdbuf;{ char *strcpy(); register short l,w,lnval,nextra; short width(), nextab(); int strlen(); lnval = env.llval - env.tival; if(*wrdbuf == env.tabchr){ outw = nextab(outw + env.tival) - env.tival; /* because outw floats from the indent */ /* and nextab is absolute */ if(outp != outbuf && outw > lnval){ linebreak(); outw = nextab(outw + env.tival) - env.tival; } *++outp = env.tabchr; outwds = 0; /* adjust from next word */ } else{ w = width(wrdbuf); l = strlen(wrdbuf); if(outp != outbuf && (outw + w > lnval || /* too big */ (char *)(outp + l) >= &outbuf[MAXOUT])){ --outp; /* we put in a blank earlier */ if(env.adjust == YES){ nextra = lnval - outw + 1; spread(outp,nextra,outwds); outp += nextra; } linebreak(); /* flush previous line */ } strcpy(outp,wrdbuf); outp += l; *outp++ = ' '; /* blank between words */ outw += w + 1; /* 1 for blank */ outwds++; } } /* ** resenv - restore environment n levels back */ resenv(n) short n;{ register struct envir *ep,*tp; linebreak(); /* to flush any latent output */ for(ep = curenv; ep != NULL && --n > 0; free(tp)) ep = (tp = ep)->lastenv; if(ep != NULL){ env = *ep; curenv = ep->lastenv; free(ep); } } /* ** savenv - keep environment for later restoration */ savenv(){ register struct envir *ep; if((ep = (struct envir *)malloc((unsigned)sizeof(env))) == NULL){ error(nomem,CHARNULL); return; } *ep = env; /* structure copy */ ep->lastenv = curenv; curenv = ep; } /* ** set - set parameter and check range */ short set(param,val,argtyp,defval,minval,maxval) short param,val,defval,minval,maxval; char argtyp;{ switch(argtyp){ case '\n': param = defval; break; case '+': param = param + val; break; case '-': param = param - val; break; default: param = val; } param = min(param,maxval); param = max(param,minval); return(param); } /* ** skip - output n blank lines */ skip(n) short n;{ while(n-- > 0) putchar('\n'); } /* ** skpage - skip n pages */ skpage(n) short n;{ while(n-- > 0){ phead(); skip(env.bottom + 1 - lineno); lineno = env.bottom + 1; pfoot(); } } /* ** spc - space n lines or to bottom of page (cf blnk) */ spc(n) short n;{ linebreak(); if(lineno > env.bottom) return; if(lineno == 0) phead(); skip(min(n,env.bottom + 1 - lineno)); lineno += n; if(lineno > env.bottom){ pfoot(); if(env.skpval > 0) skpage(env.skpval); env.skpval = 0; } } /* ** spread - spread words to justify right margin */ spread(ptr,nextra,nwrds) char *ptr; short nextra,nwrds;{ register char *p,*q; register short nb,nholes; static short dir = 0; if(nextra <= 0 || nwrds <= 1) return; dir = !dir; /* reverse previous direction */ nholes = nwrds - 1; p = ptr - 1; q = ptr + nextra; *q-- = '\0'; while(p < q){ if((*q = *p) == ' '){ nb = dir ? (nextra - 1) / nholes + 1 : nextra / nholes; nextra -= nb; nholes--; while(nb-- > 0){ *--q = ' '; } } p--; q--; } } /* ** tabcol - enters pseudotab stops, checking validity */ tabcol(buf) char *buf;{ int atoi(); register short tp,incr,val; for(tp = 0; tp < MAXTABS - 1; tp++){ skipnbl(buf); skipbl(buf); if(*buf == '\n') break; /* end of list */ incr = *buf++ == '+' ? YES : NO; val = atoi(buf); if(incr == YES && tp > 1) /* relative tab */ val = env.tabpos[tp - 1] + val; env.tabpos[tp] = val; if(val < 0 || (tp > 1 && val < env.tabpos[tp - 1])) tp--; } env.tabpos[tp] = 0; /* end of list */ } /* ** text - process text lines */ text(inbuf) char *inbuf;{ register char *p; char wrdbuf[MAXIN]; char *getwrd(); if(env.litval > 0){ put(inbuf); env.litval--; return; } if(*inbuf == ' ' || *inbuf == '\n') leadbl(inbuf); /* move left, set tival */ if(env.ulval > 0){ /* underlining */ underl(inbuf,wrdbuf,&wrdbuf[MAXIN]); env.ulval--; } if(env.ceval > 0){ /* centering */ center(inbuf); put(inbuf); env.ceval--; } else if(*inbuf == '\n') /* all blank line */ put(inbuf); else if(env.fill == NO) /* unfilled text */ put(inbuf); else for(p = inbuf; (p = getwrd(p,wrdbuf)) != NULL;) putwrd(wrdbuf); } /* ** underl - underline a line */ underl(buf,tbuf,tend) char *buf,*tbuf,*tend;{ register char c; register char *p,*q; char *strcpy(); p = buf; q = tbuf; while(*p != '\n' && q < tend){ if(isalnum(c = *p++)){ *q++ = '_'; *q++ = '\b'; } *q++ = c; } *q++ = '\n'; *q = '\0'; strcpy(buf,tbuf); /* copy it back to buf */ } /* ** width - compute width of character string */ short width(buf) char *buf;{ register short i; register char c; for(i = 0; (c = *buf) != '\0'; buf++){ if(c == '\b') i--; else if(c != '\n') i++; } return(i); }