1*42270Sbostic /*- 2*42270Sbostic * Copyright (c) 1990 The Regents of the University of California. 3*42270Sbostic * All rights reserved. 4*42270Sbostic * 5*42270Sbostic * This code is derived from software contributed to Berkeley by 6*42270Sbostic * Michael Rendell of the Memorial University of Newfoundland. 7*42270Sbostic * 8*42270Sbostic * %sccs.include.redist.c% 9*42270Sbostic */ 10982Sbill 11*42270Sbostic #ifndef lint 12*42270Sbostic char copyright[] = 13*42270Sbostic "@(#) Copyright (c) 1990 The Regents of the University of California.\n\ 14*42270Sbostic All rights reserved.\n"; 15*42270Sbostic #endif /* not lint */ 16982Sbill 17*42270Sbostic #ifndef lint 18*42270Sbostic static char sccsid[] = "@(#)col.c 5.1 (Berkeley) 05/22/90"; 19*42270Sbostic #endif /* not lint */ 20982Sbill 21*42270Sbostic #include <errno.h> 22*42270Sbostic #include <ctype.h> 23*42270Sbostic #include <string.h> 24*42270Sbostic #include <stdio.h> 25982Sbill 26*42270Sbostic #define BS '\b' /* backspace */ 27*42270Sbostic #define TAB '\t' /* tab */ 28*42270Sbostic #define SPACE ' ' /* space */ 29*42270Sbostic #define NL '\n' /* newline */ 30*42270Sbostic #define CR '\r' /* carriage return */ 31*42270Sbostic #define ESC 033 /* escape */ 32*42270Sbostic #define SI 017 /* shift in to normal character set */ 33*42270Sbostic #define SO 016 /* shift out to alternate character set */ 34*42270Sbostic #define VT 013 /* vertical tab (aka reverse line feed) */ 35*42270Sbostic #define RLF 07 /* ESC-07 reverse line feed */ 36*42270Sbostic #define RHLF 08 /* ESC-08 reverse half-line feed */ 37*42270Sbostic #define FHLF 09 /* ESC-09 forward half-line feed */ 38982Sbill 39*42270Sbostic /* build up at least this many lines before flushing them out */ 40*42270Sbostic #define BUFFER_MARGIN 32 41982Sbill 42*42270Sbostic typedef char CSET; 43982Sbill 44*42270Sbostic typedef struct char_str { 45*42270Sbostic #define CS_NORMAL 1 46*42270Sbostic #define CS_ALTERNATE 2 47*42270Sbostic short c_column; /* column character is in */ 48*42270Sbostic CSET c_set; /* character set (currently only 2) */ 49*42270Sbostic char c_char; /* character in question */ 50*42270Sbostic } CHAR; 51982Sbill 52*42270Sbostic typedef struct line_str LINE; 53*42270Sbostic struct line_str { 54*42270Sbostic CHAR *l_line; /* characters on the line */ 55*42270Sbostic LINE *l_prev; /* previous line */ 56*42270Sbostic LINE *l_next; /* next line */ 57*42270Sbostic int l_lsize; /* allocated sizeof l_line */ 58*42270Sbostic int l_line_len; /* strlen(l_line) */ 59*42270Sbostic int l_needs_sort; /* set if chars went in out of order */ 60*42270Sbostic int l_max_col; /* max column in the line */ 61*42270Sbostic }; 62982Sbill 63*42270Sbostic LINE *alloc_line(); 64*42270Sbostic void *xmalloc(); 65982Sbill 66*42270Sbostic CSET last_set; /* char_set of last char printed */ 67*42270Sbostic LINE *lines; 68*42270Sbostic int compress_spaces; /* if doing space -> tab conversion */ 69*42270Sbostic int fine; /* if `fine' resolution (half lines) */ 70*42270Sbostic int max_bufd_lines; /* max # lines to keep in memory */ 71*42270Sbostic int nblank_lines; /* # blanks after last flushed line */ 72*42270Sbostic int no_backspaces; /* if not to output any backspaces */ 73982Sbill 74*42270Sbostic #define PUTC(ch) \ 75*42270Sbostic if (putchar(ch) == EOF) \ 76*42270Sbostic wrerr(); 77982Sbill 78*42270Sbostic main(argc, argv) 79*42270Sbostic int argc; 80*42270Sbostic char **argv; 81*42270Sbostic { 82*42270Sbostic extern int optind; 83*42270Sbostic extern char *optarg; 84*42270Sbostic register int ch; 85*42270Sbostic CHAR *c; 86*42270Sbostic CSET cur_set; /* current character set */ 87*42270Sbostic LINE *l; /* current line */ 88*42270Sbostic int extra_lines; /* # of lines above first line */ 89*42270Sbostic int cur_col; /* current column */ 90*42270Sbostic int cur_line; /* line number of current position */ 91*42270Sbostic int max_line; /* max value of cur_line */ 92*42270Sbostic int this_line; /* line l points to */ 93*42270Sbostic int nflushd_lines; /* number of lines that were flushed */ 94*42270Sbostic int adjust, opt, warned; 95982Sbill 96*42270Sbostic max_bufd_lines = 128; 97*42270Sbostic compress_spaces = 1; /* compress spaces into tabs */ 98*42270Sbostic while ((opt = getopt(argc, argv, "bfhl:x")) != EOF) 99*42270Sbostic switch (opt) { 100*42270Sbostic case 'b': /* do not output backspaces */ 101*42270Sbostic no_backspaces = 1; 102*42270Sbostic break; 103*42270Sbostic case 'f': /* allow half forward line feeds */ 104*42270Sbostic fine = 1; 105*42270Sbostic break; 106*42270Sbostic case 'h': /* compress spaces into tabs */ 107*42270Sbostic compress_spaces = 1; 108*42270Sbostic break; 109*42270Sbostic case 'l': /* buffered line count */ 110*42270Sbostic if ((max_bufd_lines = atoi(optarg)) <= 0) { 111*42270Sbostic (void)fprintf(stderr, 112*42270Sbostic "col: bad -l argument %s.\n", optarg); 113*42270Sbostic exit(1); 114982Sbill } 115*42270Sbostic break; 116*42270Sbostic case 'x': /* do not compress spaces into tabs */ 117*42270Sbostic compress_spaces = 0; 118*42270Sbostic break; 119*42270Sbostic case '?': 120*42270Sbostic default: 121*42270Sbostic usage(); 122*42270Sbostic } 123982Sbill 124*42270Sbostic if (optind != argc) 125*42270Sbostic usage(); 126982Sbill 127*42270Sbostic /* this value is in half lines */ 128*42270Sbostic max_bufd_lines *= 2; 129982Sbill 130*42270Sbostic adjust = cur_col = extra_lines = warned = 0; 131*42270Sbostic cur_line = max_line = nflushd_lines = this_line = 0; 132*42270Sbostic cur_set = last_set = CS_NORMAL; 133*42270Sbostic lines = l = alloc_line(); 134982Sbill 135*42270Sbostic while ((ch = getchar()) != EOF) { 136*42270Sbostic if (!isgraph(ch)) { 137*42270Sbostic switch (ch) { 138*42270Sbostic case BS: /* can't go back further */ 139*42270Sbostic if (cur_col == 0) 140*42270Sbostic continue; 141*42270Sbostic --cur_col; 142*42270Sbostic continue; 143*42270Sbostic case CR: 144*42270Sbostic cur_col = 0; 145*42270Sbostic continue; 146*42270Sbostic case ESC: /* just ignore EOF */ 147*42270Sbostic switch(getchar()) { 148*42270Sbostic case RLF: 149*42270Sbostic cur_line -= 2; 150*42270Sbostic break; 151*42270Sbostic case RHLF: 152*42270Sbostic cur_line--; 153*42270Sbostic break; 154*42270Sbostic case FHLF: 155*42270Sbostic cur_line++; 156*42270Sbostic if (cur_line > max_line) 157*42270Sbostic max_line = cur_line; 158*42270Sbostic } 159*42270Sbostic continue; 160*42270Sbostic case NL: 161*42270Sbostic cur_line += 2; 162*42270Sbostic if (cur_line > max_line) 163*42270Sbostic max_line = cur_line; 164*42270Sbostic cur_col = 0; 165*42270Sbostic continue; 166*42270Sbostic case SPACE: 167*42270Sbostic ++cur_col; 168*42270Sbostic continue; 169*42270Sbostic case SI: 170*42270Sbostic cur_set = CS_NORMAL; 171*42270Sbostic continue; 172*42270Sbostic case SO: 173*42270Sbostic cur_set = CS_ALTERNATE; 174*42270Sbostic continue; 175*42270Sbostic case TAB: /* adjust column */ 176*42270Sbostic cur_col |= 7; 177*42270Sbostic ++cur_col; 178*42270Sbostic continue; 179*42270Sbostic case VT: 180*42270Sbostic cur_line -= 2; 181*42270Sbostic continue; 182*42270Sbostic } 183982Sbill continue; 184*42270Sbostic } 185982Sbill 186*42270Sbostic /* Must stuff ch in a line - are we at the right one? */ 187*42270Sbostic if (cur_line != this_line - adjust) { 188*42270Sbostic LINE *lnew; 189*42270Sbostic int nmove; 190982Sbill 191*42270Sbostic adjust = 0; 192*42270Sbostic nmove = cur_line - this_line; 193*42270Sbostic if (!fine) { 194*42270Sbostic /* round up to next line */ 195*42270Sbostic if (cur_line & 1) { 196*42270Sbostic adjust = 1; 197*42270Sbostic nmove++; 198*42270Sbostic } 199982Sbill } 200*42270Sbostic if (nmove < 0) { 201*42270Sbostic for (; nmove < 0 && l->l_prev; nmove++) 202*42270Sbostic l = l->l_prev; 203*42270Sbostic if (nmove) { 204*42270Sbostic if (nflushd_lines == 0) { 205*42270Sbostic /* 206*42270Sbostic * Allow backup past first 207*42270Sbostic * line if nothing has been 208*42270Sbostic * flushed yet. 209*42270Sbostic */ 210*42270Sbostic for (; nmove < 0; nmove++) { 211*42270Sbostic lnew = alloc_line(); 212*42270Sbostic l->l_prev = lnew; 213*42270Sbostic lnew->l_next = l; 214*42270Sbostic l = lines = lnew; 215*42270Sbostic extra_lines++; 216*42270Sbostic } 217*42270Sbostic } else { 218*42270Sbostic if (!warned++) 219*42270Sbostic warn(cur_line); 220*42270Sbostic cur_line -= nmove; 221*42270Sbostic } 222*42270Sbostic } 223*42270Sbostic } else { 224*42270Sbostic /* may need to allocate here */ 225*42270Sbostic for (; nmove > 0 && l->l_next; nmove--) 226*42270Sbostic l = l->l_next; 227*42270Sbostic for (; nmove > 0; nmove--) { 228*42270Sbostic lnew = alloc_line(); 229*42270Sbostic lnew->l_prev = l; 230*42270Sbostic l->l_next = lnew; 231*42270Sbostic l = lnew; 232*42270Sbostic } 233*42270Sbostic } 234*42270Sbostic this_line = cur_line + adjust; 235*42270Sbostic nmove = this_line - nflushd_lines; 236*42270Sbostic if (nmove >= max_bufd_lines + BUFFER_MARGIN) { 237*42270Sbostic nflushd_lines += nmove - max_bufd_lines; 238*42270Sbostic flush_lines(nmove - max_bufd_lines); 239*42270Sbostic } 240982Sbill } 241*42270Sbostic /* grow line's buffer? */ 242*42270Sbostic if (l->l_line_len + 1 >= l->l_lsize) { 243*42270Sbostic int need; 244*42270Sbostic 245*42270Sbostic need = l->l_lsize ? l->l_lsize * 2 : 90; 246*42270Sbostic l->l_line = (CHAR *)xmalloc((void *) l->l_line, 247*42270Sbostic (unsigned) need * sizeof(CHAR)); 248*42270Sbostic l->l_lsize = need; 249*42270Sbostic } 250*42270Sbostic c = &l->l_line[l->l_line_len++]; 251*42270Sbostic c->c_char = ch; 252*42270Sbostic c->c_set = cur_set; 253*42270Sbostic c->c_column = cur_col; 254*42270Sbostic /* 255*42270Sbostic * If things are put in out of order, they will need sorting 256*42270Sbostic * when it is flushed. 257*42270Sbostic */ 258*42270Sbostic if (cur_col < l->l_max_col) 259*42270Sbostic l->l_needs_sort = 1; 260*42270Sbostic else 261*42270Sbostic l->l_max_col = cur_col; 262*42270Sbostic cur_col++; 263982Sbill } 264*42270Sbostic /* goto the last line that had a character on it */ 265*42270Sbostic for (; l->l_next; l = l->l_next) 266*42270Sbostic this_line++; 267*42270Sbostic flush_lines(this_line - nflushd_lines + extra_lines + 1); 268982Sbill 269*42270Sbostic /* make sure we leave things in a sane state */ 270*42270Sbostic if (last_set != CS_NORMAL) 271*42270Sbostic PUTC('\017'); 272*42270Sbostic 273*42270Sbostic /* flush out the last few blank lines */ 274*42270Sbostic nblank_lines = max_line - this_line; 275*42270Sbostic if (max_line & 1) 276*42270Sbostic nblank_lines++; 277*42270Sbostic else if (!nblank_lines) 278*42270Sbostic /* missing a \n on the last line? */ 279*42270Sbostic nblank_lines = 2; 280*42270Sbostic flush_blanks(); 281982Sbill exit(0); 282982Sbill } 283982Sbill 284*42270Sbostic flush_lines(nflush) 285*42270Sbostic int nflush; 286982Sbill { 287*42270Sbostic LINE *l; 288982Sbill 289*42270Sbostic while (--nflush >= 0) { 290*42270Sbostic l = lines; 291*42270Sbostic lines = l->l_next; 292*42270Sbostic if (l->l_line) { 293*42270Sbostic flush_blanks(); 294*42270Sbostic flush_line(l); 295982Sbill } 296*42270Sbostic nblank_lines++; 297*42270Sbostic if (l->l_line) 298*42270Sbostic (void)free((void *)l->l_line); 299*42270Sbostic free_line(l); 300982Sbill } 301*42270Sbostic if (lines) 302*42270Sbostic lines->l_prev = NULL; 303982Sbill } 304982Sbill 305*42270Sbostic /* 306*42270Sbostic * Print a number of newline/half newlines. If fine flag is set, nblank_lines 307*42270Sbostic * is the number of half line feeds, otherwise it is the number of whole line 308*42270Sbostic * feeds. 309*42270Sbostic */ 310*42270Sbostic flush_blanks() 311982Sbill { 312*42270Sbostic int half, i, nb; 313982Sbill 314*42270Sbostic half = 0; 315*42270Sbostic nb = nblank_lines; 316*42270Sbostic if (nb & 1) { 317*42270Sbostic if (fine) 318*42270Sbostic half = 1; 319*42270Sbostic else 320*42270Sbostic nb++; 321982Sbill } 322*42270Sbostic nb /= 2; 323*42270Sbostic for (i = nb; --i >= 0;) 324*42270Sbostic PUTC('\n'); 325*42270Sbostic if (half) { 326*42270Sbostic PUTC('\033'); 327*42270Sbostic PUTC('9'); 328*42270Sbostic if (!nb) 329*42270Sbostic PUTC('\r'); 330*42270Sbostic } 331*42270Sbostic nblank_lines = 0; 332982Sbill } 333982Sbill 334*42270Sbostic /* 335*42270Sbostic * Write a line to stdout taking care of space to tab conversion (-h flag) 336*42270Sbostic * and character set shifts. 337*42270Sbostic */ 338*42270Sbostic flush_line(l) 339*42270Sbostic LINE *l; 340982Sbill { 341*42270Sbostic CHAR *c, *endc; 342*42270Sbostic int nchars, last_col, this_col; 343982Sbill 344*42270Sbostic last_col = 0; 345*42270Sbostic nchars = l->l_line_len; 346982Sbill 347*42270Sbostic if (l->l_needs_sort) { 348*42270Sbostic static CHAR *sorted; 349*42270Sbostic static int count_size, *count, i, save, sorted_size, tot; 350*42270Sbostic 351*42270Sbostic /* 352*42270Sbostic * Do an O(n) sort on l->l_line by column being careful to 353*42270Sbostic * preserve the order of characters in the same column. 354*42270Sbostic */ 355*42270Sbostic if (l->l_lsize > sorted_size) { 356*42270Sbostic sorted_size = l->l_lsize; 357*42270Sbostic sorted = (CHAR *)xmalloc((void *)sorted, 358*42270Sbostic (unsigned)sizeof(CHAR) * sorted_size); 359982Sbill } 360*42270Sbostic if (l->l_max_col >= count_size) { 361*42270Sbostic count_size = l->l_max_col + 1; 362*42270Sbostic count = (int *)xmalloc((void *)count, 363*42270Sbostic (unsigned)sizeof(int) * count_size); 364982Sbill } 365*42270Sbostic bzero((char *)count, sizeof(int) * l->l_max_col + 1); 366*42270Sbostic for (i = nchars, c = l->l_line; --i >= 0; c++) 367*42270Sbostic count[c->c_column]++; 368*42270Sbostic 369*42270Sbostic /* 370*42270Sbostic * calculate running total (shifted down by 1) to use as 371*42270Sbostic * indices into new line. 372*42270Sbostic */ 373*42270Sbostic for (tot = 0, i = 0; i <= l->l_max_col; i++) { 374*42270Sbostic save = count[i]; 375*42270Sbostic count[i] = tot; 376*42270Sbostic tot += save; 377*42270Sbostic } 378*42270Sbostic 379*42270Sbostic for (i = nchars, c = l->l_line; --i >= 0; c++) 380*42270Sbostic sorted[count[c->c_column]++] = *c; 381*42270Sbostic c = sorted; 382*42270Sbostic } else 383*42270Sbostic c = l->l_line; 384*42270Sbostic while (nchars > 0) { 385*42270Sbostic this_col = c->c_column; 386*42270Sbostic endc = c; 387*42270Sbostic do { 388*42270Sbostic ++endc; 389*42270Sbostic } while (--nchars > 0 && this_col == endc->c_column); 390*42270Sbostic 391*42270Sbostic /* if -b only print last character */ 392*42270Sbostic if (no_backspaces) 393*42270Sbostic c = endc - 1; 394*42270Sbostic 395*42270Sbostic if (this_col > last_col) { 396*42270Sbostic int nspace = this_col - last_col; 397*42270Sbostic 398*42270Sbostic if (compress_spaces && nspace > 1) { 399*42270Sbostic int ntabs; 400*42270Sbostic 401*42270Sbostic ntabs = this_col / 8 - last_col / 8; 402*42270Sbostic nspace -= ntabs * 8; 403*42270Sbostic while (--ntabs >= 0) 404*42270Sbostic PUTC('\t'); 405*42270Sbostic } 406*42270Sbostic while (--nspace >= 0) 407*42270Sbostic PUTC(' '); 408*42270Sbostic last_col = this_col; 409*42270Sbostic } 410*42270Sbostic last_col++; 411*42270Sbostic 412*42270Sbostic for (;;) { 413*42270Sbostic if (c->c_set != last_set) { 414*42270Sbostic switch (c->c_set) { 415*42270Sbostic case CS_NORMAL: 416*42270Sbostic PUTC('\017'); 417*42270Sbostic break; 418*42270Sbostic case CS_ALTERNATE: 419*42270Sbostic PUTC('\016'); 420982Sbill } 421*42270Sbostic last_set = c->c_set; 422982Sbill } 423*42270Sbostic PUTC(c->c_char); 424*42270Sbostic if (++c >= endc) 425982Sbill break; 426*42270Sbostic PUTC('\b'); 427982Sbill } 428982Sbill } 429982Sbill } 430982Sbill 431*42270Sbostic #define NALLOC 64 432*42270Sbostic 433*42270Sbostic static LINE *line_freelist; 434*42270Sbostic 435*42270Sbostic LINE * 436*42270Sbostic alloc_line() 437982Sbill { 438*42270Sbostic LINE *l; 439*42270Sbostic int i; 440*42270Sbostic 441*42270Sbostic if (!line_freelist) { 442*42270Sbostic l = (LINE *)xmalloc((void *)NULL, sizeof(LINE) * NALLOC); 443*42270Sbostic line_freelist = l; 444*42270Sbostic for (i = 1; i < NALLOC; i++, l++) 445*42270Sbostic l->l_next = l + 1; 446*42270Sbostic l->l_next = NULL; 447982Sbill } 448*42270Sbostic l = line_freelist; 449*42270Sbostic line_freelist = l->l_next; 450*42270Sbostic 451*42270Sbostic bzero(l, sizeof(LINE)); 452*42270Sbostic return(l); 453982Sbill } 454982Sbill 455*42270Sbostic free_line(l) 456*42270Sbostic LINE *l; 457982Sbill { 458*42270Sbostic l->l_next = line_freelist; 459*42270Sbostic line_freelist = l; 460*42270Sbostic } 461*42270Sbostic 462*42270Sbostic void * 463*42270Sbostic xmalloc(p, size) 464*42270Sbostic void *p; 465*42270Sbostic size_t size; 466*42270Sbostic { 467*42270Sbostic if (!(p = (void *)realloc(p, size))) { 468*42270Sbostic (void)fprintf(stderr, "col: %s.\n", strerror(ENOMEM)); 469*42270Sbostic exit(1); 470982Sbill } 471*42270Sbostic return(p); 472982Sbill } 473*42270Sbostic 474*42270Sbostic usage() 475*42270Sbostic { 476*42270Sbostic (void)fprintf(stderr, "usage: col [-bfx] [-l nline]\n"); 477*42270Sbostic exit(1); 478*42270Sbostic } 479*42270Sbostic 480*42270Sbostic wrerr() 481*42270Sbostic { 482*42270Sbostic (void)fprintf(stderr, "col: write error.\n"); 483*42270Sbostic exit(1); 484*42270Sbostic } 485*42270Sbostic 486*42270Sbostic warn(line) 487*42270Sbostic int line; 488*42270Sbostic { 489*42270Sbostic (void)fprintf(stderr, 490*42270Sbostic "col: warning: can't back up %s.\n", line < 0 ? 491*42270Sbostic "past first line" : "-- line already flushed"); 492*42270Sbostic } 493