1 /***********************************************************************
2 * *
3 * This software is part of the ast package *
4 * Copyright (c) 1989-2011 AT&T Intellectual Property *
5 * and is licensed under the *
6 * Eclipse Public License, Version 1.0 *
7 * by AT&T Intellectual Property *
8 * *
9 * A copy of the License is available at *
10 * http://www.eclipse.org/org/documents/epl-v10.html *
11 * (with md5 checksum b35adb5213ca9657e911e9befb180842) *
12 * *
13 * Information and Software Systems Research *
14 * AT&T Research *
15 * Florham Park NJ *
16 * *
17 * Glenn Fowler <gsf@research.att.com> *
18 * *
19 ***********************************************************************/
20 #pragma prototyped
21 /*
22 * expand.c and unexpand.c
23 * They can be linked together
24 * Written by David Korn
25 * Sat Oct 8 13:47:13 EDT 1994
26 */
27
28 static const char expand_usage[] =
29 "[-?@(#)$Id: expand (AT&T Research) 1999-06-17 $\n]"
30 USAGE_LICENSE
31 "[+NAME?expand - convert tabs to spaces]"
32 "[+DESCRIPTION?\bexpand\b writes the contents of each given file "
33 "to standard output with tab characters replaced with one or "
34 "more space characters needed to pad to the next tab stop. Each "
35 "backspace character copied to standard output causes the column "
36 "position to be decremented by 1 unless already in the first column.]" "[+?If no \afile\a is given, or if the \afile\a is \b-\b, \bexpand\b "
37 "copies from standard input. The start of the file is defined "
38 "as the current offset.]"
39 "[i:initial? Only convert initial tabs (those that precede all non "
40 "space or tab characters) on each line to spaces.]"
41 "[t:tabs]:[tablist:=8?\atablist\a is a comma or space separated list "
42 "of positive integers that specifies the tab stops. If only one "
43 "tab stop is specified, then tabs will be set at that many "
44 "column positions apart. Otherwise, the value in \atablist\a "
45 "must be in ascending order and the tab stops will be set to "
46 "these positions. In the event of \bexpand\b having to process "
47 "tab characters beyond the last specified tab stop, each tab "
48 "character is replaced by a single tab.]"
49 "\n"
50 "\n[file ...]\n"
51 "\n"
52 "[+EXIT STATUS]{"
53 "[+0?All files expanded successfully.]"
54 "[+>0?One or more files failed to open or could not be read.]"
55 "}"
56 "[+SEE ALSO?\bunexpand\b(1), \bpr\b(1)]"
57 ;
58
59 static const char unexpand_usage[] =
60 "[-?@(#)$Id: unexpand (AT&T Research) 1999-06-07 $\n]"
61 USAGE_LICENSE
62 "[+NAME?unexpand - convert spaces to tabs]"
63 "[+DESCRIPTION?\bunexpand\b writes the contents of each given file "
64 "to standard output with strings of two or more space and "
65 "tab characters at the beginning of each line converted "
66 "to as many tabs as possible followed by as many spaces needed "
67 "to fill the same number of column positions. By default, "
68 "tabs are set at every 8th column. Each backspace character copied "
69 "to standard output causes the column position to be decremented by 1 "
70 "unless already in the first column.]"
71 "[+?If no \afile\a is given, or if the \afile\a is \b-\b, \bunexpand\b "
72 "copies from standard input. The start of the file is defined "
73 "as the current offset.]"
74 "[a:all?Convert all strings of two or more spaces or tabs, not just "
75 "initial ones.]"
76 "[t:tabs]:[tablist:=8?\atablist\a is a comma or space separated list "
77 "of positive integers that specifies the tab stops. If only one "
78 "tab stop is specified, then tabs will be set at that many "
79 "column positions apart. Otherwise, the value in \atablist\a "
80 "must be in ascending order and the tab stops will be set to "
81 "these positions. This option implies the \b-a\b option.]"
82 "\n"
83 "\n[file ...]\n"
84 "\n"
85 "[+EXIT STATUS?]{"
86 "[+0?All files unexpanded successfully.]"
87 "[+>0?One or more files failed to open or could not be read.]"
88 "}"
89 "[+SEE ALSO?\bexpand\b(1), \bpr\b(1)]"
90 ;
91
92
93 #include <cmd.h>
94
95 #define S_BS 1
96 #define S_TAB 2
97 #define S_NL 3
98 #define S_EOF 4
99 #define S_SPACE 5
100
gettabs(const char * arg,int * ntab)101 static int *gettabs(const char *arg, int *ntab)
102 {
103 register const char *cp=arg;
104 register int c,n=1,old= -1;
105 int *tablist;
106 while(c= *cp++)
107 {
108 if(c==' ' || c=='\t' || c==',')
109 n++;
110 }
111 tablist = newof(NiL,int,n,0);
112 n=0;
113 while(1)
114 {
115 cp=arg;
116 while((c= *cp) && c==' ' || c=='\t' || c==',')
117 cp++;
118 if(c==0)
119 break;
120 tablist[n] = strtol(cp,(char**)&arg,10)-1;
121 if(cp==arg)
122 error(ERROR_exit(1),"%c - invalid character in tablist",*cp);
123 if(tablist[n] <= old)
124 error(ERROR_exit(1),"tab stops must be increasing");
125 old = tablist[n++];
126 }
127 *ntab=n;
128 return(tablist);
129 }
130
expand(Sfio_t * in,Sfio_t * out,int tablist[],int tabmax,int type,int initial)131 static int expand(Sfio_t *in, Sfio_t *out, int tablist[], int tabmax, int type,int initial)
132 {
133 static char state[256];
134 register int n=0;
135 register char *cp, *first, *buff;
136 register int savec;
137 register char *cpend;
138 state[0] = S_EOF;
139 state['\b'] = S_BS;
140 state['\n'] = S_NL;
141 if(type)
142 state['\t'] = S_TAB;
143 else
144 state[' '] = S_SPACE;
145 errno=0;
146 while(buff = cp = sfreserve(in,SF_UNBOUND,0))
147 {
148 first = cp-n;
149 cpend = cp + sfvalue(in);
150 if(state[n= *(unsigned char*)(cpend-1)]==0 || (n==' '&&type==0))
151 cpend[-1] = 0; /* put in sentinal */
152 savec = n;
153 while(1)
154 {
155 while((n=state[*(unsigned char*)cp++])==0);
156 switch(n)
157 {
158 case S_SPACE:
159 if(tabmax==0 || cp==first+1)
160 {
161 register int i,tabspace = tablist[0];
162 n = 1;
163 cp -= 1;
164 if(tabmax==0)
165 tabspace -= (cp-first)%tabspace;
166 while(cp[n]==' ')
167 n++;
168 i = cp-buff;
169 /* check for end of buffer */
170 if(cp[n]==0 && cp+n+1==cpend && savec==' ')
171 {
172 /* keep grabbing spaces */
173 register int c;
174 if(i)
175 sfwrite(out,buff, i);
176 i=0;
177 n++;
178 while((c=sfgetc(in))==' ')
179 n++;
180 if(c!=EOF)
181 sfungetc(in,c);
182 buff = cp+n;
183 }
184 cp += n;
185 if(n >= tabspace || cp+n>cpend)
186 {
187 if(i)
188 {
189 sfwrite(out,buff, i);
190 i=0;
191 }
192 buff = cp;
193 while(n >= tabspace)
194 {
195 sfputc(out,'\t');
196 n -= tabspace;
197 if(++i < tabmax)
198 tabspace = tablist[i]-tablist[i-1];
199 else if(tabmax<=1)
200 tabspace = tablist[0];
201 else
202 break;
203
204 }
205 if(n>0)
206 sfnputc(out,' ',n);
207 }
208 }
209 if(tabmax)
210 state[' '] = 0;
211 break;
212 case S_TAB:
213 sfwrite(out,buff, (cp-1)-buff);
214 n = (cp-1)-first;
215 if(tabmax==1)
216 n = tablist[0]*((n+tablist[0])/tablist[0])-n;
217 else
218 {
219 register int i=0;
220 while(1)
221 {
222 if(i>=tabmax)
223 n=1;
224 else if(tablist[i++]<=n)
225 continue;
226 else
227 n = tablist[i-1]-n;
228 break;
229 }
230 }
231 if(n<=1)
232 sfputc(out,' ');
233 else
234 {
235 sfnputc(out,' ',n);
236 first -= (n-1);
237 }
238 buff = cp;
239 if(initial)
240 state[' '] = 0;
241 break;
242 case S_BS:
243 if((first+=2) > cp)
244 first = cp;
245 break;
246 case S_EOF:
247 if(cp==cpend)
248 {
249 /* end of buffer */
250 cp[-1] = savec;
251 }
252 break;
253 case S_NL:
254 first = cp;
255 if(type==0)
256 state[' '] = S_SPACE;
257 else
258 state['\t'] = S_TAB;
259 break;
260 }
261 if(cp >= cpend)
262 {
263 if((n=cp-buff)>0)
264 sfwrite(out, buff, n);
265 n = cp-first;
266 break;
267 }
268 }
269 }
270 return(errno);
271 }
272
273 int
main(int argc,char ** argv)274 main(int argc, char** argv)
275 {
276 register int n,type;
277 register char *cp;
278 register Sfio_t *fp;
279 int ntabs= -1;
280 int tabstop=8;
281 int initial=0;
282 int *tablist=&tabstop;
283 const char *usage;
284
285 if (cp = strrchr(argv[0], '/')) cp++;
286 else cp = argv[0];
287 error_info.id = cp;
288 type = *cp == 'e';
289 if(type)
290 usage = expand_usage;
291 else
292 usage = unexpand_usage;
293 while (n = optget(argv, usage)) switch (n)
294 {
295 case 't':
296 tablist = gettabs(opt_info.arg, &ntabs);
297 break;
298 case 'i':
299 initial = 1;
300 break;
301 case 'a':
302 if(ntabs < 0)
303 ntabs = 0;
304 break;
305 case ':':
306 cp = argv[opt_info.index]+1;
307 if(*cp>='0' && *cp<='9')
308 tablist = gettabs(cp, &ntabs);
309 else
310 error(2, "%s", opt_info.arg);
311 break;
312 case '?':
313 error(ERROR_usage(2), "%s", opt_info.arg);
314 break;
315 }
316 argv += opt_info.index;
317 if(ntabs<0)
318 ntabs=1;
319 else if(ntabs==1)
320 tablist[0] += 1;
321 if(error_info.errors)
322 error(ERROR_usage(2), "%s", optusage((char*)0));
323 if(cp= *argv)
324 argv++;
325 do
326 {
327 if (!cp || streq(cp,"-"))
328 fp = sfstdin;
329 else if (!(fp = sfopen(NiL, cp, "r")))
330 {
331 error(ERROR_system(0), "%s: cannot open", cp);
332 error_info.errors = 1;
333 continue;
334 }
335 if(expand(fp,sfstdout,tablist,ntabs,type,initial)<0)
336 error(ERROR_system(1), "failed");
337 if (fp != sfstdin)
338 sfclose(fp);
339 } while (cp = *argv++);
340 return(error_info.errors);
341 }
342