488 lines
12 KiB
C
488 lines
12 KiB
C
/*
|
|
* cawf - a C version of Henry Spencer's awf(1), the Amazingly
|
|
* Workable (text) Formatter
|
|
*
|
|
* V. Abell, Purdue University Computing Center
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 1991 Purdue University Research Foundation,
|
|
* West Lafayette, Indiana 47907. All rights reserved.
|
|
*
|
|
* Written by Victor A. Abell <abe@mace.cc.purdue.edu>, Purdue
|
|
* University Computing Center. Not derived from licensed software;
|
|
* derived from awf(1) by Henry Spencer of the University of Toronto.
|
|
*
|
|
* Permission is granted to anyone to use this software for any
|
|
* purpose on any computer system, and to alter it and redistribute
|
|
* it freely, subject to the following restrictions:
|
|
*
|
|
* 1. The author is not responsible for any consequences of use of
|
|
* this software, even if they arise from flaws in it.
|
|
*
|
|
* 2. The origin of this software must not be misrepresented, either
|
|
* by explicit claim or by omission. Credits must appear in the
|
|
* documentation.
|
|
*
|
|
* 3. Altered versions must be plainly marked as such, and must not
|
|
* be misrepresented as being the original software. Credits must
|
|
* appear in the documentation.
|
|
*
|
|
* 4. This notice may not be removed or altered.
|
|
*/
|
|
|
|
static char Version[] = "4.0";
|
|
|
|
#include "cawf.h"
|
|
|
|
#include <sys/stat.h>
|
|
#ifndef UNIX
|
|
#include <io.h>
|
|
#include <process.h>
|
|
#include <string.h>
|
|
#include <sys\types.h>
|
|
#include <sys\stat.h>
|
|
#endif
|
|
|
|
|
|
main(argc, argv)
|
|
int argc;
|
|
char *argv[];
|
|
{
|
|
char *ep; /* environment pointer */
|
|
int fff = 0; /* final form feed status */
|
|
char **files; /* file names */
|
|
int help = 0; /* help status */
|
|
int i; /* temporary index */
|
|
size_t l; /* length */
|
|
char *lib = CAWFLIB; /* library path */
|
|
int libl; /* library path length */
|
|
int mac = 0; /* macro specification status */
|
|
int nf = 0; /* number of files */
|
|
char *np; /* name pointer */
|
|
int pc; /* prolog count */
|
|
struct stat sbuf; /* stat buffer */
|
|
/*
|
|
* Save program name.
|
|
*/
|
|
if ((Pname = strrchr(argv[0], '\\')) != NULL)
|
|
Pname++;
|
|
else if ((Pname = strrchr(argv[0], '/')) != NULL)
|
|
Pname++;
|
|
else
|
|
Pname = argv[0];
|
|
/*
|
|
* Set error file stream pointer.
|
|
*/
|
|
Efs = stderr;
|
|
/*
|
|
* Get library name.
|
|
*/
|
|
if ((np = getenv("CAWFLIB")) != NULL)
|
|
lib = np;
|
|
libl = strlen(lib);
|
|
/*
|
|
* Get device file name.
|
|
*/
|
|
for (ep = getenv("TERM");; ep = NULL) {
|
|
if (ep == NULL || *ep == '\0')
|
|
ep = "dumb";
|
|
l = libl + 1 + strlen(ep) + strlen(".dev") + 1;
|
|
if ((np = malloc(l)) == NULL)
|
|
Error(FATAL, NOLINE,
|
|
" no string space for device file: ", ep);
|
|
(void) sprintf(np, "%s/%s.dev", lib, ep);
|
|
if (stat(np, &sbuf) == 0)
|
|
break;
|
|
if (strcmp(ep, "dumb") == 0)
|
|
Error(FATAL, NOLINE, " no dumb.dev file in ", lib);
|
|
(void) free(np);
|
|
}
|
|
if ((files = malloc((argc + 2) * sizeof(files[0]))) == NULL)
|
|
Error(FATAL, NOLINE, " no space for file list",
|
|
NULL);
|
|
files[nf++] = np;
|
|
/*
|
|
* Get common text file name.
|
|
*/
|
|
l = libl + 1 + strlen("common") + 1;
|
|
if ((np = malloc(l)) == NULL)
|
|
Error(FATAL, NOLINE, " no string space for common file name",
|
|
NULL);
|
|
(void) sprintf(np, "%s/common", lib);
|
|
files[nf++] = np;
|
|
/*
|
|
* Process options.
|
|
*/
|
|
while ((i = getopt(argc, argv, "c:d:ef:hm:")) != EOF) {
|
|
switch (i) {
|
|
/*
|
|
* -c<device_configuration_file_path>>
|
|
*/
|
|
case 'c':
|
|
Devconf = optarg;
|
|
break;
|
|
/*
|
|
* -d<output_device_name> -- define output device name
|
|
*
|
|
* The default output device name is NORMAL -- i.e., a device that
|
|
* does bold face with backspace and overprinting and italic face with
|
|
* underscore. NORMAL is usually a terminal device.
|
|
*
|
|
* There is a built-in device, named ANSI, that does bold face with
|
|
* the ANSI shadow mode and italic face with the ANSI underscore mode.
|
|
* ANSI is normally a terminal device that supports the ANSI shadow
|
|
* and underscore modes.
|
|
*
|
|
* There is a built-in output device, named NONE, that does nothing
|
|
* at all for the bold or italic faces. This is usually a terminal
|
|
* device.
|
|
*
|
|
* All other device names must match a stanza in the device
|
|
* configuration file.
|
|
*/
|
|
case 'd':
|
|
Device = optarg;
|
|
break;
|
|
/*
|
|
* -e -- eject: issue final form feed
|
|
*/
|
|
case 'e':
|
|
fff = 1;
|
|
break;
|
|
/*
|
|
* -f<output_device_font_name> -- define font name for the output
|
|
* device (from device configuration
|
|
* file)
|
|
*/
|
|
case 'f':
|
|
Devfont = optarg;
|
|
break;
|
|
/*
|
|
* -h -- display help (usage)
|
|
*/
|
|
case 'h':
|
|
help = 1;
|
|
break;
|
|
/*
|
|
* -m<macro_file_name>
|
|
*
|
|
* Special support is provided for -man, -me and -ms.
|
|
*/
|
|
case 'm':
|
|
if (mac) {
|
|
Error(WARN, NOLINE,
|
|
"multiple macro file declaration",
|
|
NULL);
|
|
break;
|
|
}
|
|
l = libl + 2 + strlen(optarg) + strlen(".mac") + 1;
|
|
if ((np = malloc(l)) == NULL)
|
|
Error(FATAL, NOLINE, " no string space for ",
|
|
argv[1]);
|
|
(void) sprintf(np, "%s/m%s.mac", lib, optarg);
|
|
files[nf++] = np;
|
|
if (strcmp(optarg, "an") == 0)
|
|
Marg = MANMACROS;
|
|
else if (strcmp(optarg, "s") == 0
|
|
|| strcmp(optarg, "e") == 0)
|
|
Marg = MSMACROS;
|
|
mac++;
|
|
break;
|
|
/*
|
|
* Option not recognized by getopt().
|
|
*/
|
|
case '?':
|
|
Err = 1;
|
|
}
|
|
}
|
|
if (Defdev())
|
|
Err++;
|
|
if (help || Err) {
|
|
(void) fprintf(stderr,
|
|
"%s %s usage: [-c<c>] [-d<d>] [-e] [-f<f>] [-h] [-m<m>] file...\n",
|
|
Pname, Version);
|
|
(void) fprintf(stderr,
|
|
"\t-c<c> <c> is the device configuration file path\n");
|
|
(void) fprintf(stderr,
|
|
"\t-d<d> <d> is the output device name\n");
|
|
(void) fprintf(stderr,
|
|
"\t (default = NORMAL, using \\b for bold and italic)\n");
|
|
(void) fprintf(stderr,
|
|
"\t (built-ins = ANSI, NONE and NORMAL)\n");
|
|
(void) fprintf(stderr,
|
|
"\t-e issue eject after last page\n");
|
|
(void) fprintf(stderr,
|
|
"\t-f<f> <f> is the output device font name\n");
|
|
(void) fprintf(stderr,
|
|
"\t-h display help (this output)\n");
|
|
(void) fprintf(stderr,
|
|
"\t-m<m> m<m> is the macro file name\n");
|
|
(void) fprintf(stderr,
|
|
"\tfile ... source file names\n");
|
|
exit(Err);
|
|
}
|
|
if (mac == 0) {
|
|
|
|
/*
|
|
* No macroes - enable Bold, Italic, Roman and Courier fonts.
|
|
*/
|
|
for (i = 0; Fcode[i].nm; i++) {
|
|
switch (Fcode[i].nm) {
|
|
case 'B':
|
|
case 'I':
|
|
case 'R':
|
|
case 'C':
|
|
Fcode[i].status = '1';
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* Add user-supplied file names.
|
|
*/
|
|
pc = nf;
|
|
if (optind >= argc) {
|
|
files[nf++] = NULL; /* STDIN */
|
|
} else {
|
|
while (optind < argc)
|
|
files[nf++] = argv[optind++];
|
|
}
|
|
/*
|
|
* Make sure all input files are accessible.
|
|
*/
|
|
for (i = 0; i < nf; i++) {
|
|
if (files[i] != NULL) {
|
|
if (stat(files[i], &sbuf) != 0)
|
|
Error(WARN, NOLINE, " can't find ", files[i]);
|
|
}
|
|
}
|
|
if (Err)
|
|
exit(1);
|
|
/*
|
|
* Miscellaneous initialization.
|
|
*/
|
|
|
|
for (i = 0; ; i++) {
|
|
if (Pat[i].re == NULL)
|
|
break;
|
|
if ((Pat[i].pat = regcomp(Pat[i].re)) == NULL)
|
|
Error(WARN, NOLINE, Pat[i].re, " regcomp failure");
|
|
}
|
|
if ((i = Findscale((int)'n', 0.0, 0)) < 0)
|
|
Error(WARN, NOLINE, " can't find Scale['n']", NULL);
|
|
Scalen = Scale[i].val;
|
|
if ((i = Findscale((int)'u', 0.0, 0)) < 0)
|
|
Error(WARN, NOLINE, " can't find Scale['u']", NULL);
|
|
Scaleu = Scale[i].val;
|
|
if ((i = Findscale((int)'v', 0.0, 0)) < 0)
|
|
Error(WARN, NOLINE, " can't find Scale['v']", NULL);
|
|
Scalev = Scale[i].val;
|
|
(void) Findstr((unsigned char *)"CH", (unsigned char *)"= % -", 1);
|
|
Cont = Newstr((unsigned char *)" ");
|
|
Contlen = 1;
|
|
if ((Trtbl = (unsigned char *)malloc(256)) == NULL)
|
|
Error(WARN, NOLINE, " can't allocate translate table space",
|
|
NULL);
|
|
else {
|
|
*Trtbl = ' ';
|
|
for (i = 1; i < 256; i++)
|
|
Trtbl[i] = (unsigned char) i;
|
|
}
|
|
if (Err)
|
|
exit(1);
|
|
/*
|
|
* Here begins pass1 of awf - reading input lines and expanding macros.
|
|
*/
|
|
|
|
/*
|
|
* Output prolog.
|
|
*/
|
|
if (Fstr.i) {
|
|
for (i = 0; i < Fstr.il; i++) {
|
|
Charput((int)Fstr.i[i]);
|
|
}
|
|
}
|
|
Macro((unsigned char *)".^x");
|
|
Macro((unsigned char *)".^b");
|
|
Macro((unsigned char *)".^# 1 <prolog>");
|
|
/*
|
|
* Read input files.
|
|
*/
|
|
for (i = 0; i < nf; i++) {
|
|
Dowarn = (i >= pc);
|
|
if (files[i] == NULL) {
|
|
np = "stdin";
|
|
Ifs = stdin;
|
|
} else {
|
|
#ifdef UNIX
|
|
if ((Ifs = fopen(files[i], "r")) == NULL)
|
|
#else
|
|
if ((Ifs = fopen(files[i], "rt")) == NULL)
|
|
#endif
|
|
Error(FATAL, NOLINE, " can't open ", files[i]);
|
|
np = files[i];
|
|
}
|
|
if (i >= pc) {
|
|
(void) sprintf((char *)Line, ".^# 1 %s", np);
|
|
Macro(Line);
|
|
NR = 0;
|
|
}
|
|
Fsp = 0;
|
|
do {
|
|
while (fgets((char *)Line, MAXLINE, Ifs) != NULL) {
|
|
NR++;
|
|
if ((np = strrchr((char *)Line, '\n')) != NULL)
|
|
*np = '\0';
|
|
else
|
|
Line[MAXLINE-1] = '\0';
|
|
Macro(Line);
|
|
}
|
|
if (i >= pc)
|
|
Macro((unsigned char *)".^e");
|
|
if (Ifs != stdin)
|
|
(void) fclose(Ifs);
|
|
if (Fsp > 0) {
|
|
Free(&Inname);
|
|
Inname = Inn_stk[Fsp-1];
|
|
NR = NR_stk[Fsp-1];
|
|
Ifs = Ifs_stk[Fsp-1];
|
|
}
|
|
} while (Fsp-- > 0);
|
|
}
|
|
Macro(NULL);
|
|
if (fff)
|
|
Charput((int)'\f');
|
|
exit(Err);
|
|
}
|
|
|
|
|
|
/*
|
|
* Macro(inp) - process a possible macro statement
|
|
* pass non-macros and macros alike to pass 2
|
|
*/
|
|
|
|
void
|
|
Macro(inp)
|
|
unsigned char *inp; /* possible macro statement pointer */
|
|
{
|
|
unsigned char c[2]; /* characters */
|
|
int endm; /* end of macro status */
|
|
FILE *fs; /* temporary file stream */
|
|
int i, j, k; /* temporary indexes */
|
|
int mx; /* Macrotab[] index */
|
|
int req; /* request character status */
|
|
unsigned char *s1, *s2; /* temporary string pointers */
|
|
|
|
if (inp == NULL) {
|
|
Pass2(NULL);
|
|
return;
|
|
}
|
|
req = (*inp == '.' || *inp == '\'') ? 1 : 0;
|
|
/*
|
|
* Check for file name designator.
|
|
*/
|
|
if (req && inp[1] == '^' && inp[2] == '#') {
|
|
Free(&Inname);
|
|
Inname = Field(3, inp, 1);
|
|
F = NULL;
|
|
Pass2(inp);
|
|
return;
|
|
}
|
|
/*
|
|
* Check for source command - "^[.']so".
|
|
*/
|
|
if (req && inp[1] == 's' && inp[2] == 'o') {
|
|
if ((s1 = Field(2, inp, 1)) == NULL) {
|
|
Error(WARN, LINE, " no file specified", NULL);
|
|
return;
|
|
}
|
|
if ((fs = fopen((char *)s1, "r")) == NULL) {
|
|
Error(WARN, LINE, " can't open", NULL);
|
|
return;
|
|
}
|
|
if (Fsp >= MAXFSTK) {
|
|
(void) fclose(fs);
|
|
Error(WARN, LINE, " nesting too deep", NULL);
|
|
return;
|
|
}
|
|
Ifs_stk[Fsp] = Ifs;
|
|
Ifs = fs;
|
|
Inn_stk[Fsp] = Inname;
|
|
Inname = F;
|
|
F = NULL;
|
|
NR_stk[Fsp++] = NR;
|
|
NR = 0;
|
|
return;
|
|
}
|
|
/*
|
|
* Check for ignore.
|
|
*/
|
|
if (req && inp[1] == 'i' && inp[2] == 'g') {
|
|
while (fgets((char *)inp, MAXLINE, Ifs) != NULL) {
|
|
NR++;
|
|
if (inp[0] == '.' && inp[1] == '.') break;
|
|
}
|
|
return;
|
|
}
|
|
/*
|
|
* Check for start of macro definition.
|
|
*/
|
|
if (req && inp[1] == 'd' && inp[2] == 'e') {
|
|
if (inp[3] != ' ' || inp[4] == '\0') {
|
|
Error(WARN, LINE, " illegal macro definition", NULL);
|
|
return;
|
|
}
|
|
c[0] = inp[4];
|
|
c[1] = inp[5];
|
|
Curmx = Findmacro(c, 1);
|
|
return;
|
|
}
|
|
/*
|
|
* Check for macro text. Remove double backslashes.
|
|
*/
|
|
if (req && (inp[1] == '\0' || (inp[2] == '\0' && inp[0] == inp[1])))
|
|
endm = 1;
|
|
else
|
|
endm = 0;
|
|
if (Curmx >= 0 && !endm) {
|
|
if (Mtx >= MAXMTXT)
|
|
Error(FATAL, LINE, " out of macro text space", NULL);
|
|
if ((s1 = (unsigned char *)strchr((char *)inp, '\\')) == NULL)
|
|
Macrotxt[Mtx] = Newstr(inp);
|
|
else {
|
|
for (s1 = Pass1ln, s2 = inp;; s1++) {
|
|
if ((*s1 = *s2++) == '\0')
|
|
break;
|
|
if (*s1 == '\\' && *s2 == '\\')
|
|
s2++;
|
|
}
|
|
Macrotxt[Mtx] = Newstr(Pass1ln);
|
|
}
|
|
if (Macrotab[Curmx].bx == -1)
|
|
Macrotab[Curmx].bx = Mtx;
|
|
Mtx++;
|
|
Macrotab[Curmx].ct++;
|
|
return;
|
|
}
|
|
/*
|
|
* Check for end of macro.
|
|
*/
|
|
if (Curmx >= 0 && endm) {
|
|
Curmx = -1;
|
|
(void) sprintf((char *)Pass1ln, ".^# %d %s", NR, Inname);
|
|
Pass2(Pass1ln);
|
|
return;
|
|
}
|
|
/*
|
|
* Check for conditionals and macro expansions.
|
|
*/
|
|
if (req
|
|
&& (((mx = Findmacro(inp+1, 0)) != -1) || regexec(Pat[0].pat, inp))) {
|
|
Expand(inp);
|
|
return;
|
|
}
|
|
/*
|
|
* None of the above: forward the line.
|
|
*/
|
|
Pass2(inp);
|
|
}
|