xref: /minix/minix/commands/cawf/cawf.c (revision 9f988b79)
1 /*
2  *	cawf - a C version of Henry Spencer's awf(1), the Amazingly
3  *	       Workable (text) Formatter
4  *
5  *	V. Abell, Purdue University Computing Center
6  */
7 
8 /*
9  *	Copyright (c) 1991 Purdue University Research Foundation,
10  *	West Lafayette, Indiana 47907.  All rights reserved.
11  *
12  *	Written by Victor A. Abell <abe@mace.cc.purdue.edu>,  Purdue
13  *	University Computing Center.  Not derived from licensed software;
14  *	derived from awf(1) by Henry Spencer of the University of Toronto.
15  *
16  *	Permission is granted to anyone to use this software for any
17  *	purpose on any computer system, and to alter it and redistribute
18  *	it freely, subject to the following restrictions:
19  *
20  *	1. The author is not responsible for any consequences of use of
21  *	   this software, even if they arise from flaws in it.
22  *
23  *	2. The origin of this software must not be misrepresented, either
24  *	   by explicit claim or by omission.  Credits must appear in the
25  *	   documentation.
26  *
27  *	3. Altered versions must be plainly marked as such, and must not
28  *	   be misrepresented as being the original software.  Credits must
29  *	   appear in the documentation.
30  *
31  *	4. This notice may not be removed or altered.
32  */
33 
34 static char Version[] = "4.0";
35 
36 #include "cawf.h"
37 
38 #include <sys/stat.h>
39 #include <unistd.h>
40 #ifndef	UNIX
41 #include <io.h>
42 #include <process.h>
43 #include <string.h>
44 #include <sys\types.h>
45 #include <sys\stat.h>
46 #endif
47 
48 
49 int main(int argc, char *argv[]) {
50 	char *ep;               	/* environment pointer */
51 	int fff = 0;			/* final form feed status */
52 	char **files;			/* file names */
53 	int help = 0;			/* help status */
54 	int i;	               		/* temporary index */
55 	size_t l;                       /* length */
56 	char *lib = CAWFLIB;		/* library path */
57 	int libl;			/* library path length */
58 	int mac = 0;			/* macro specification status */
59 	int nf = 0;             	/* number of files */
60 	char *np;               	/* name pointer */
61 	int pc;                 	/* prolog count */
62 	struct stat sbuf;               /* stat buffer */
63 /*
64  * Save program name.
65  */
66 	if ((Pname = strrchr(argv[0], '\\')) != NULL)
67 		Pname++;
68 	else if ((Pname = strrchr(argv[0], '/')) != NULL)
69 		Pname++;
70 	else
71 		Pname = argv[0];
72 /*
73  * Set error file stream pointer.
74  */
75 	Efs = stderr;
76 /*
77  * Get library name.
78  */
79 	if ((np = getenv("CAWFLIB")) != NULL)
80 		lib = np;
81 	libl = strlen(lib);
82 /*
83  * Get device file name.
84  */
85 	for (ep = getenv("TERM");; ep = NULL) {
86 		if (ep == NULL || *ep == '\0')
87 			ep = "dumb";
88 		l = libl + 1 + strlen(ep) + strlen(".dev") + 1;
89 		if ((np = malloc(l)) == NULL)
90 			Error(FATAL, NOLINE,
91 				" no string space for device file: ", ep);
92 		(void) sprintf(np, "%s/%s.dev", lib, ep);
93 		if (stat(np, &sbuf) == 0)
94 			break;
95 		if (strcmp(ep, "dumb") == 0)
96 			Error(FATAL, NOLINE, " no dumb.dev file in ", lib);
97 		(void) free(np);
98 	}
99 	if ((files = malloc((argc + 2) * sizeof(files[0]))) == NULL)
100 		Error(FATAL, NOLINE, " no space for file list",
101 			NULL);
102 	files[nf++] = np;
103 /*
104  * Get common text file name.
105  */
106 	l = libl + 1 + strlen("common") + 1;
107 	if ((np = malloc(l)) == NULL)
108 		Error(FATAL, NOLINE, " no string space for common file name",
109 			NULL);
110 	(void) sprintf(np, "%s/common", lib);
111 	files[nf++] = np;
112 /*
113  * Process options.
114  */
115 	while ((i = getopt(argc, argv, "c:d:ef:hm:")) != EOF) {
116 		switch (i) {
117 	/*
118 	 * -c<device_configuration_file_path>>
119 	 */
120 		case 'c':
121 			Devconf = optarg;
122 			break;
123 	/*
124 	 * -d<output_device_name> -- define output device name
125 	 *
126 	 * The default output device name is NORMAL -- i.e., a device that
127 	 * does bold face with backspace and overprinting and italic face with
128 	 * underscore.  NORMAL is usually a terminal device.
129 	 *
130 	 * There is a built-in device, named ANSI, that does bold face with
131 	 * the ANSI shadow mode and italic face with the ANSI underscore mode.
132 	 * ANSI is normally a terminal device that supports the ANSI shadow
133 	 * and underscore modes.
134 	 *
135 	 * There is a built-in output device, named NONE, that does nothing
136 	 * at all for the bold or italic faces.  This is usually a terminal
137 	 * device.
138 	 *
139 	 * All other device names must match a stanza in the device
140 	 * configuration file.
141 	 */
142 		case 'd':
143 			Device = optarg;
144 			break;
145 	/*
146 	 * -e -- eject: issue final form feed
147 	 */
148 		case 'e':
149 			fff = 1;
150 			break;
151 	/*
152 	 * -f<output_device_font_name> -- define font name for the output
153 	 *				  device (from device configuration
154 	 *				  file)
155 	 */
156 		case 'f':
157 			Devfont = optarg;
158 			break;
159 	/*
160 	 * -h -- display help (usage)
161 	 */
162 		case 'h':
163 			help = 1;
164 			break;
165 	/*
166 	 * -m<macro_file_name>
167 	 *
168 	 *  Special support is provided for -man, -me and -ms.
169 	 */
170 		case 'm':
171 			if (mac) {
172 				Error(WARN, NOLINE,
173 					"multiple macro file declaration",
174 					NULL);
175 				break;
176 			}
177 			l = libl + 2 + strlen(optarg) + strlen(".mac") + 1;
178 			if ((np = malloc(l)) == NULL)
179 				Error(FATAL, NOLINE, " no string space for ",
180 					argv[1]);
181 			(void) sprintf(np, "%s/m%s.mac", lib, optarg);
182 			files[nf++] = np;
183 			if (strcmp(optarg, "an") == 0)
184 				Marg = MANMACROS;
185 			else if (strcmp(optarg, "s") == 0
186 			     ||  strcmp(optarg, "e") == 0)
187 				Marg = MSMACROS;
188 			mac++;
189 			break;
190 	/*
191 	 * Option not recognized by getopt().
192 	 */
193 		case '?':
194 			Err = 1;
195 		}
196 	}
197 	if (Defdev())
198 		Err++;
199 	if (help || Err) {
200 	  (void) fprintf(stderr,
201 	    "%s %s usage: [-c<c>] [-d<d>] [-e] [-f<f>] [-h] [-m<m>] file...\n",
202 		Pname, Version);
203 	  (void) fprintf(stderr,
204 	    "\t-c<c>     <c> is the device configuration file path\n");
205 	  (void) fprintf(stderr,
206 	    "\t-d<d>     <d> is the output device name\n");
207 	  (void) fprintf(stderr,
208 	    "\t          (default = NORMAL, using \\b for bold and italic)\n");
209 	  (void) fprintf(stderr,
210 	    "\t          (built-ins = ANSI, NONE and NORMAL)\n");
211 	  (void) fprintf(stderr,
212 	    "\t-e        issue eject after last page\n");
213 	  (void) fprintf(stderr,
214 	    "\t-f<f>     <f> is the output device font name\n");
215 	  (void) fprintf(stderr,
216 	    "\t-h        display help (this output)\n");
217 	  (void) fprintf(stderr,
218 	    "\t-m<m>     m<m> is the macro file name\n");
219 	  (void) fprintf(stderr,
220 	    "\tfile ...  source file names\n");
221 	  exit(Err);
222 	}
223 	if (mac == 0) {
224 
225 	    /*
226 	     * No macroes - enable Bold, Italic, Roman and Courier fonts.
227 	     */
228 		for (i = 0; Fcode[i].nm; i++) {
229 			switch (Fcode[i].nm) {
230 			case 'B':
231 			case 'I':
232 			case 'R':
233 			case 'C':
234 				Fcode[i].status = '1';
235 			}
236 		}
237 	}
238 /*
239  * Add user-supplied file names.
240  */
241 	pc = nf;
242 	if (optind >= argc) {
243 		files[nf++] = NULL;       /* STDIN */
244 	} else {
245 		while (optind < argc)
246 			files[nf++] = argv[optind++];
247 	}
248 /*
249  * Make sure all input files are accessible.
250  */
251 	for (i = 0; i < nf; i++) {
252 		if (files[i] != NULL) {
253 			if (stat(files[i], &sbuf) != 0)
254 				Error(WARN, NOLINE, " can't find ", files[i]);
255 		}
256 	}
257 	if (Err)
258 		exit(1);
259 /*
260  * Miscellaneous initialization.
261  */
262 
263 	for (i = 0; ; i++) {
264 		if (Pat[i].re == NULL)
265 			break;
266 		if ((Pat[i].pat = regcomp(Pat[i].re)) == NULL)
267 			Error(WARN, NOLINE, Pat[i].re, " regcomp failure");
268 	}
269 	if ((i = Findscale((int)'n', 0.0, 0)) < 0)
270 		Error(WARN, NOLINE, " can't find Scale['n']", NULL);
271 	Scalen = Scale[i].val;
272 	if ((i = Findscale((int)'u', 0.0, 0)) < 0)
273 		Error(WARN, NOLINE, " can't find Scale['u']", NULL);
274 	Scaleu = Scale[i].val;
275 	if ((i = Findscale((int)'v', 0.0, 0)) < 0)
276 		Error(WARN, NOLINE, " can't find Scale['v']", NULL);
277 	Scalev = Scale[i].val;
278 	(void) Findstr((unsigned char *)"CH", (unsigned char *)"= % -", 1);
279 	Cont = Newstr((unsigned char *)" ");
280 	Contlen = 1;
281 	if ((Trtbl = (unsigned char *)malloc(256)) == NULL)
282 		Error(WARN, NOLINE, " can't allocate translate table space",
283 			NULL);
284 	else {
285 		*Trtbl = ' ';
286 		for (i = 1; i < 256; i++)
287 			Trtbl[i] = (unsigned char) i;
288 	}
289 	if (Err)
290 		exit(1);
291 /*
292  * Here begins pass1 of awf - reading input lines and expanding macros.
293  */
294 
295 /*
296  * Output prolog.
297  */
298 	if (Fstr.i) {
299 		for (i = 0; i < Fstr.il; i++) {
300 			Charput((int)Fstr.i[i]);
301 		}
302 	}
303 	Macro((unsigned char *)".^x");
304 	Macro((unsigned char *)".^b");
305 	Macro((unsigned char *)".^# 1 <prolog>");
306 /*
307  * Read input files.
308  */
309 	for (i = 0; i < nf; i++) {
310 		Dowarn = (i >= pc);
311 		if (files[i] == NULL) {
312 			np = "stdin";
313 			Ifs = stdin;
314 		} else {
315 #ifdef	UNIX
316 			if ((Ifs = fopen(files[i], "r")) == NULL)
317 #else
318 			if ((Ifs = fopen(files[i], "rt")) == NULL)
319 #endif
320 				Error(FATAL, NOLINE, " can't open ", files[i]);
321 			np = files[i];
322 		}
323 		if (i >= pc) {
324 			(void) sprintf((char *)Line, ".^# 1 %s", np);
325 			Macro(Line);
326 			NR = 0;
327 		}
328 		Fsp = 0;
329 		do {
330 			while (fgets((char *)Line, MAXLINE, Ifs) != NULL) {
331 				NR++;
332 				if ((np = strrchr((char *)Line, '\n')) != NULL)
333 					*np = '\0';
334 				else
335 					Line[MAXLINE-1] = '\0';
336 				Macro(Line);
337 			}
338 			if (i >= pc)
339 				Macro((unsigned char *)".^e");
340 			if (Ifs != stdin)
341 				(void) fclose(Ifs);
342 			if (Fsp > 0) {
343 				Free(&Inname);
344 				Inname = Inn_stk[Fsp-1];
345 				NR = NR_stk[Fsp-1];
346 				Ifs = Ifs_stk[Fsp-1];
347 			}
348 		} while (Fsp-- > 0);
349 	}
350 	Macro(NULL);
351 	if (fff)
352 		Charput((int)'\f');
353 	exit(Err);
354 }
355 
356 
357 /*
358  * Macro(inp) - process a possible macro statement
359  *		pass non-macros and macros alike to pass 2
360  */
361 
362 void Macro(unsigned char *inp) { /* possible macro statement pointer */
363 	unsigned char c[2];		/* characters */
364 	int endm;			/* end of macro status */
365 	FILE *fs;			/* temporary file stream */
366 	int i, j, k;                    /* temporary indexes */
367 	int mx;                         /* Macrotab[] index */
368 	int req;			/* request character status */
369 	unsigned char *s1, *s2;		/* temporary string pointers */
370 
371 	if (inp == NULL) {
372 		Pass2(NULL);
373 		return;
374 	}
375 	req = (*inp == '.' || *inp == '\'') ? 1 : 0;
376 /*
377  * Check for file name designator.
378  */
379 	if (req && inp[1] == '^' && inp[2] == '#') {
380 		Free(&Inname);
381 		Inname = Field(3, inp, 1);
382 		F = NULL;
383 		Pass2(inp);
384 		return;
385 	}
386 /*
387  * Check for source command - "^[.']so".
388  */
389 	if (req && inp[1] == 's' && inp[2] == 'o') {
390 		if ((s1 = Field(2, inp, 1)) == NULL) {
391 			Error(WARN, LINE, " no file specified", NULL);
392 			return;
393 		}
394 		if ((fs = fopen((char *)s1, "r")) == NULL) {
395 			Error(WARN, LINE, " can't open", NULL);
396 			return;
397 		}
398 		if (Fsp >= MAXFSTK) {
399 			(void) fclose(fs);
400 			Error(WARN, LINE, " nesting too deep", NULL);
401 			return;
402 		}
403 		Ifs_stk[Fsp] = Ifs;
404 		Ifs = fs;
405 		Inn_stk[Fsp] = Inname;
406 		Inname = F;
407 		F = NULL;
408 		NR_stk[Fsp++] = NR;
409 		NR = 0;
410 		return;
411 	}
412  /*
413   * Check for ignore.
414   */
415 	if (req && inp[1] == 'i' && inp[2] == 'g') {
416 		while (fgets((char *)inp, MAXLINE, Ifs) != NULL) {
417 			NR++;
418 			if (inp[0] == '.' && inp[1] == '.') break;
419 		}
420 		return;
421 	}
422  /*
423   * Check for start of macro definition.
424   */
425 	if (req && inp[1] == 'd' && inp[2] == 'e') {
426 		if (inp[3] != ' ' || inp[4] == '\0') {
427 			Error(WARN, LINE, " illegal macro definition", NULL);
428 			return;
429 		}
430 		c[0] = inp[4];
431 		c[1] = inp[5];
432 		Curmx = Findmacro(c, 1);
433 		return;
434 	}
435 /*
436  * Check for macro text.  Remove double backslashes.
437  */
438 	if (req && (inp[1] == '\0' || (inp[2] == '\0' && inp[0] == inp[1])))
439 		endm = 1;
440 	else
441 		endm = 0;
442 	if (Curmx >= 0 && !endm) {
443 		if (Mtx >= MAXMTXT)
444 			Error(FATAL, LINE, " out of macro text space", NULL);
445 		if ((s1 = (unsigned char *)strchr((char *)inp, '\\')) == NULL)
446 			Macrotxt[Mtx] = Newstr(inp);
447 		else {
448 			for (s1 = Pass1ln, s2 = inp;; s1++) {
449 				if ((*s1 = *s2++) == '\0')
450 					break;
451 				if (*s1 == '\\' && *s2 == '\\')
452 					s2++;
453 			}
454 			Macrotxt[Mtx] = Newstr(Pass1ln);
455 		}
456 		if (Macrotab[Curmx].bx == -1)
457 			Macrotab[Curmx].bx = Mtx;
458 		Mtx++;
459 		Macrotab[Curmx].ct++;
460 		return;
461 	}
462 /*
463  * Check for end of macro.
464  */
465 	if (Curmx >= 0 && endm) {
466 		Curmx = -1;
467 		(void) sprintf((char *)Pass1ln, ".^# %d %s", NR, Inname);
468 		Pass2(Pass1ln);
469 		return;
470 	}
471  /*
472   * Check for conditionals and macro expansions.
473   */
474 	if (req
475 	&&  (((mx = Findmacro(inp+1, 0)) != -1) || regexec(Pat[0].pat, inp))) {
476 		Expand(inp);
477 		return;
478 	}
479 /*
480  * None of the above: forward the line.
481  */
482 	Pass2(inp);
483 }
484