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