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