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