142270Sbostic /*- 266652Spendry * Copyright (c) 1990, 1993, 1994 361942Sbostic * The Regents of the University of California. 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 1261942Sbostic static char copyright[] = 1366652Spendry "@(#) Copyright (c) 1990, 1993, 1994\n\ 1461942Sbostic The Regents of the University of California. All rights reserved.\n"; 1542270Sbostic #endif /* not lint */ 16982Sbill 1742270Sbostic #ifndef lint 18*69013Sbostic static char sccsid[] = "@(#)col.c 8.4 (Berkeley) 04/28/95"; 1942270Sbostic #endif /* not lint */ 20982Sbill 2142270Sbostic #include <ctype.h> 2266590Spendry #include <err.h> 2342270Sbostic #include <string.h> 2442270Sbostic #include <stdio.h> 2566590Spendry #include <stdlib.h> 26982Sbill 2742270Sbostic #define BS '\b' /* backspace */ 2842270Sbostic #define TAB '\t' /* tab */ 2942270Sbostic #define SPACE ' ' /* space */ 3042270Sbostic #define NL '\n' /* newline */ 3142270Sbostic #define CR '\r' /* carriage return */ 3242305Sbostic #define ESC '\033' /* escape */ 3342305Sbostic #define SI '\017' /* shift in to normal character set */ 3442305Sbostic #define SO '\016' /* shift out to alternate character set */ 3542305Sbostic #define VT '\013' /* vertical tab (aka reverse line feed) */ 3646233Storek #define RLF '\007' /* ESC-07 reverse line feed */ 3746233Storek #define RHLF '\010' /* ESC-010 reverse half-line feed */ 3846233Storek #define FHLF '\011' /* ESC-011 forward half-line feed */ 39982Sbill 4042270Sbostic /* build up at least this many lines before flushing them out */ 4142270Sbostic #define BUFFER_MARGIN 32 42982Sbill 4342270Sbostic typedef char CSET; 44982Sbill 4542270Sbostic typedef struct char_str { 4642270Sbostic #define CS_NORMAL 1 4742270Sbostic #define CS_ALTERNATE 2 4842270Sbostic short c_column; /* column character is in */ 4942270Sbostic CSET c_set; /* character set (currently only 2) */ 5042270Sbostic char c_char; /* character in question */ 5142270Sbostic } CHAR; 52982Sbill 5342270Sbostic typedef struct line_str LINE; 5442270Sbostic struct line_str { 5542270Sbostic CHAR *l_line; /* characters on the line */ 5642270Sbostic LINE *l_prev; /* previous line */ 5742270Sbostic LINE *l_next; /* next line */ 5842270Sbostic int l_lsize; /* allocated sizeof l_line */ 5942270Sbostic int l_line_len; /* strlen(l_line) */ 6042270Sbostic int l_needs_sort; /* set if chars went in out of order */ 6142270Sbostic int l_max_col; /* max column in the line */ 6242270Sbostic }; 63982Sbill 6466590Spendry LINE *alloc_line __P((void)); 6566590Spendry void dowarn __P((int)); 6666590Spendry void flush_line __P((LINE *)); 6766590Spendry void flush_lines __P((int)); 6866590Spendry void flush_blanks __P((void)); 6966590Spendry void free_line __P((LINE *)); 7066590Spendry void usage __P((void)); 7166590Spendry void wrerr __P((void)); 7266590Spendry void *xmalloc __P((void *, size_t)); 73982Sbill 7466590Spendry CSET last_set; /* char_set of last char printed */ 7566590Spendry LINE *lines; 7666590Spendry int compress_spaces; /* if doing space -> tab conversion */ 7766590Spendry int fine; /* if `fine' resolution (half lines) */ 7866590Spendry int max_bufd_lines; /* max # lines to keep in memory */ 7966590Spendry int nblank_lines; /* # blanks after last flushed line */ 8066590Spendry int no_backspaces; /* if not to output any backspaces */ 81982Sbill 8242270Sbostic #define PUTC(ch) \ 8342270Sbostic if (putchar(ch) == EOF) \ 8442270Sbostic wrerr(); 85982Sbill 8666590Spendry int 8742270Sbostic main(argc, argv) 8842270Sbostic int argc; 8942270Sbostic char **argv; 9042270Sbostic { 9166590Spendry int ch; 9242270Sbostic CHAR *c; 9342270Sbostic CSET cur_set; /* current character set */ 9442270Sbostic LINE *l; /* current line */ 9542270Sbostic int extra_lines; /* # of lines above first line */ 9642270Sbostic int cur_col; /* current column */ 9742270Sbostic int cur_line; /* line number of current position */ 9842270Sbostic int max_line; /* max value of cur_line */ 9942270Sbostic int this_line; /* line l points to */ 10042270Sbostic int nflushd_lines; /* number of lines that were flushed */ 10142270Sbostic int adjust, opt, warned; 102982Sbill 10342270Sbostic max_bufd_lines = 128; 10442270Sbostic compress_spaces = 1; /* compress spaces into tabs */ 10542270Sbostic while ((opt = getopt(argc, argv, "bfhl:x")) != EOF) 10642270Sbostic switch (opt) { 10742270Sbostic case 'b': /* do not output backspaces */ 10842270Sbostic no_backspaces = 1; 10942270Sbostic break; 11042270Sbostic case 'f': /* allow half forward line feeds */ 11142270Sbostic fine = 1; 11242270Sbostic break; 11342270Sbostic case 'h': /* compress spaces into tabs */ 11442270Sbostic compress_spaces = 1; 11542270Sbostic break; 11642270Sbostic case 'l': /* buffered line count */ 11742270Sbostic if ((max_bufd_lines = atoi(optarg)) <= 0) { 11842270Sbostic (void)fprintf(stderr, 11942270Sbostic "col: bad -l argument %s.\n", optarg); 12042270Sbostic exit(1); 121982Sbill } 12242270Sbostic break; 12342270Sbostic case 'x': /* do not compress spaces into tabs */ 12442270Sbostic compress_spaces = 0; 12542270Sbostic break; 12642270Sbostic case '?': 12742270Sbostic default: 12842270Sbostic usage(); 12942270Sbostic } 130982Sbill 13142270Sbostic if (optind != argc) 13242270Sbostic usage(); 133982Sbill 13442270Sbostic /* this value is in half lines */ 13542270Sbostic max_bufd_lines *= 2; 136982Sbill 13742270Sbostic adjust = cur_col = extra_lines = warned = 0; 13842270Sbostic cur_line = max_line = nflushd_lines = this_line = 0; 13942270Sbostic cur_set = last_set = CS_NORMAL; 14042270Sbostic lines = l = alloc_line(); 141982Sbill 14242270Sbostic while ((ch = getchar()) != EOF) { 14342270Sbostic if (!isgraph(ch)) { 14442270Sbostic switch (ch) { 14542270Sbostic case BS: /* can't go back further */ 14642270Sbostic if (cur_col == 0) 14742270Sbostic continue; 14842270Sbostic --cur_col; 14942270Sbostic continue; 15042270Sbostic case CR: 15142270Sbostic cur_col = 0; 15242270Sbostic continue; 15342270Sbostic case ESC: /* just ignore EOF */ 15442270Sbostic switch(getchar()) { 15542270Sbostic case RLF: 15642270Sbostic cur_line -= 2; 15742270Sbostic break; 15842270Sbostic case RHLF: 15942270Sbostic cur_line--; 16042270Sbostic break; 16142270Sbostic case FHLF: 16242270Sbostic cur_line++; 16342270Sbostic if (cur_line > max_line) 16442270Sbostic max_line = cur_line; 16542270Sbostic } 16642270Sbostic continue; 16742270Sbostic case NL: 16842270Sbostic cur_line += 2; 16942270Sbostic if (cur_line > max_line) 17042270Sbostic max_line = cur_line; 17142270Sbostic cur_col = 0; 17242270Sbostic continue; 17342270Sbostic case SPACE: 17442270Sbostic ++cur_col; 17542270Sbostic continue; 17642270Sbostic case SI: 17742270Sbostic cur_set = CS_NORMAL; 17842270Sbostic continue; 17942270Sbostic case SO: 18042270Sbostic cur_set = CS_ALTERNATE; 18142270Sbostic continue; 18242270Sbostic case TAB: /* adjust column */ 18342270Sbostic cur_col |= 7; 18442270Sbostic ++cur_col; 18542270Sbostic continue; 18642270Sbostic case VT: 18742270Sbostic cur_line -= 2; 18842270Sbostic continue; 18942270Sbostic } 190982Sbill continue; 19142270Sbostic } 192982Sbill 19342270Sbostic /* Must stuff ch in a line - are we at the right one? */ 19442270Sbostic if (cur_line != this_line - adjust) { 19542270Sbostic LINE *lnew; 19642270Sbostic int nmove; 197982Sbill 19842270Sbostic adjust = 0; 19942270Sbostic nmove = cur_line - this_line; 20042270Sbostic if (!fine) { 20142270Sbostic /* round up to next line */ 20242270Sbostic if (cur_line & 1) { 20342270Sbostic adjust = 1; 20442270Sbostic nmove++; 20542270Sbostic } 206982Sbill } 20742270Sbostic if (nmove < 0) { 20842270Sbostic for (; nmove < 0 && l->l_prev; nmove++) 20942270Sbostic l = l->l_prev; 21042270Sbostic if (nmove) { 21142270Sbostic if (nflushd_lines == 0) { 21242270Sbostic /* 21342270Sbostic * Allow backup past first 21442270Sbostic * line if nothing has been 21542270Sbostic * flushed yet. 21642270Sbostic */ 21742270Sbostic for (; nmove < 0; nmove++) { 21842270Sbostic lnew = alloc_line(); 21942270Sbostic l->l_prev = lnew; 22042270Sbostic lnew->l_next = l; 22142270Sbostic l = lines = lnew; 22242270Sbostic extra_lines++; 22342270Sbostic } 22442270Sbostic } else { 22542270Sbostic if (!warned++) 22666590Spendry dowarn(cur_line); 22742270Sbostic cur_line -= nmove; 22842270Sbostic } 22942270Sbostic } 23042270Sbostic } else { 23142270Sbostic /* may need to allocate here */ 23242270Sbostic for (; nmove > 0 && l->l_next; nmove--) 23342270Sbostic l = l->l_next; 23442270Sbostic for (; nmove > 0; nmove--) { 23542270Sbostic lnew = alloc_line(); 23642270Sbostic lnew->l_prev = l; 23742270Sbostic l->l_next = lnew; 23842270Sbostic l = lnew; 23942270Sbostic } 24042270Sbostic } 24142270Sbostic this_line = cur_line + adjust; 24242270Sbostic nmove = this_line - nflushd_lines; 24342270Sbostic if (nmove >= max_bufd_lines + BUFFER_MARGIN) { 24442270Sbostic nflushd_lines += nmove - max_bufd_lines; 24542270Sbostic flush_lines(nmove - max_bufd_lines); 24642270Sbostic } 247982Sbill } 24842270Sbostic /* grow line's buffer? */ 24942270Sbostic if (l->l_line_len + 1 >= l->l_lsize) { 25042270Sbostic int need; 25142270Sbostic 25242270Sbostic need = l->l_lsize ? l->l_lsize * 2 : 90; 25342270Sbostic l->l_line = (CHAR *)xmalloc((void *) l->l_line, 25442270Sbostic (unsigned) need * sizeof(CHAR)); 25542270Sbostic l->l_lsize = need; 25642270Sbostic } 25742270Sbostic c = &l->l_line[l->l_line_len++]; 25842270Sbostic c->c_char = ch; 25942270Sbostic c->c_set = cur_set; 26042270Sbostic c->c_column = cur_col; 26142270Sbostic /* 26242270Sbostic * If things are put in out of order, they will need sorting 26342270Sbostic * when it is flushed. 26442270Sbostic */ 26542270Sbostic if (cur_col < l->l_max_col) 26642270Sbostic l->l_needs_sort = 1; 26742270Sbostic else 26842270Sbostic l->l_max_col = cur_col; 26942270Sbostic cur_col++; 270982Sbill } 271*69013Sbostic if (max_line == 0) 272*69013Sbostic exit(0); /* no lines, so just exit */ 273*69013Sbostic 27442270Sbostic /* goto the last line that had a character on it */ 27542270Sbostic for (; l->l_next; l = l->l_next) 27642270Sbostic this_line++; 27742270Sbostic flush_lines(this_line - nflushd_lines + extra_lines + 1); 278982Sbill 27942270Sbostic /* make sure we leave things in a sane state */ 28042270Sbostic if (last_set != CS_NORMAL) 28142270Sbostic PUTC('\017'); 28242270Sbostic 28342270Sbostic /* flush out the last few blank lines */ 28442270Sbostic nblank_lines = max_line - this_line; 28542270Sbostic if (max_line & 1) 28642270Sbostic nblank_lines++; 28742270Sbostic else if (!nblank_lines) 28842270Sbostic /* missing a \n on the last line? */ 28942270Sbostic nblank_lines = 2; 29042270Sbostic flush_blanks(); 291982Sbill exit(0); 292982Sbill } 293982Sbill 29466590Spendry void 29542270Sbostic flush_lines(nflush) 29642270Sbostic int nflush; 297982Sbill { 29842270Sbostic LINE *l; 299982Sbill 30042270Sbostic while (--nflush >= 0) { 30142270Sbostic l = lines; 30242270Sbostic lines = l->l_next; 30342270Sbostic if (l->l_line) { 30442270Sbostic flush_blanks(); 30542270Sbostic flush_line(l); 306982Sbill } 30742270Sbostic nblank_lines++; 30842270Sbostic if (l->l_line) 30942270Sbostic (void)free((void *)l->l_line); 31042270Sbostic free_line(l); 311982Sbill } 31242270Sbostic if (lines) 31342270Sbostic lines->l_prev = NULL; 314982Sbill } 315982Sbill 31642270Sbostic /* 31742270Sbostic * Print a number of newline/half newlines. If fine flag is set, nblank_lines 31842270Sbostic * is the number of half line feeds, otherwise it is the number of whole line 31942270Sbostic * feeds. 32042270Sbostic */ 32166590Spendry void 32242270Sbostic flush_blanks() 323982Sbill { 32442270Sbostic int half, i, nb; 325982Sbill 32642270Sbostic half = 0; 32742270Sbostic nb = nblank_lines; 32842270Sbostic if (nb & 1) { 32942270Sbostic if (fine) 33042270Sbostic half = 1; 33142270Sbostic else 33242270Sbostic nb++; 333982Sbill } 33442270Sbostic nb /= 2; 33542270Sbostic for (i = nb; --i >= 0;) 33642270Sbostic PUTC('\n'); 33742270Sbostic if (half) { 33842270Sbostic PUTC('\033'); 33942270Sbostic PUTC('9'); 34042270Sbostic if (!nb) 34142270Sbostic PUTC('\r'); 34242270Sbostic } 34342270Sbostic nblank_lines = 0; 344982Sbill } 345982Sbill 34642270Sbostic /* 34742270Sbostic * Write a line to stdout taking care of space to tab conversion (-h flag) 34842270Sbostic * and character set shifts. 34942270Sbostic */ 35066590Spendry void 35142270Sbostic flush_line(l) 35242270Sbostic LINE *l; 353982Sbill { 35442270Sbostic CHAR *c, *endc; 35542270Sbostic int nchars, last_col, this_col; 356982Sbill 35742270Sbostic last_col = 0; 35842270Sbostic nchars = l->l_line_len; 359982Sbill 36042270Sbostic if (l->l_needs_sort) { 36142270Sbostic static CHAR *sorted; 36242270Sbostic static int count_size, *count, i, save, sorted_size, tot; 36342270Sbostic 36442270Sbostic /* 36542270Sbostic * Do an O(n) sort on l->l_line by column being careful to 36642270Sbostic * preserve the order of characters in the same column. 36742270Sbostic */ 36842270Sbostic if (l->l_lsize > sorted_size) { 36942270Sbostic sorted_size = l->l_lsize; 37042270Sbostic sorted = (CHAR *)xmalloc((void *)sorted, 37142270Sbostic (unsigned)sizeof(CHAR) * sorted_size); 372982Sbill } 37342270Sbostic if (l->l_max_col >= count_size) { 37442270Sbostic count_size = l->l_max_col + 1; 37542270Sbostic count = (int *)xmalloc((void *)count, 37642270Sbostic (unsigned)sizeof(int) * count_size); 377982Sbill } 37866590Spendry memset((char *)count, 0, sizeof(int) * l->l_max_col + 1); 37942270Sbostic for (i = nchars, c = l->l_line; --i >= 0; c++) 38042270Sbostic count[c->c_column]++; 38142270Sbostic 38242270Sbostic /* 38342270Sbostic * calculate running total (shifted down by 1) to use as 38442270Sbostic * indices into new line. 38542270Sbostic */ 38642270Sbostic for (tot = 0, i = 0; i <= l->l_max_col; i++) { 38742270Sbostic save = count[i]; 38842270Sbostic count[i] = tot; 38942270Sbostic tot += save; 39042270Sbostic } 39142270Sbostic 39242270Sbostic for (i = nchars, c = l->l_line; --i >= 0; c++) 39342270Sbostic sorted[count[c->c_column]++] = *c; 39442270Sbostic c = sorted; 39542270Sbostic } else 39642270Sbostic c = l->l_line; 39742270Sbostic while (nchars > 0) { 39842270Sbostic this_col = c->c_column; 39942270Sbostic endc = c; 40042270Sbostic do { 40142270Sbostic ++endc; 40242270Sbostic } while (--nchars > 0 && this_col == endc->c_column); 40342270Sbostic 40442270Sbostic /* if -b only print last character */ 40542270Sbostic if (no_backspaces) 40642270Sbostic c = endc - 1; 40742270Sbostic 40842270Sbostic if (this_col > last_col) { 40942270Sbostic int nspace = this_col - last_col; 41042270Sbostic 41142270Sbostic if (compress_spaces && nspace > 1) { 41242270Sbostic int ntabs; 41342270Sbostic 41442270Sbostic ntabs = this_col / 8 - last_col / 8; 41542270Sbostic nspace -= ntabs * 8; 41642270Sbostic while (--ntabs >= 0) 41742270Sbostic PUTC('\t'); 41842270Sbostic } 41942270Sbostic while (--nspace >= 0) 42042270Sbostic PUTC(' '); 42142270Sbostic last_col = this_col; 42242270Sbostic } 42342270Sbostic last_col++; 42442270Sbostic 42542270Sbostic for (;;) { 42642270Sbostic if (c->c_set != last_set) { 42742270Sbostic switch (c->c_set) { 42842270Sbostic case CS_NORMAL: 42942270Sbostic PUTC('\017'); 43042270Sbostic break; 43142270Sbostic case CS_ALTERNATE: 43242270Sbostic PUTC('\016'); 433982Sbill } 43442270Sbostic last_set = c->c_set; 435982Sbill } 43642270Sbostic PUTC(c->c_char); 43742270Sbostic if (++c >= endc) 438982Sbill break; 43942270Sbostic PUTC('\b'); 440982Sbill } 441982Sbill } 442982Sbill } 443982Sbill 44442270Sbostic #define NALLOC 64 44542270Sbostic 44642270Sbostic static LINE *line_freelist; 44742270Sbostic 44842270Sbostic LINE * 44942270Sbostic alloc_line() 450982Sbill { 45142270Sbostic LINE *l; 45242270Sbostic int i; 45342270Sbostic 45442270Sbostic if (!line_freelist) { 45542270Sbostic l = (LINE *)xmalloc((void *)NULL, sizeof(LINE) * NALLOC); 45642270Sbostic line_freelist = l; 45742270Sbostic for (i = 1; i < NALLOC; i++, l++) 45842270Sbostic l->l_next = l + 1; 45942270Sbostic l->l_next = NULL; 460982Sbill } 46142270Sbostic l = line_freelist; 46242270Sbostic line_freelist = l->l_next; 46342270Sbostic 46466590Spendry memset(l, 0, sizeof(LINE)); 46566590Spendry return (l); 466982Sbill } 467982Sbill 46866590Spendry void 46942270Sbostic free_line(l) 47042270Sbostic LINE *l; 471982Sbill { 47266590Spendry 47342270Sbostic l->l_next = line_freelist; 47442270Sbostic line_freelist = l; 47542270Sbostic } 47642270Sbostic 47742270Sbostic void * 47842270Sbostic xmalloc(p, size) 47942270Sbostic void *p; 48042270Sbostic size_t size; 48142270Sbostic { 48266590Spendry 48366590Spendry if (!(p = (void *)realloc(p, size))) 48466590Spendry err(1, NULL); 48566590Spendry return (p); 486982Sbill } 48742270Sbostic 48866590Spendry void 48942270Sbostic usage() 49042270Sbostic { 49166590Spendry 49242270Sbostic (void)fprintf(stderr, "usage: col [-bfx] [-l nline]\n"); 49342270Sbostic exit(1); 49442270Sbostic } 49542270Sbostic 49666590Spendry void 49742270Sbostic wrerr() 49842270Sbostic { 49966590Spendry 50042270Sbostic (void)fprintf(stderr, "col: write error.\n"); 50142270Sbostic exit(1); 50242270Sbostic } 50342270Sbostic 50466590Spendry void 50566590Spendry dowarn(line) 50642270Sbostic int line; 50742270Sbostic { 50866590Spendry 50966590Spendry warnx("warning: can't back up %s", 51066590Spendry line < 0 ? "past first line" : "-- line already flushed"); 51142270Sbostic } 512