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