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
savebyte(char c)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
peekbyte(size_t back,char c)65 peekbyte(size_t back, char c)
66 {
67
68 return (queuelen >= back && queue[queuelen - back] == c);
69 }
70
71 static void
savewhite(char c,bool leading)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
printwhite(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
readchar(void)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
writechar(char c)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
main(void)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