1 /*- 2 * Copyright (c) 1990, 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * 5 * This code is derived from software contributed to Berkeley by 6 * Michael Rendell of the Memorial University of Newfoundland. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. All advertising materials mentioning features or use of this software 17 * must display the following acknowledgement: 18 * This product includes software developed by the University of 19 * California, Berkeley and its contributors. 20 * 4. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37 #ifndef lint 38 static const char copyright[] = 39 "@(#) Copyright (c) 1990, 1993, 1994\n\ 40 The Regents of the University of California. All rights reserved.\n"; 41 #endif /* not lint */ 42 43 #ifndef lint 44 #if 0 45 static char sccsid[] = "@(#)col.c 8.5 (Berkeley) 5/4/95"; 46 #endif 47 static const char rcsid[] = 48 "$FreeBSD: src/usr.bin/col/col.c,v 1.6.6.4 2001/08/02 01:27:12 obrien Exp $"; 49 #endif /* not lint */ 50 51 #include <ctype.h> 52 #include <err.h> 53 #include <string.h> 54 #include <stdio.h> 55 #include <stdlib.h> 56 #include <unistd.h> 57 #include <locale.h> 58 59 #define BS '\b' /* backspace */ 60 #define TAB '\t' /* tab */ 61 #define SPACE ' ' /* space */ 62 #define NL '\n' /* newline */ 63 #define CR '\r' /* carriage return */ 64 #define ESC '\033' /* escape */ 65 #define SI '\017' /* shift in to normal character set */ 66 #define SO '\016' /* shift out to alternate character set */ 67 #define VT '\013' /* vertical tab (aka reverse line feed) */ 68 #define RLF '\007' /* ESC-07 reverse line feed */ 69 #define RHLF '\010' /* ESC-010 reverse half-line feed */ 70 #define FHLF '\011' /* ESC-011 forward half-line feed */ 71 72 /* build up at least this many lines before flushing them out */ 73 #define BUFFER_MARGIN 32 74 75 typedef char CSET; 76 77 typedef struct char_str { 78 #define CS_NORMAL 1 79 #define CS_ALTERNATE 2 80 short c_column; /* column character is in */ 81 CSET c_set; /* character set (currently only 2) */ 82 char c_char; /* character in question */ 83 } CHAR; 84 85 typedef struct line_str LINE; 86 struct line_str { 87 CHAR *l_line; /* characters on the line */ 88 LINE *l_prev; /* previous line */ 89 LINE *l_next; /* next line */ 90 int l_lsize; /* allocated sizeof l_line */ 91 int l_line_len; /* strlen(l_line) */ 92 int l_needs_sort; /* set if chars went in out of order */ 93 int l_max_col; /* max column in the line */ 94 }; 95 96 LINE *alloc_line __P((void)); 97 void dowarn __P((int)); 98 void flush_line __P((LINE *)); 99 void flush_lines __P((int)); 100 void flush_blanks __P((void)); 101 void free_line __P((LINE *)); 102 int main __P((int, char **)); 103 void usage __P((void)); 104 105 CSET last_set; /* char_set of last char printed */ 106 LINE *lines; 107 int compress_spaces; /* if doing space -> tab conversion */ 108 int fine; /* if `fine' resolution (half lines) */ 109 int max_bufd_lines; /* max # lines to keep in memory */ 110 int nblank_lines; /* # blanks after last flushed line */ 111 int no_backspaces; /* if not to output any backspaces */ 112 int pass_unknown_seqs; /* pass unknown control sequences */ 113 114 #define PUTC(ch) \ 115 do { \ 116 if (putchar(ch) == EOF) \ 117 errx(1, "write error"); \ 118 } while (0) 119 120 int 121 main(argc, argv) 122 int argc; 123 char **argv; 124 { 125 int ch; 126 CHAR *c; 127 CSET cur_set; /* current character set */ 128 LINE *l; /* current line */ 129 int extra_lines; /* # of lines above first line */ 130 int cur_col; /* current column */ 131 int cur_line; /* line number of current position */ 132 int max_line; /* max value of cur_line */ 133 int this_line; /* line l points to */ 134 int nflushd_lines; /* number of lines that were flushed */ 135 int adjust, opt, warned; 136 137 (void)setlocale(LC_CTYPE, ""); 138 139 max_bufd_lines = 128; 140 compress_spaces = 1; /* compress spaces into tabs */ 141 while ((opt = getopt(argc, argv, "bfhl:px")) != -1) 142 switch (opt) { 143 case 'b': /* do not output backspaces */ 144 no_backspaces = 1; 145 break; 146 case 'f': /* allow half forward line feeds */ 147 fine = 1; 148 break; 149 case 'h': /* compress spaces into tabs */ 150 compress_spaces = 1; 151 break; 152 case 'l': /* buffered line count */ 153 if ((max_bufd_lines = atoi(optarg)) <= 0) 154 errx(1, "bad -l argument %s", optarg); 155 break; 156 case 'p': /* pass unknown control sequences */ 157 pass_unknown_seqs = 1; 158 break; 159 case 'x': /* do not compress spaces into tabs */ 160 compress_spaces = 0; 161 break; 162 case '?': 163 default: 164 usage(); 165 } 166 167 if (optind != argc) 168 usage(); 169 170 /* this value is in half lines */ 171 max_bufd_lines *= 2; 172 173 adjust = cur_col = extra_lines = warned = 0; 174 cur_line = max_line = nflushd_lines = this_line = 0; 175 cur_set = last_set = CS_NORMAL; 176 lines = l = alloc_line(); 177 178 while ((ch = getchar()) != EOF) { 179 if (!isgraph(ch)) { 180 switch (ch) { 181 case BS: /* can't go back further */ 182 if (cur_col == 0) 183 continue; 184 --cur_col; 185 continue; 186 case CR: 187 cur_col = 0; 188 continue; 189 case ESC: /* just ignore EOF */ 190 switch(getchar()) { 191 case RLF: 192 cur_line -= 2; 193 break; 194 case RHLF: 195 cur_line--; 196 break; 197 case FHLF: 198 cur_line++; 199 if (cur_line > max_line) 200 max_line = cur_line; 201 } 202 continue; 203 case NL: 204 cur_line += 2; 205 if (cur_line > max_line) 206 max_line = cur_line; 207 cur_col = 0; 208 continue; 209 case SPACE: 210 ++cur_col; 211 continue; 212 case SI: 213 cur_set = CS_NORMAL; 214 continue; 215 case SO: 216 cur_set = CS_ALTERNATE; 217 continue; 218 case TAB: /* adjust column */ 219 cur_col |= 7; 220 ++cur_col; 221 continue; 222 case VT: 223 cur_line -= 2; 224 continue; 225 } 226 if (!pass_unknown_seqs) 227 continue; 228 } 229 230 /* Must stuff ch in a line - are we at the right one? */ 231 if (cur_line != this_line - adjust) { 232 LINE *lnew; 233 int nmove; 234 235 adjust = 0; 236 nmove = cur_line - this_line; 237 if (!fine) { 238 /* round up to next line */ 239 if (cur_line & 1) { 240 adjust = 1; 241 nmove++; 242 } 243 } 244 if (nmove < 0) { 245 for (; nmove < 0 && l->l_prev; nmove++) 246 l = l->l_prev; 247 if (nmove) { 248 if (nflushd_lines == 0) { 249 /* 250 * Allow backup past first 251 * line if nothing has been 252 * flushed yet. 253 */ 254 for (; nmove < 0; nmove++) { 255 lnew = alloc_line(); 256 l->l_prev = lnew; 257 lnew->l_next = l; 258 l = lines = lnew; 259 extra_lines++; 260 } 261 } else { 262 if (!warned++) 263 dowarn(cur_line); 264 cur_line -= nmove; 265 } 266 } 267 } else { 268 /* may need to allocate here */ 269 for (; nmove > 0 && l->l_next; nmove--) 270 l = l->l_next; 271 for (; nmove > 0; nmove--) { 272 lnew = alloc_line(); 273 lnew->l_prev = l; 274 l->l_next = lnew; 275 l = lnew; 276 } 277 } 278 this_line = cur_line + adjust; 279 nmove = this_line - nflushd_lines; 280 if (nmove >= max_bufd_lines + BUFFER_MARGIN) { 281 nflushd_lines += nmove - max_bufd_lines; 282 flush_lines(nmove - max_bufd_lines); 283 } 284 } 285 /* grow line's buffer? */ 286 if (l->l_line_len + 1 >= l->l_lsize) { 287 int need; 288 289 need = l->l_lsize ? l->l_lsize * 2 : 90; 290 if ((l->l_line = realloc(l->l_line, 291 (unsigned)need * sizeof(CHAR))) == NULL) 292 err(1, (char *)NULL); 293 l->l_lsize = need; 294 } 295 c = &l->l_line[l->l_line_len++]; 296 c->c_char = ch; 297 c->c_set = cur_set; 298 c->c_column = cur_col; 299 /* 300 * If things are put in out of order, they will need sorting 301 * when it is flushed. 302 */ 303 if (cur_col < l->l_max_col) 304 l->l_needs_sort = 1; 305 else 306 l->l_max_col = cur_col; 307 cur_col++; 308 } 309 if (max_line == 0) 310 exit(0); /* no lines, so just exit */ 311 312 /* goto the last line that had a character on it */ 313 for (; l->l_next; l = l->l_next) 314 this_line++; 315 flush_lines(this_line - nflushd_lines + extra_lines + 1); 316 317 /* make sure we leave things in a sane state */ 318 if (last_set != CS_NORMAL) 319 PUTC('\017'); 320 321 /* flush out the last few blank lines */ 322 nblank_lines = max_line - this_line; 323 if (max_line & 1) 324 nblank_lines++; 325 else if (!nblank_lines) 326 /* missing a \n on the last line? */ 327 nblank_lines = 2; 328 flush_blanks(); 329 exit(0); 330 } 331 332 void 333 flush_lines(nflush) 334 int nflush; 335 { 336 LINE *l; 337 338 while (--nflush >= 0) { 339 l = lines; 340 lines = l->l_next; 341 if (l->l_line) { 342 flush_blanks(); 343 flush_line(l); 344 } 345 nblank_lines++; 346 if (l->l_line) 347 (void)free(l->l_line); 348 free_line(l); 349 } 350 if (lines) 351 lines->l_prev = NULL; 352 } 353 354 /* 355 * Print a number of newline/half newlines. If fine flag is set, nblank_lines 356 * is the number of half line feeds, otherwise it is the number of whole line 357 * feeds. 358 */ 359 void 360 flush_blanks() 361 { 362 int half, i, nb; 363 364 half = 0; 365 nb = nblank_lines; 366 if (nb & 1) { 367 if (fine) 368 half = 1; 369 else 370 nb++; 371 } 372 nb /= 2; 373 for (i = nb; --i >= 0;) 374 PUTC('\n'); 375 if (half) { 376 PUTC('\033'); 377 PUTC('9'); 378 if (!nb) 379 PUTC('\r'); 380 } 381 nblank_lines = 0; 382 } 383 384 /* 385 * Write a line to stdout taking care of space to tab conversion (-h flag) 386 * and character set shifts. 387 */ 388 void 389 flush_line(l) 390 LINE *l; 391 { 392 CHAR *c, *endc; 393 int nchars, last_col, this_col; 394 395 last_col = 0; 396 nchars = l->l_line_len; 397 398 if (l->l_needs_sort) { 399 static CHAR *sorted; 400 static int count_size, *count, i, save, sorted_size, tot; 401 402 /* 403 * Do an O(n) sort on l->l_line by column being careful to 404 * preserve the order of characters in the same column. 405 */ 406 if (l->l_lsize > sorted_size) { 407 sorted_size = l->l_lsize; 408 if ((sorted = realloc(sorted, 409 (unsigned)sizeof(CHAR) * sorted_size)) == NULL) 410 err(1, (char *)NULL); 411 } 412 if (l->l_max_col >= count_size) { 413 count_size = l->l_max_col + 1; 414 if ((count = realloc(count, 415 (unsigned)sizeof(int) * count_size)) == NULL) 416 err(1, (char *)NULL); 417 } 418 memset(count, 0, sizeof(int) * l->l_max_col + 1); 419 for (i = nchars, c = l->l_line; --i >= 0; c++) 420 count[c->c_column]++; 421 422 /* 423 * calculate running total (shifted down by 1) to use as 424 * indices into new line. 425 */ 426 for (tot = 0, i = 0; i <= l->l_max_col; i++) { 427 save = count[i]; 428 count[i] = tot; 429 tot += save; 430 } 431 432 for (i = nchars, c = l->l_line; --i >= 0; c++) 433 sorted[count[c->c_column]++] = *c; 434 c = sorted; 435 } else 436 c = l->l_line; 437 while (nchars > 0) { 438 this_col = c->c_column; 439 endc = c; 440 do { 441 ++endc; 442 } while (--nchars > 0 && this_col == endc->c_column); 443 444 /* if -b only print last character */ 445 if (no_backspaces) 446 c = endc - 1; 447 448 if (this_col > last_col) { 449 int nspace = this_col - last_col; 450 451 if (compress_spaces && nspace > 1) { 452 while (1) { 453 int tab_col, tab_size;; 454 455 tab_col = (last_col + 8) & ~7; 456 if (tab_col > this_col) 457 break; 458 tab_size = tab_col - last_col; 459 if (tab_size == 1) 460 PUTC(' '); 461 else 462 PUTC('\t'); 463 nspace -= tab_size; 464 last_col = tab_col; 465 } 466 } 467 while (--nspace >= 0) 468 PUTC(' '); 469 last_col = this_col; 470 } 471 last_col++; 472 473 for (;;) { 474 if (c->c_set != last_set) { 475 switch (c->c_set) { 476 case CS_NORMAL: 477 PUTC('\017'); 478 break; 479 case CS_ALTERNATE: 480 PUTC('\016'); 481 } 482 last_set = c->c_set; 483 } 484 PUTC(c->c_char); 485 if (++c >= endc) 486 break; 487 PUTC('\b'); 488 } 489 } 490 } 491 492 #define NALLOC 64 493 494 static LINE *line_freelist; 495 496 LINE * 497 alloc_line() 498 { 499 LINE *l; 500 int i; 501 502 if (!line_freelist) { 503 if ((l = realloc(NULL, sizeof(LINE) * NALLOC)) == NULL) 504 err(1, (char *)NULL); 505 line_freelist = l; 506 for (i = 1; i < NALLOC; i++, l++) 507 l->l_next = l + 1; 508 l->l_next = NULL; 509 } 510 l = line_freelist; 511 line_freelist = l->l_next; 512 513 memset(l, 0, sizeof(LINE)); 514 return (l); 515 } 516 517 void 518 free_line(l) 519 LINE *l; 520 { 521 522 l->l_next = line_freelist; 523 line_freelist = l; 524 } 525 526 void 527 usage() 528 { 529 530 (void)fprintf(stderr, "usage: col [-bfhpx] [-l nline]\n"); 531 exit(1); 532 } 533 534 void 535 dowarn(line) 536 int line; 537 { 538 539 warnx("warning: can't back up %s", 540 line < 0 ? "past first line" : "-- line already flushed"); 541 } 542