142270Sbostic /*- 242270Sbostic * Copyright (c) 1990 The Regents of the University of California. 342270Sbostic * All rights reserved. 442270Sbostic * 542270Sbostic * This code is derived from software contributed to Berkeley by 642270Sbostic * Michael Rendell of the Memorial University of Newfoundland. 742270Sbostic * 842270Sbostic * %sccs.include.redist.c% 942270Sbostic */ 10982Sbill 1142270Sbostic #ifndef lint 1242270Sbostic char copyright[] = 1342270Sbostic "@(#) Copyright (c) 1990 The Regents of the University of California.\n\ 1442270Sbostic All rights reserved.\n"; 1542270Sbostic #endif /* not lint */ 16982Sbill 1742270Sbostic #ifndef lint 18*42305Sbostic static char sccsid[] = "@(#)col.c 5.2 (Berkeley) 05/24/90"; 1942270Sbostic #endif /* not lint */ 20982Sbill 2142270Sbostic #include <errno.h> 2242270Sbostic #include <ctype.h> 2342270Sbostic #include <string.h> 2442270Sbostic #include <stdio.h> 25982Sbill 2642270Sbostic #define BS '\b' /* backspace */ 2742270Sbostic #define TAB '\t' /* tab */ 2842270Sbostic #define SPACE ' ' /* space */ 2942270Sbostic #define NL '\n' /* newline */ 3042270Sbostic #define CR '\r' /* carriage return */ 31*42305Sbostic #define ESC '\033' /* escape */ 32*42305Sbostic #define SI '\017' /* shift in to normal character set */ 33*42305Sbostic #define SO '\016' /* shift out to alternate character set */ 34*42305Sbostic #define VT '\013' /* vertical tab (aka reverse line feed) */ 35*42305Sbostic #define RLF '\07' /* ESC-07 reverse line feed */ 36*42305Sbostic #define RHLF '\08' /* ESC-08 reverse half-line feed */ 37*42305Sbostic #define FHLF '\09' /* ESC-09 forward half-line feed */ 38982Sbill 3942270Sbostic /* build up at least this many lines before flushing them out */ 4042270Sbostic #define BUFFER_MARGIN 32 41982Sbill 4242270Sbostic typedef char CSET; 43982Sbill 4442270Sbostic typedef struct char_str { 4542270Sbostic #define CS_NORMAL 1 4642270Sbostic #define CS_ALTERNATE 2 4742270Sbostic short c_column; /* column character is in */ 4842270Sbostic CSET c_set; /* character set (currently only 2) */ 4942270Sbostic char c_char; /* character in question */ 5042270Sbostic } CHAR; 51982Sbill 5242270Sbostic typedef struct line_str LINE; 5342270Sbostic struct line_str { 5442270Sbostic CHAR *l_line; /* characters on the line */ 5542270Sbostic LINE *l_prev; /* previous line */ 5642270Sbostic LINE *l_next; /* next line */ 5742270Sbostic int l_lsize; /* allocated sizeof l_line */ 5842270Sbostic int l_line_len; /* strlen(l_line) */ 5942270Sbostic int l_needs_sort; /* set if chars went in out of order */ 6042270Sbostic int l_max_col; /* max column in the line */ 6142270Sbostic }; 62982Sbill 6342270Sbostic LINE *alloc_line(); 6442270Sbostic void *xmalloc(); 65982Sbill 6642270Sbostic CSET last_set; /* char_set of last char printed */ 6742270Sbostic LINE *lines; 6842270Sbostic int compress_spaces; /* if doing space -> tab conversion */ 6942270Sbostic int fine; /* if `fine' resolution (half lines) */ 7042270Sbostic int max_bufd_lines; /* max # lines to keep in memory */ 7142270Sbostic int nblank_lines; /* # blanks after last flushed line */ 7242270Sbostic int no_backspaces; /* if not to output any backspaces */ 73982Sbill 7442270Sbostic #define PUTC(ch) \ 7542270Sbostic if (putchar(ch) == EOF) \ 7642270Sbostic wrerr(); 77982Sbill 7842270Sbostic main(argc, argv) 7942270Sbostic int argc; 8042270Sbostic char **argv; 8142270Sbostic { 8242270Sbostic extern int optind; 8342270Sbostic extern char *optarg; 8442270Sbostic register int ch; 8542270Sbostic CHAR *c; 8642270Sbostic CSET cur_set; /* current character set */ 8742270Sbostic LINE *l; /* current line */ 8842270Sbostic int extra_lines; /* # of lines above first line */ 8942270Sbostic int cur_col; /* current column */ 9042270Sbostic int cur_line; /* line number of current position */ 9142270Sbostic int max_line; /* max value of cur_line */ 9242270Sbostic int this_line; /* line l points to */ 9342270Sbostic int nflushd_lines; /* number of lines that were flushed */ 9442270Sbostic int adjust, opt, warned; 95982Sbill 9642270Sbostic max_bufd_lines = 128; 9742270Sbostic compress_spaces = 1; /* compress spaces into tabs */ 9842270Sbostic while ((opt = getopt(argc, argv, "bfhl:x")) != EOF) 9942270Sbostic switch (opt) { 10042270Sbostic case 'b': /* do not output backspaces */ 10142270Sbostic no_backspaces = 1; 10242270Sbostic break; 10342270Sbostic case 'f': /* allow half forward line feeds */ 10442270Sbostic fine = 1; 10542270Sbostic break; 10642270Sbostic case 'h': /* compress spaces into tabs */ 10742270Sbostic compress_spaces = 1; 10842270Sbostic break; 10942270Sbostic case 'l': /* buffered line count */ 11042270Sbostic if ((max_bufd_lines = atoi(optarg)) <= 0) { 11142270Sbostic (void)fprintf(stderr, 11242270Sbostic "col: bad -l argument %s.\n", optarg); 11342270Sbostic exit(1); 114982Sbill } 11542270Sbostic break; 11642270Sbostic case 'x': /* do not compress spaces into tabs */ 11742270Sbostic compress_spaces = 0; 11842270Sbostic break; 11942270Sbostic case '?': 12042270Sbostic default: 12142270Sbostic usage(); 12242270Sbostic } 123982Sbill 12442270Sbostic if (optind != argc) 12542270Sbostic usage(); 126982Sbill 12742270Sbostic /* this value is in half lines */ 12842270Sbostic max_bufd_lines *= 2; 129982Sbill 13042270Sbostic adjust = cur_col = extra_lines = warned = 0; 13142270Sbostic cur_line = max_line = nflushd_lines = this_line = 0; 13242270Sbostic cur_set = last_set = CS_NORMAL; 13342270Sbostic lines = l = alloc_line(); 134982Sbill 13542270Sbostic while ((ch = getchar()) != EOF) { 13642270Sbostic if (!isgraph(ch)) { 13742270Sbostic switch (ch) { 13842270Sbostic case BS: /* can't go back further */ 13942270Sbostic if (cur_col == 0) 14042270Sbostic continue; 14142270Sbostic --cur_col; 14242270Sbostic continue; 14342270Sbostic case CR: 14442270Sbostic cur_col = 0; 14542270Sbostic continue; 14642270Sbostic case ESC: /* just ignore EOF */ 14742270Sbostic switch(getchar()) { 14842270Sbostic case RLF: 14942270Sbostic cur_line -= 2; 15042270Sbostic break; 15142270Sbostic case RHLF: 15242270Sbostic cur_line--; 15342270Sbostic break; 15442270Sbostic case FHLF: 15542270Sbostic cur_line++; 15642270Sbostic if (cur_line > max_line) 15742270Sbostic max_line = cur_line; 15842270Sbostic } 15942270Sbostic continue; 16042270Sbostic case NL: 16142270Sbostic cur_line += 2; 16242270Sbostic if (cur_line > max_line) 16342270Sbostic max_line = cur_line; 16442270Sbostic cur_col = 0; 16542270Sbostic continue; 16642270Sbostic case SPACE: 16742270Sbostic ++cur_col; 16842270Sbostic continue; 16942270Sbostic case SI: 17042270Sbostic cur_set = CS_NORMAL; 17142270Sbostic continue; 17242270Sbostic case SO: 17342270Sbostic cur_set = CS_ALTERNATE; 17442270Sbostic continue; 17542270Sbostic case TAB: /* adjust column */ 17642270Sbostic cur_col |= 7; 17742270Sbostic ++cur_col; 17842270Sbostic continue; 17942270Sbostic case VT: 18042270Sbostic cur_line -= 2; 18142270Sbostic continue; 18242270Sbostic } 183982Sbill continue; 18442270Sbostic } 185982Sbill 18642270Sbostic /* Must stuff ch in a line - are we at the right one? */ 18742270Sbostic if (cur_line != this_line - adjust) { 18842270Sbostic LINE *lnew; 18942270Sbostic int nmove; 190982Sbill 19142270Sbostic adjust = 0; 19242270Sbostic nmove = cur_line - this_line; 19342270Sbostic if (!fine) { 19442270Sbostic /* round up to next line */ 19542270Sbostic if (cur_line & 1) { 19642270Sbostic adjust = 1; 19742270Sbostic nmove++; 19842270Sbostic } 199982Sbill } 20042270Sbostic if (nmove < 0) { 20142270Sbostic for (; nmove < 0 && l->l_prev; nmove++) 20242270Sbostic l = l->l_prev; 20342270Sbostic if (nmove) { 20442270Sbostic if (nflushd_lines == 0) { 20542270Sbostic /* 20642270Sbostic * Allow backup past first 20742270Sbostic * line if nothing has been 20842270Sbostic * flushed yet. 20942270Sbostic */ 21042270Sbostic for (; nmove < 0; nmove++) { 21142270Sbostic lnew = alloc_line(); 21242270Sbostic l->l_prev = lnew; 21342270Sbostic lnew->l_next = l; 21442270Sbostic l = lines = lnew; 21542270Sbostic extra_lines++; 21642270Sbostic } 21742270Sbostic } else { 21842270Sbostic if (!warned++) 21942270Sbostic warn(cur_line); 22042270Sbostic cur_line -= nmove; 22142270Sbostic } 22242270Sbostic } 22342270Sbostic } else { 22442270Sbostic /* may need to allocate here */ 22542270Sbostic for (; nmove > 0 && l->l_next; nmove--) 22642270Sbostic l = l->l_next; 22742270Sbostic for (; nmove > 0; nmove--) { 22842270Sbostic lnew = alloc_line(); 22942270Sbostic lnew->l_prev = l; 23042270Sbostic l->l_next = lnew; 23142270Sbostic l = lnew; 23242270Sbostic } 23342270Sbostic } 23442270Sbostic this_line = cur_line + adjust; 23542270Sbostic nmove = this_line - nflushd_lines; 23642270Sbostic if (nmove >= max_bufd_lines + BUFFER_MARGIN) { 23742270Sbostic nflushd_lines += nmove - max_bufd_lines; 23842270Sbostic flush_lines(nmove - max_bufd_lines); 23942270Sbostic } 240982Sbill } 24142270Sbostic /* grow line's buffer? */ 24242270Sbostic if (l->l_line_len + 1 >= l->l_lsize) { 24342270Sbostic int need; 24442270Sbostic 24542270Sbostic need = l->l_lsize ? l->l_lsize * 2 : 90; 24642270Sbostic l->l_line = (CHAR *)xmalloc((void *) l->l_line, 24742270Sbostic (unsigned) need * sizeof(CHAR)); 24842270Sbostic l->l_lsize = need; 24942270Sbostic } 25042270Sbostic c = &l->l_line[l->l_line_len++]; 25142270Sbostic c->c_char = ch; 25242270Sbostic c->c_set = cur_set; 25342270Sbostic c->c_column = cur_col; 25442270Sbostic /* 25542270Sbostic * If things are put in out of order, they will need sorting 25642270Sbostic * when it is flushed. 25742270Sbostic */ 25842270Sbostic if (cur_col < l->l_max_col) 25942270Sbostic l->l_needs_sort = 1; 26042270Sbostic else 26142270Sbostic l->l_max_col = cur_col; 26242270Sbostic cur_col++; 263982Sbill } 26442270Sbostic /* goto the last line that had a character on it */ 26542270Sbostic for (; l->l_next; l = l->l_next) 26642270Sbostic this_line++; 26742270Sbostic flush_lines(this_line - nflushd_lines + extra_lines + 1); 268982Sbill 26942270Sbostic /* make sure we leave things in a sane state */ 27042270Sbostic if (last_set != CS_NORMAL) 27142270Sbostic PUTC('\017'); 27242270Sbostic 27342270Sbostic /* flush out the last few blank lines */ 27442270Sbostic nblank_lines = max_line - this_line; 27542270Sbostic if (max_line & 1) 27642270Sbostic nblank_lines++; 27742270Sbostic else if (!nblank_lines) 27842270Sbostic /* missing a \n on the last line? */ 27942270Sbostic nblank_lines = 2; 28042270Sbostic flush_blanks(); 281982Sbill exit(0); 282982Sbill } 283982Sbill 28442270Sbostic flush_lines(nflush) 28542270Sbostic int nflush; 286982Sbill { 28742270Sbostic LINE *l; 288982Sbill 28942270Sbostic while (--nflush >= 0) { 29042270Sbostic l = lines; 29142270Sbostic lines = l->l_next; 29242270Sbostic if (l->l_line) { 29342270Sbostic flush_blanks(); 29442270Sbostic flush_line(l); 295982Sbill } 29642270Sbostic nblank_lines++; 29742270Sbostic if (l->l_line) 29842270Sbostic (void)free((void *)l->l_line); 29942270Sbostic free_line(l); 300982Sbill } 30142270Sbostic if (lines) 30242270Sbostic lines->l_prev = NULL; 303982Sbill } 304982Sbill 30542270Sbostic /* 30642270Sbostic * Print a number of newline/half newlines. If fine flag is set, nblank_lines 30742270Sbostic * is the number of half line feeds, otherwise it is the number of whole line 30842270Sbostic * feeds. 30942270Sbostic */ 31042270Sbostic flush_blanks() 311982Sbill { 31242270Sbostic int half, i, nb; 313982Sbill 31442270Sbostic half = 0; 31542270Sbostic nb = nblank_lines; 31642270Sbostic if (nb & 1) { 31742270Sbostic if (fine) 31842270Sbostic half = 1; 31942270Sbostic else 32042270Sbostic nb++; 321982Sbill } 32242270Sbostic nb /= 2; 32342270Sbostic for (i = nb; --i >= 0;) 32442270Sbostic PUTC('\n'); 32542270Sbostic if (half) { 32642270Sbostic PUTC('\033'); 32742270Sbostic PUTC('9'); 32842270Sbostic if (!nb) 32942270Sbostic PUTC('\r'); 33042270Sbostic } 33142270Sbostic nblank_lines = 0; 332982Sbill } 333982Sbill 33442270Sbostic /* 33542270Sbostic * Write a line to stdout taking care of space to tab conversion (-h flag) 33642270Sbostic * and character set shifts. 33742270Sbostic */ 33842270Sbostic flush_line(l) 33942270Sbostic LINE *l; 340982Sbill { 34142270Sbostic CHAR *c, *endc; 34242270Sbostic int nchars, last_col, this_col; 343982Sbill 34442270Sbostic last_col = 0; 34542270Sbostic nchars = l->l_line_len; 346982Sbill 34742270Sbostic if (l->l_needs_sort) { 34842270Sbostic static CHAR *sorted; 34942270Sbostic static int count_size, *count, i, save, sorted_size, tot; 35042270Sbostic 35142270Sbostic /* 35242270Sbostic * Do an O(n) sort on l->l_line by column being careful to 35342270Sbostic * preserve the order of characters in the same column. 35442270Sbostic */ 35542270Sbostic if (l->l_lsize > sorted_size) { 35642270Sbostic sorted_size = l->l_lsize; 35742270Sbostic sorted = (CHAR *)xmalloc((void *)sorted, 35842270Sbostic (unsigned)sizeof(CHAR) * sorted_size); 359982Sbill } 36042270Sbostic if (l->l_max_col >= count_size) { 36142270Sbostic count_size = l->l_max_col + 1; 36242270Sbostic count = (int *)xmalloc((void *)count, 36342270Sbostic (unsigned)sizeof(int) * count_size); 364982Sbill } 36542270Sbostic bzero((char *)count, sizeof(int) * l->l_max_col + 1); 36642270Sbostic for (i = nchars, c = l->l_line; --i >= 0; c++) 36742270Sbostic count[c->c_column]++; 36842270Sbostic 36942270Sbostic /* 37042270Sbostic * calculate running total (shifted down by 1) to use as 37142270Sbostic * indices into new line. 37242270Sbostic */ 37342270Sbostic for (tot = 0, i = 0; i <= l->l_max_col; i++) { 37442270Sbostic save = count[i]; 37542270Sbostic count[i] = tot; 37642270Sbostic tot += save; 37742270Sbostic } 37842270Sbostic 37942270Sbostic for (i = nchars, c = l->l_line; --i >= 0; c++) 38042270Sbostic sorted[count[c->c_column]++] = *c; 38142270Sbostic c = sorted; 38242270Sbostic } else 38342270Sbostic c = l->l_line; 38442270Sbostic while (nchars > 0) { 38542270Sbostic this_col = c->c_column; 38642270Sbostic endc = c; 38742270Sbostic do { 38842270Sbostic ++endc; 38942270Sbostic } while (--nchars > 0 && this_col == endc->c_column); 39042270Sbostic 39142270Sbostic /* if -b only print last character */ 39242270Sbostic if (no_backspaces) 39342270Sbostic c = endc - 1; 39442270Sbostic 39542270Sbostic if (this_col > last_col) { 39642270Sbostic int nspace = this_col - last_col; 39742270Sbostic 39842270Sbostic if (compress_spaces && nspace > 1) { 39942270Sbostic int ntabs; 40042270Sbostic 40142270Sbostic ntabs = this_col / 8 - last_col / 8; 40242270Sbostic nspace -= ntabs * 8; 40342270Sbostic while (--ntabs >= 0) 40442270Sbostic PUTC('\t'); 40542270Sbostic } 40642270Sbostic while (--nspace >= 0) 40742270Sbostic PUTC(' '); 40842270Sbostic last_col = this_col; 40942270Sbostic } 41042270Sbostic last_col++; 41142270Sbostic 41242270Sbostic for (;;) { 41342270Sbostic if (c->c_set != last_set) { 41442270Sbostic switch (c->c_set) { 41542270Sbostic case CS_NORMAL: 41642270Sbostic PUTC('\017'); 41742270Sbostic break; 41842270Sbostic case CS_ALTERNATE: 41942270Sbostic PUTC('\016'); 420982Sbill } 42142270Sbostic last_set = c->c_set; 422982Sbill } 42342270Sbostic PUTC(c->c_char); 42442270Sbostic if (++c >= endc) 425982Sbill break; 42642270Sbostic PUTC('\b'); 427982Sbill } 428982Sbill } 429982Sbill } 430982Sbill 43142270Sbostic #define NALLOC 64 43242270Sbostic 43342270Sbostic static LINE *line_freelist; 43442270Sbostic 43542270Sbostic LINE * 43642270Sbostic alloc_line() 437982Sbill { 43842270Sbostic LINE *l; 43942270Sbostic int i; 44042270Sbostic 44142270Sbostic if (!line_freelist) { 44242270Sbostic l = (LINE *)xmalloc((void *)NULL, sizeof(LINE) * NALLOC); 44342270Sbostic line_freelist = l; 44442270Sbostic for (i = 1; i < NALLOC; i++, l++) 44542270Sbostic l->l_next = l + 1; 44642270Sbostic l->l_next = NULL; 447982Sbill } 44842270Sbostic l = line_freelist; 44942270Sbostic line_freelist = l->l_next; 45042270Sbostic 45142270Sbostic bzero(l, sizeof(LINE)); 45242270Sbostic return(l); 453982Sbill } 454982Sbill 45542270Sbostic free_line(l) 45642270Sbostic LINE *l; 457982Sbill { 45842270Sbostic l->l_next = line_freelist; 45942270Sbostic line_freelist = l; 46042270Sbostic } 46142270Sbostic 46242270Sbostic void * 46342270Sbostic xmalloc(p, size) 46442270Sbostic void *p; 46542270Sbostic size_t size; 46642270Sbostic { 46742270Sbostic if (!(p = (void *)realloc(p, size))) { 46842270Sbostic (void)fprintf(stderr, "col: %s.\n", strerror(ENOMEM)); 46942270Sbostic exit(1); 470982Sbill } 47142270Sbostic return(p); 472982Sbill } 47342270Sbostic 47442270Sbostic usage() 47542270Sbostic { 47642270Sbostic (void)fprintf(stderr, "usage: col [-bfx] [-l nline]\n"); 47742270Sbostic exit(1); 47842270Sbostic } 47942270Sbostic 48042270Sbostic wrerr() 48142270Sbostic { 48242270Sbostic (void)fprintf(stderr, "col: write error.\n"); 48342270Sbostic exit(1); 48442270Sbostic } 48542270Sbostic 48642270Sbostic warn(line) 48742270Sbostic int line; 48842270Sbostic { 48942270Sbostic (void)fprintf(stderr, 49042270Sbostic "col: warning: can't back up %s.\n", line < 0 ? 49142270Sbostic "past first line" : "-- line already flushed"); 49242270Sbostic } 493