1 1.20 rillig /* $NetBSD: col.c,v 1.20 2021/09/10 21:52:17 rillig Exp $ */ 2 1.6 glass 3 1.1 cgd /*- 4 1.19 christos * SPDX-License-Identifier: BSD-3-Clause 5 1.19 christos * 6 1.6 glass * Copyright (c) 1990, 1993, 1994 7 1.6 glass * The Regents of the University of California. All rights reserved. 8 1.1 cgd * 9 1.1 cgd * This code is derived from software contributed to Berkeley by 10 1.1 cgd * Michael Rendell of the Memorial University of Newfoundland. 11 1.1 cgd * 12 1.1 cgd * Redistribution and use in source and binary forms, with or without 13 1.1 cgd * modification, are permitted provided that the following conditions 14 1.1 cgd * are met: 15 1.1 cgd * 1. Redistributions of source code must retain the above copyright 16 1.1 cgd * notice, this list of conditions and the following disclaimer. 17 1.1 cgd * 2. Redistributions in binary form must reproduce the above copyright 18 1.1 cgd * notice, this list of conditions and the following disclaimer in the 19 1.1 cgd * documentation and/or other materials provided with the distribution. 20 1.12 agc * 3. Neither the name of the University nor the names of its contributors 21 1.1 cgd * may be used to endorse or promote products derived from this software 22 1.1 cgd * without specific prior written permission. 23 1.1 cgd * 24 1.1 cgd * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 1.1 cgd * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 1.1 cgd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 1.1 cgd * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 1.1 cgd * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 1.1 cgd * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 1.1 cgd * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 1.1 cgd * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 1.1 cgd * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 1.1 cgd * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 1.1 cgd * SUCH DAMAGE. 35 1.1 cgd */ 36 1.1 cgd 37 1.8 lukem #include <sys/cdefs.h> 38 1.1 cgd #ifndef lint 39 1.15 lukem __COPYRIGHT("@(#) Copyright (c) 1990, 1993, 1994\ 40 1.15 lukem The Regents of the University of California. All rights reserved."); 41 1.1 cgd #endif /* not lint */ 42 1.19 christos 43 1.1 cgd #ifndef lint 44 1.6 glass #if 0 45 1.7 jtc static char sccsid[] = "@(#)col.c 8.5 (Berkeley) 5/4/95"; 46 1.19 christos __FBSDID("$FreeBSD: head/usr.bin/col/col.c 366577 2020-10-09 15:27:37Z markj $") 47 1.19 christos ; 48 1.19 christos 49 1.6 glass #endif 50 1.20 rillig __RCSID("$NetBSD: col.c,v 1.20 2021/09/10 21:52:17 rillig Exp $"); 51 1.1 cgd #endif /* not lint */ 52 1.1 cgd 53 1.6 glass #include <err.h> 54 1.19 christos #include <errno.h> 55 1.19 christos #include <inttypes.h> 56 1.19 christos #include <limits.h> 57 1.19 christos #include <locale.h> 58 1.1 cgd #include <stdio.h> 59 1.5 cgd #include <stdlib.h> 60 1.19 christos #include <string.h> 61 1.19 christos #include <termios.h> 62 1.7 jtc #include <unistd.h> 63 1.19 christos #include <wchar.h> 64 1.19 christos #include <wctype.h> 65 1.1 cgd 66 1.1 cgd #define BS '\b' /* backspace */ 67 1.1 cgd #define TAB '\t' /* tab */ 68 1.1 cgd #define SPACE ' ' /* space */ 69 1.1 cgd #define NL '\n' /* newline */ 70 1.1 cgd #define CR '\r' /* carriage return */ 71 1.1 cgd #define ESC '\033' /* escape */ 72 1.1 cgd #define SI '\017' /* shift in to normal character set */ 73 1.1 cgd #define SO '\016' /* shift out to alternate character set */ 74 1.1 cgd #define VT '\013' /* vertical tab (aka reverse line feed) */ 75 1.19 christos #define RLF '7' /* ESC-7 reverse line feed */ 76 1.19 christos #define RHLF '8' /* ESC-8 reverse half-line feed */ 77 1.19 christos #define FHLF '9' /* ESC-9 forward half-line feed */ 78 1.1 cgd 79 1.1 cgd /* build up at least this many lines before flushing them out */ 80 1.1 cgd #define BUFFER_MARGIN 32 81 1.1 cgd 82 1.1 cgd typedef char CSET; 83 1.1 cgd 84 1.1 cgd typedef struct char_str { 85 1.1 cgd #define CS_NORMAL 1 86 1.1 cgd #define CS_ALTERNATE 2 87 1.19 christos int c_column; /* column character is in */ 88 1.1 cgd CSET c_set; /* character set (currently only 2) */ 89 1.19 christos wchar_t c_char; /* character in question */ 90 1.19 christos int c_width; /* character width */ 91 1.1 cgd } CHAR; 92 1.1 cgd 93 1.1 cgd typedef struct line_str LINE; 94 1.1 cgd struct line_str { 95 1.1 cgd CHAR *l_line; /* characters on the line */ 96 1.1 cgd LINE *l_prev; /* previous line */ 97 1.1 cgd LINE *l_next; /* next line */ 98 1.1 cgd int l_lsize; /* allocated sizeof l_line */ 99 1.1 cgd int l_line_len; /* strlen(l_line) */ 100 1.1 cgd int l_needs_sort; /* set if chars went in out of order */ 101 1.1 cgd int l_max_col; /* max column in the line */ 102 1.1 cgd }; 103 1.1 cgd 104 1.19 christos static void addto_lineno(int *, int); 105 1.19 christos static LINE *alloc_line(void); 106 1.16 joerg static void dowarn(int); 107 1.16 joerg static void flush_line(LINE *); 108 1.16 joerg static void flush_lines(int); 109 1.16 joerg static void flush_blanks(void); 110 1.16 joerg static void free_line(LINE *); 111 1.16 joerg __dead static void usage(void); 112 1.16 joerg 113 1.16 joerg static CSET last_set; /* char_set of last char printed */ 114 1.19 christos static LINE *lines; 115 1.16 joerg static int compress_spaces; /* if doing space -> tab conversion */ 116 1.16 joerg static int fine; /* if `fine' resolution (half lines) */ 117 1.19 christos static int max_bufd_lines; /* max # of half lines to keep in memory */ 118 1.16 joerg static int nblank_lines; /* # blanks after last flushed line */ 119 1.16 joerg static int no_backspaces; /* if not to output any backspaces */ 120 1.19 christos static int pass_unknown_seqs; /* pass unknown control sequences */ 121 1.1 cgd 122 1.1 cgd #define PUTC(ch) \ 123 1.19 christos do { \ 124 1.19 christos if (putwchar(ch) == WEOF) \ 125 1.19 christos errx(EXIT_FAILURE, "write error"); \ 126 1.20 rillig } while (0) 127 1.1 cgd 128 1.6 glass int 129 1.14 xtraeme main(int argc, char **argv) 130 1.1 cgd { 131 1.19 christos wint_t ch; 132 1.1 cgd CHAR *c; 133 1.1 cgd CSET cur_set; /* current character set */ 134 1.1 cgd LINE *l; /* current line */ 135 1.1 cgd int extra_lines; /* # of lines above first line */ 136 1.1 cgd int cur_col; /* current column */ 137 1.1 cgd int cur_line; /* line number of current position */ 138 1.1 cgd int max_line; /* max value of cur_line */ 139 1.1 cgd int this_line; /* line l points to */ 140 1.1 cgd int nflushd_lines; /* number of lines that were flushed */ 141 1.19 christos int adjust, opt, warned, width; 142 1.19 christos int e; 143 1.1 cgd 144 1.19 christos (void)setlocale(LC_CTYPE, ""); 145 1.19 christos 146 1.19 christos max_bufd_lines = 256; 147 1.1 cgd compress_spaces = 1; /* compress spaces into tabs */ 148 1.11 tron while ((opt = getopt(argc, argv, "bfhl:px")) != -1) 149 1.1 cgd switch (opt) { 150 1.1 cgd case 'b': /* do not output backspaces */ 151 1.1 cgd no_backspaces = 1; 152 1.1 cgd break; 153 1.1 cgd case 'f': /* allow half forward line feeds */ 154 1.1 cgd fine = 1; 155 1.1 cgd break; 156 1.1 cgd case 'h': /* compress spaces into tabs */ 157 1.1 cgd compress_spaces = 1; 158 1.1 cgd break; 159 1.1 cgd case 'l': /* buffered line count */ 160 1.19 christos max_bufd_lines = (int)strtoi(optarg, NULL, 0, 1, 161 1.19 christos (INT_MAX - BUFFER_MARGIN) / 2, &e) * 2; 162 1.19 christos if (e) 163 1.19 christos errc(EXIT_FAILURE, e, "bad -l argument `%s'", 164 1.19 christos optarg); 165 1.1 cgd break; 166 1.10 kleink case 'p': /* pass unknown control sequences */ 167 1.10 kleink pass_unknown_seqs = 1; 168 1.10 kleink break; 169 1.1 cgd case 'x': /* do not compress spaces into tabs */ 170 1.1 cgd compress_spaces = 0; 171 1.1 cgd break; 172 1.1 cgd case '?': 173 1.1 cgd default: 174 1.1 cgd usage(); 175 1.1 cgd } 176 1.1 cgd 177 1.1 cgd if (optind != argc) 178 1.1 cgd usage(); 179 1.1 cgd 180 1.1 cgd adjust = cur_col = extra_lines = warned = 0; 181 1.1 cgd cur_line = max_line = nflushd_lines = this_line = 0; 182 1.1 cgd cur_set = last_set = CS_NORMAL; 183 1.1 cgd lines = l = alloc_line(); 184 1.1 cgd 185 1.19 christos while ((ch = getwchar()) != WEOF) { 186 1.19 christos if (!iswgraph(ch)) { 187 1.1 cgd switch (ch) { 188 1.1 cgd case BS: /* can't go back further */ 189 1.1 cgd if (cur_col == 0) 190 1.1 cgd continue; 191 1.1 cgd --cur_col; 192 1.1 cgd continue; 193 1.1 cgd case CR: 194 1.1 cgd cur_col = 0; 195 1.1 cgd continue; 196 1.1 cgd case ESC: /* just ignore EOF */ 197 1.19 christos switch(getwchar()) { 198 1.19 christos /* 199 1.19 christos * In the input stream, accept both the 200 1.19 christos * XPG5 sequences ESC-digit and the 201 1.19 christos * traditional BSD sequences ESC-ctrl. 202 1.19 christos */ 203 1.19 christos case '\007': 204 1.19 christos /* FALLTHROUGH */ 205 1.1 cgd case RLF: 206 1.19 christos addto_lineno(&cur_line, -2); 207 1.1 cgd break; 208 1.19 christos case '\010': 209 1.19 christos /* FALLTHROUGH */ 210 1.1 cgd case RHLF: 211 1.19 christos addto_lineno(&cur_line, -1); 212 1.1 cgd break; 213 1.19 christos case '\011': 214 1.19 christos /* FALLTHROUGH */ 215 1.1 cgd case FHLF: 216 1.19 christos addto_lineno(&cur_line, 1); 217 1.1 cgd if (cur_line > max_line) 218 1.1 cgd max_line = cur_line; 219 1.1 cgd } 220 1.1 cgd continue; 221 1.1 cgd case NL: 222 1.19 christos addto_lineno(&cur_line, 2); 223 1.1 cgd if (cur_line > max_line) 224 1.1 cgd max_line = cur_line; 225 1.1 cgd cur_col = 0; 226 1.1 cgd continue; 227 1.1 cgd case SPACE: 228 1.1 cgd ++cur_col; 229 1.1 cgd continue; 230 1.1 cgd case SI: 231 1.1 cgd cur_set = CS_NORMAL; 232 1.1 cgd continue; 233 1.1 cgd case SO: 234 1.1 cgd cur_set = CS_ALTERNATE; 235 1.1 cgd continue; 236 1.1 cgd case TAB: /* adjust column */ 237 1.1 cgd cur_col |= 7; 238 1.1 cgd ++cur_col; 239 1.1 cgd continue; 240 1.1 cgd case VT: 241 1.19 christos addto_lineno(&cur_line, -2); 242 1.19 christos continue; 243 1.19 christos } 244 1.19 christos if (iswspace(ch)) { 245 1.19 christos if ((width = wcwidth(ch)) > 0) 246 1.19 christos cur_col += width; 247 1.1 cgd continue; 248 1.1 cgd } 249 1.10 kleink if (!pass_unknown_seqs) 250 1.10 kleink continue; 251 1.1 cgd } 252 1.1 cgd 253 1.1 cgd /* Must stuff ch in a line - are we at the right one? */ 254 1.19 christos if (cur_line + adjust != this_line) { 255 1.1 cgd LINE *lnew; 256 1.1 cgd 257 1.19 christos /* round up to next line */ 258 1.19 christos adjust = !fine && (cur_line & 1); 259 1.19 christos 260 1.19 christos if (cur_line + adjust < this_line) { 261 1.19 christos while (cur_line + adjust < this_line && 262 1.19 christos l->l_prev != NULL) { 263 1.19 christos l = l->l_prev; 264 1.19 christos this_line--; 265 1.1 cgd } 266 1.19 christos if (cur_line + adjust < this_line) { 267 1.1 cgd if (nflushd_lines == 0) { 268 1.1 cgd /* 269 1.1 cgd * Allow backup past first 270 1.1 cgd * line if nothing has been 271 1.1 cgd * flushed yet. 272 1.1 cgd */ 273 1.19 christos while (cur_line + adjust 274 1.19 christos < this_line) { 275 1.1 cgd lnew = alloc_line(); 276 1.1 cgd l->l_prev = lnew; 277 1.1 cgd lnew->l_next = l; 278 1.1 cgd l = lines = lnew; 279 1.1 cgd extra_lines++; 280 1.19 christos this_line--; 281 1.1 cgd } 282 1.1 cgd } else { 283 1.1 cgd if (!warned++) 284 1.6 glass dowarn(cur_line); 285 1.19 christos cur_line = this_line - adjust; 286 1.1 cgd } 287 1.1 cgd } 288 1.1 cgd } else { 289 1.1 cgd /* may need to allocate here */ 290 1.19 christos while (cur_line + adjust > this_line) { 291 1.19 christos if (l->l_next == NULL) { 292 1.19 christos l->l_next = alloc_line(); 293 1.19 christos l->l_next->l_prev = l; 294 1.19 christos } 295 1.1 cgd l = l->l_next; 296 1.19 christos this_line++; 297 1.1 cgd } 298 1.1 cgd } 299 1.19 christos if (this_line > nflushd_lines && 300 1.19 christos this_line - nflushd_lines >= 301 1.19 christos max_bufd_lines + BUFFER_MARGIN) { 302 1.19 christos if (extra_lines) { 303 1.19 christos flush_lines(extra_lines); 304 1.19 christos extra_lines = 0; 305 1.19 christos } 306 1.19 christos flush_lines(this_line - nflushd_lines - 307 1.19 christos max_bufd_lines); 308 1.19 christos nflushd_lines = this_line - max_bufd_lines; 309 1.1 cgd } 310 1.1 cgd } 311 1.1 cgd /* grow line's buffer? */ 312 1.1 cgd if (l->l_line_len + 1 >= l->l_lsize) { 313 1.1 cgd int need; 314 1.1 cgd 315 1.1 cgd need = l->l_lsize ? l->l_lsize * 2 : 90; 316 1.19 christos if ((l->l_line = realloc(l->l_line, 317 1.19 christos (unsigned)need * sizeof(CHAR))) == NULL) 318 1.19 christos err(EXIT_FAILURE, NULL); 319 1.1 cgd l->l_lsize = need; 320 1.1 cgd } 321 1.1 cgd c = &l->l_line[l->l_line_len++]; 322 1.1 cgd c->c_char = ch; 323 1.1 cgd c->c_set = cur_set; 324 1.1 cgd c->c_column = cur_col; 325 1.19 christos c->c_width = wcwidth(ch); 326 1.1 cgd /* 327 1.1 cgd * If things are put in out of order, they will need sorting 328 1.1 cgd * when it is flushed. 329 1.1 cgd */ 330 1.1 cgd if (cur_col < l->l_max_col) 331 1.1 cgd l->l_needs_sort = 1; 332 1.1 cgd else 333 1.1 cgd l->l_max_col = cur_col; 334 1.19 christos if (c->c_width > 0) 335 1.19 christos cur_col += c->c_width; 336 1.19 christos } 337 1.19 christos if (ferror(stdin)) 338 1.19 christos err(EXIT_FAILURE, NULL); 339 1.19 christos if (extra_lines) { 340 1.19 christos /* 341 1.19 christos * Extra lines only exist if no lines have been flushed 342 1.19 christos * yet. This means that 'lines' must point to line zero 343 1.19 christos * after we flush the extra lines. 344 1.19 christos */ 345 1.19 christos flush_lines(extra_lines); 346 1.19 christos l = lines; 347 1.19 christos this_line = 0; 348 1.1 cgd } 349 1.7 jtc 350 1.1 cgd /* goto the last line that had a character on it */ 351 1.1 cgd for (; l->l_next; l = l->l_next) 352 1.1 cgd this_line++; 353 1.19 christos flush_lines(this_line - nflushd_lines + 1); 354 1.1 cgd 355 1.1 cgd /* make sure we leave things in a sane state */ 356 1.1 cgd if (last_set != CS_NORMAL) 357 1.19 christos PUTC(SI); 358 1.1 cgd 359 1.1 cgd /* flush out the last few blank lines */ 360 1.19 christos if (max_line >= this_line) 361 1.19 christos nblank_lines = max_line - this_line + (max_line & 1); 362 1.19 christos if (nblank_lines == 0) 363 1.19 christos /* end with a newline even if the source doesn't */ 364 1.1 cgd nblank_lines = 2; 365 1.1 cgd flush_blanks(); 366 1.10 kleink exit(EXIT_SUCCESS); 367 1.1 cgd } 368 1.1 cgd 369 1.19 christos /* 370 1.19 christos * Prints the first 'nflush' lines. Printed lines are freed. 371 1.19 christos * After this function returns, 'lines' points to the first 372 1.19 christos * of the remaining lines, and 'nblank_lines' will have the 373 1.19 christos * number of half line feeds between the final flushed line 374 1.19 christos * and the first remaining line. 375 1.19 christos */ 376 1.16 joerg static void 377 1.14 xtraeme flush_lines(int nflush) 378 1.1 cgd { 379 1.1 cgd LINE *l; 380 1.1 cgd 381 1.1 cgd while (--nflush >= 0) { 382 1.1 cgd l = lines; 383 1.1 cgd lines = l->l_next; 384 1.1 cgd if (l->l_line) { 385 1.1 cgd flush_blanks(); 386 1.1 cgd flush_line(l); 387 1.19 christos free(l->l_line); 388 1.1 cgd } 389 1.19 christos if (l->l_next) 390 1.19 christos nblank_lines++; 391 1.1 cgd free_line(l); 392 1.1 cgd } 393 1.1 cgd if (lines) 394 1.1 cgd lines->l_prev = NULL; 395 1.1 cgd } 396 1.1 cgd 397 1.1 cgd /* 398 1.19 christos * Print a number of newline/half newlines. 399 1.19 christos * nblank_lines is the number of half line feeds. 400 1.1 cgd */ 401 1.16 joerg static void 402 1.14 xtraeme flush_blanks(void) 403 1.1 cgd { 404 1.1 cgd int half, i, nb; 405 1.1 cgd 406 1.1 cgd half = 0; 407 1.1 cgd nb = nblank_lines; 408 1.1 cgd if (nb & 1) { 409 1.1 cgd if (fine) 410 1.1 cgd half = 1; 411 1.1 cgd else 412 1.1 cgd nb++; 413 1.1 cgd } 414 1.1 cgd nb /= 2; 415 1.1 cgd for (i = nb; --i >= 0;) 416 1.1 cgd PUTC('\n'); 417 1.1 cgd if (half) { 418 1.19 christos PUTC(ESC); 419 1.19 christos PUTC(FHLF); 420 1.1 cgd if (!nb) 421 1.1 cgd PUTC('\r'); 422 1.1 cgd } 423 1.1 cgd nblank_lines = 0; 424 1.1 cgd } 425 1.1 cgd 426 1.1 cgd /* 427 1.1 cgd * Write a line to stdout taking care of space to tab conversion (-h flag) 428 1.1 cgd * and character set shifts. 429 1.1 cgd */ 430 1.16 joerg static void 431 1.14 xtraeme flush_line(LINE *l) 432 1.1 cgd { 433 1.1 cgd CHAR *c, *endc; 434 1.19 christos int i, j, nchars, last_col, save, this_col, tot; 435 1.1 cgd 436 1.1 cgd last_col = 0; 437 1.1 cgd nchars = l->l_line_len; 438 1.1 cgd 439 1.1 cgd if (l->l_needs_sort) { 440 1.1 cgd static CHAR *sorted; 441 1.19 christos static int count_size, *count, sorted_size; 442 1.1 cgd 443 1.1 cgd /* 444 1.1 cgd * Do an O(n) sort on l->l_line by column being careful to 445 1.1 cgd * preserve the order of characters in the same column. 446 1.1 cgd */ 447 1.1 cgd if (l->l_lsize > sorted_size) { 448 1.1 cgd sorted_size = l->l_lsize; 449 1.19 christos if ((sorted = realloc(sorted, 450 1.19 christos sizeof(CHAR) * (size_t)sorted_size)) == NULL) 451 1.19 christos err(EXIT_FAILURE, NULL); 452 1.1 cgd } 453 1.1 cgd if (l->l_max_col >= count_size) { 454 1.1 cgd count_size = l->l_max_col + 1; 455 1.19 christos if ((count = realloc(count, 456 1.19 christos sizeof(int) * (size_t)count_size)) == NULL) 457 1.19 christos err(EXIT_FAILURE, NULL); 458 1.1 cgd } 459 1.19 christos memset(count, 0, sizeof(int) * (size_t)l->l_max_col + 1); 460 1.1 cgd for (i = nchars, c = l->l_line; --i >= 0; c++) 461 1.1 cgd count[c->c_column]++; 462 1.1 cgd 463 1.1 cgd /* 464 1.1 cgd * calculate running total (shifted down by 1) to use as 465 1.1 cgd * indices into new line. 466 1.1 cgd */ 467 1.1 cgd for (tot = 0, i = 0; i <= l->l_max_col; i++) { 468 1.1 cgd save = count[i]; 469 1.1 cgd count[i] = tot; 470 1.1 cgd tot += save; 471 1.1 cgd } 472 1.1 cgd 473 1.1 cgd for (i = nchars, c = l->l_line; --i >= 0; c++) 474 1.1 cgd sorted[count[c->c_column]++] = *c; 475 1.1 cgd c = sorted; 476 1.1 cgd } else 477 1.1 cgd c = l->l_line; 478 1.1 cgd while (nchars > 0) { 479 1.1 cgd this_col = c->c_column; 480 1.1 cgd endc = c; 481 1.1 cgd do { 482 1.1 cgd ++endc; 483 1.1 cgd } while (--nchars > 0 && this_col == endc->c_column); 484 1.1 cgd 485 1.1 cgd /* if -b only print last character */ 486 1.19 christos if (no_backspaces) { 487 1.1 cgd c = endc - 1; 488 1.19 christos if (nchars > 0 && 489 1.19 christos this_col + c->c_width > endc->c_column) 490 1.19 christos continue; 491 1.19 christos } 492 1.1 cgd 493 1.1 cgd if (this_col > last_col) { 494 1.1 cgd int nspace = this_col - last_col; 495 1.1 cgd 496 1.1 cgd if (compress_spaces && nspace > 1) { 497 1.19 christos while (1) { 498 1.19 christos int tab_col, tab_size; 499 1.1 cgd 500 1.19 christos tab_col = (last_col + 8) & ~7; 501 1.19 christos if (tab_col > this_col) 502 1.19 christos break; 503 1.19 christos tab_size = tab_col - last_col; 504 1.19 christos if (tab_size == 1) 505 1.19 christos PUTC(' '); 506 1.19 christos else 507 1.4 mycroft PUTC('\t'); 508 1.19 christos nspace -= tab_size; 509 1.19 christos last_col = tab_col; 510 1.4 mycroft } 511 1.1 cgd } 512 1.1 cgd while (--nspace >= 0) 513 1.1 cgd PUTC(' '); 514 1.1 cgd last_col = this_col; 515 1.1 cgd } 516 1.1 cgd 517 1.1 cgd for (;;) { 518 1.1 cgd if (c->c_set != last_set) { 519 1.1 cgd switch (c->c_set) { 520 1.1 cgd case CS_NORMAL: 521 1.19 christos PUTC(SI); 522 1.1 cgd break; 523 1.1 cgd case CS_ALTERNATE: 524 1.19 christos PUTC(SO); 525 1.1 cgd } 526 1.1 cgd last_set = c->c_set; 527 1.1 cgd } 528 1.1 cgd PUTC(c->c_char); 529 1.19 christos if ((c + 1) < endc) 530 1.19 christos for (j = 0; j < c->c_width; j++) 531 1.19 christos PUTC('\b'); 532 1.1 cgd if (++c >= endc) 533 1.1 cgd break; 534 1.1 cgd } 535 1.19 christos last_col += (c - 1)->c_width; 536 1.19 christos } 537 1.19 christos } 538 1.19 christos 539 1.19 christos /* 540 1.19 christos * Increment or decrement a line number, checking for overflow. 541 1.19 christos * Stop one below INT_MAX such that the adjust variable is safe. 542 1.19 christos */ 543 1.19 christos void 544 1.19 christos addto_lineno(int *lno, int offset) 545 1.19 christos { 546 1.19 christos if (offset > 0) { 547 1.19 christos if (*lno >= INT_MAX - offset) 548 1.19 christos errx(EXIT_FAILURE, "too many lines"); 549 1.19 christos } else { 550 1.19 christos if (*lno < INT_MIN - offset) 551 1.19 christos errx(EXIT_FAILURE, "too many reverse line feeds"); 552 1.1 cgd } 553 1.19 christos *lno += offset; 554 1.1 cgd } 555 1.1 cgd 556 1.1 cgd #define NALLOC 64 557 1.1 cgd 558 1.1 cgd static LINE *line_freelist; 559 1.1 cgd 560 1.16 joerg static LINE * 561 1.14 xtraeme alloc_line(void) 562 1.1 cgd { 563 1.1 cgd LINE *l; 564 1.1 cgd int i; 565 1.1 cgd 566 1.1 cgd if (!line_freelist) { 567 1.19 christos if ((l = realloc(NULL, sizeof(LINE) * NALLOC)) == NULL) 568 1.19 christos err(EXIT_FAILURE, NULL); 569 1.1 cgd line_freelist = l; 570 1.1 cgd for (i = 1; i < NALLOC; i++, l++) 571 1.1 cgd l->l_next = l + 1; 572 1.1 cgd l->l_next = NULL; 573 1.1 cgd } 574 1.1 cgd l = line_freelist; 575 1.1 cgd line_freelist = l->l_next; 576 1.1 cgd 577 1.19 christos memset(l, 0, sizeof(LINE)); 578 1.6 glass return (l); 579 1.1 cgd } 580 1.1 cgd 581 1.16 joerg static void 582 1.14 xtraeme free_line(LINE *l) 583 1.1 cgd { 584 1.6 glass 585 1.1 cgd l->l_next = line_freelist; 586 1.1 cgd line_freelist = l; 587 1.1 cgd } 588 1.1 cgd 589 1.16 joerg static void 590 1.14 xtraeme usage(void) 591 1.1 cgd { 592 1.6 glass 593 1.19 christos (void)fprintf(stderr, "Usage: %s [-bfhpx] [-l nline]\n", getprogname()); 594 1.10 kleink exit(EXIT_FAILURE); 595 1.1 cgd } 596 1.1 cgd 597 1.16 joerg static void 598 1.14 xtraeme dowarn(int line) 599 1.1 cgd { 600 1.6 glass 601 1.6 glass warnx("warning: can't back up %s", 602 1.6 glass line < 0 ? "past first line" : "-- line already flushed"); 603 1.1 cgd } 604