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