1 /*- 2 * Copyright (c) 2012 Ed Schouten <ed@FreeBSD.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27 #include <sys/cdefs.h> 28 #include <ctype.h> 29 #include <stdbool.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 33 static char *queue = NULL; 34 static size_t queuelen = 0, queuesize = 0; 35 static off_t column = 0; 36 37 static void 38 savebyte(char c) 39 { 40 41 if (queuelen >= queuesize) { 42 queuesize += 128; 43 queue = realloc(queue, queuesize); 44 if (queue == NULL) { 45 perror("malloc"); 46 exit(1); 47 } 48 } 49 queue[queuelen++] = c; 50 51 switch (c) { 52 case '\n': 53 column = 0; 54 break; 55 case ' ': 56 column++; 57 break; 58 case '\t': 59 column = (column / 8 + 1) * 8; 60 break; 61 } 62 } 63 64 static bool 65 peekbyte(size_t back, char c) 66 { 67 68 return (queuelen >= back && queue[queuelen - back] == c); 69 } 70 71 static void 72 savewhite(char c, bool leading) 73 { 74 off_t ncolumn; 75 76 switch (c) { 77 case '\n': 78 if (leading) { 79 /* Remove empty lines before input. */ 80 queuelen = 0; 81 column = 0; 82 } else { 83 /* Remove trailing whitespace. */ 84 while (peekbyte(1, ' ') || peekbyte(1, '\t')) 85 queuelen--; 86 /* Remove redundant empty lines. */ 87 if (peekbyte(2, '\n') && peekbyte(1, '\n')) 88 return; 89 savebyte('\n'); 90 } 91 break; 92 case ' ': 93 savebyte(' '); 94 break; 95 case '\t': 96 /* Convert preceding spaces to tabs. */ 97 ncolumn = (column / 8 + 1) * 8; 98 while (peekbyte(1, ' ')) { 99 queuelen--; 100 column--; 101 } 102 while (column < ncolumn) 103 savebyte('\t'); 104 break; 105 } 106 } 107 108 static void 109 printwhite(void) 110 { 111 off_t i; 112 113 /* Merge spaces at the start of a sentence to tabs if possible. */ 114 if ((column % 8) == 0) { 115 for (i = 0; i < column; i++) 116 if (!peekbyte(i + 1, ' ')) 117 break; 118 if (i == column) { 119 queuelen -= column; 120 for (i = 0; i < column; i += 8) 121 queue[queuelen++] = '\t'; 122 } 123 } 124 125 if (fwrite(queue, 1, queuelen, stdout) != queuelen) { 126 perror("write"); 127 exit(1); 128 } 129 queuelen = 0; 130 } 131 132 static char 133 readchar(void) 134 { 135 int c; 136 137 c = getchar(); 138 if (c == EOF && ferror(stdin)) { 139 perror("read"); 140 exit(1); 141 } 142 return (c); 143 } 144 145 static void 146 writechar(char c) 147 { 148 149 if (putchar(c) == EOF) { 150 perror("write"); 151 exit(1); 152 } 153 /* XXX: Multi-byte characters. */ 154 column++; 155 } 156 157 int 158 main(void) 159 { 160 int c; 161 bool leading = true; 162 163 while ((c = readchar()) != EOF) { 164 if (isspace(c)) 165 /* Save whitespace. */ 166 savewhite(c, leading); 167 else { 168 /* Reprint whitespace and print regular character. */ 169 printwhite(); 170 writechar(c); 171 leading = false; 172 } 173 } 174 /* Terminate non-empty files with a newline. */ 175 if (!leading) 176 writechar('\n'); 177 return (0); 178 } 179