(add_tabstop): Give correct size when reallocating tab_list buffer.
[platform/upstream/coreutils.git] / src / expand.c
1 /* expand - convert tabs to spaces
2    Copyright (C) 1989, 1991, 1995 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
17
18 /* By default, convert all tabs to spaces.
19    Preserves backspace characters in the output; they decrement the
20    column count for tab calculations.
21    The default action is equivalent to -8.
22
23    Options:
24    --tabs=tab1[,tab2[,...]]
25    -t tab1[,tab2[,...]]
26    -tab1[,tab2[,...]]   If only one tab stop is given, set the tabs tab1
27                         spaces apart instead of the default 8.  Otherwise,
28                         set the tabs at columns tab1, tab2, etc. (numbered from
29                         0); replace any tabs beyond the tabstops given with
30                         single spaces.
31    --initial
32    -i                   Only convert initial tabs on each line to spaces.
33
34    David MacKenzie <djm@gnu.ai.mit.edu> */
35
36 #include <config.h>
37
38 /* Get isblank from GNU libc.  */
39 #define _GNU_SOURCE
40
41 #include <stdio.h>
42 #include <getopt.h>
43 #include <sys/types.h>
44 #include "system.h"
45 #include "version.h"
46 #include "error.h"
47
48 /* The number of bytes added at a time to the amount of memory
49    allocated for the output line. */
50 #define OUTPUT_BLOCK 256
51
52 /* The number of bytes added at a time to the amount of memory
53    allocated for the list of tabstops. */
54 #define TABLIST_BLOCK 256
55
56 char *xmalloc ();
57 char *xrealloc ();
58
59 static FILE *next_file ();
60 static void add_tabstop ();
61 static void expand ();
62 static void parse_tabstops ();
63 static void usage ();
64 static void validate_tabstops ();
65
66 /* The name this program was run with. */
67 char *program_name;
68
69 /* If nonzero, convert blanks even after nonblank characters have been
70    read on the line. */
71 static int convert_entire_line;
72
73 /* If nonzero, the size of all tab stops.  If zero, use `tab_list' instead. */
74 static int tab_size;
75
76 /* Array of the explicit column numbers of the tab stops;
77    after `tab_list' is exhausted, each additional tab is replaced
78    by a space.  The first column is column 0. */
79 static int *tab_list;
80
81 /* The index of the first invalid element of `tab_list',
82    where the next element can be added. */
83 static int first_free_tab;
84
85 /* Null-terminated array of input filenames. */
86 static char **file_list;
87
88 /* Default for `file_list' if no files are given on the command line. */
89 static char *stdin_argv[] =
90 {
91   "-", NULL
92 };
93
94 /* Nonzero if we have ever read standard input. */
95 static int have_read_stdin;
96
97 /* Status to return to the system. */
98 static int exit_status;
99
100 /* If non-zero, display usage information and exit.  */
101 static int show_help;
102
103 /* If non-zero, print the version on standard output then exit.  */
104 static int show_version;
105
106 static struct option const longopts[] =
107 {
108   {"tabs", required_argument, NULL, 't'},
109   {"initial", no_argument, NULL, 'i'},
110   {"help", no_argument, &show_help, 1},
111   {"version", no_argument, &show_version, 1},
112   {NULL, 0, NULL, 0}
113 };
114
115 void
116 main (argc, argv)
117      int argc;
118      char **argv;
119 {
120   int tabval = -1;              /* Value of tabstop being read, or -1. */
121   int c;                        /* Option character. */
122
123   have_read_stdin = 0;
124   exit_status = 0;
125   convert_entire_line = 1;
126   tab_list = NULL;
127   first_free_tab = 0;
128   program_name = argv[0];
129
130   while ((c = getopt_long (argc, argv, "it:,0123456789", longopts, (int *) 0))
131          != EOF)
132     {
133       switch (c)
134         {
135         case 0:
136           break;
137
138         case '?':
139           usage (1);
140         case 'i':
141           convert_entire_line = 0;
142           break;
143         case 't':
144           parse_tabstops (optarg);
145           break;
146         case ',':
147           add_tabstop (tabval);
148           tabval = -1;
149           break;
150         default:
151           if (tabval == -1)
152             tabval = 0;
153           tabval = tabval * 10 + c - '0';
154           break;
155         }
156     }
157
158   if (show_version)
159     {
160       printf ("expand - %s\n", version_string);
161       exit (0);
162     }
163
164   if (show_help)
165     usage (0);
166
167   add_tabstop (tabval);
168
169   validate_tabstops (tab_list, first_free_tab);
170
171   if (first_free_tab == 0)
172     tab_size = 8;
173   else if (first_free_tab == 1)
174     tab_size = tab_list[0];
175   else
176     tab_size = 0;
177
178   if (optind == argc)
179     file_list = stdin_argv;
180   else
181     file_list = &argv[optind];
182
183   expand ();
184
185   if (have_read_stdin && fclose (stdin) == EOF)
186     error (1, errno, "-");
187   if (ferror (stdout) || fclose (stdout) == EOF)
188     error (1, errno, "write error");
189
190   exit (exit_status);
191 }
192
193 /* Add the comma or blank separated list of tabstops STOPS
194    to the list of tabstops. */
195
196 static void
197 parse_tabstops (stops)
198      char *stops;
199 {
200   int tabval = -1;
201
202   for (; *stops; stops++)
203     {
204       if (*stops == ',' || ISBLANK (*stops))
205         {
206           add_tabstop (tabval);
207           tabval = -1;
208         }
209       else if (ISDIGIT (*stops))
210         {
211           if (tabval == -1)
212             tabval = 0;
213           tabval = tabval * 10 + *stops - '0';
214         }
215       else
216         error (1, 0, "tab size contains an invalid character");
217     }
218
219   add_tabstop (tabval);
220 }
221
222 /* Add tab stop TABVAL to the end of `tab_list', except
223    if TABVAL is -1, do nothing. */
224
225 static void
226 add_tabstop (tabval)
227      int tabval;
228 {
229   if (tabval == -1)
230     return;
231   if (first_free_tab % TABLIST_BLOCK == 0)
232     tab_list = (int *) xrealloc (tab_list, first_free_tab
233                                  + TABLIST_BLOCK * sizeof (tab_list[0]));
234   tab_list[first_free_tab++] = tabval;
235 }
236
237 /* Check that the list of tabstops TABS, with ENTRIES entries,
238    contains only nonzero, ascending values. */
239
240 static void
241 validate_tabstops (tabs, entries)
242      int *tabs;
243      int entries;
244 {
245   int prev_tab = 0;
246   int i;
247
248   for (i = 0; i < entries; i++)
249     {
250       if (tabs[i] == 0)
251         error (1, 0, "tab size cannot be 0");
252       if (tabs[i] <= prev_tab)
253         error (1, 0, "tab sizes must be ascending");
254       prev_tab = tabs[i];
255     }
256 }
257
258 /* Change tabs to spaces, writing to stdout.
259    Read each file in `file_list', in order. */
260
261 static void
262 expand ()
263 {
264   FILE *fp;                     /* Input stream. */
265   int c;                        /* Each input character. */
266   int tab_index = 0;            /* Index in `tab_list' of next tabstop. */
267   int column = 0;               /* Column on screen of the next char. */
268   int next_tab_column;          /* Column the next tab stop is on. */
269   int convert = 1;              /* If nonzero, perform translations. */
270
271   fp = next_file ((FILE *) NULL);
272   if (fp == NULL)
273     return;
274   for (;;)
275     {
276       c = getc (fp);
277       if (c == EOF)
278         {
279           fp = next_file (fp);
280           if (fp == NULL)
281             break;              /* No more files. */
282           else
283             continue;
284         }
285
286       if (c == '\n')
287         {
288           putchar (c);
289           tab_index = 0;
290           column = 0;
291           convert = 1;
292         }
293       else if (c == '\t' && convert)
294         {
295           if (tab_size == 0)
296             {
297               /* Do not let tab_index == first_free_tab;
298                  stop when it is 1 less. */
299               while (tab_index < first_free_tab - 1
300                      && column >= tab_list[tab_index])
301                 tab_index++;
302               next_tab_column = tab_list[tab_index];
303               if (tab_index < first_free_tab - 1)
304                 tab_index++;
305               if (column >= next_tab_column)
306                 next_tab_column = column + 1; /* Ran out of tab stops. */
307             }
308           else
309             {
310               next_tab_column = column + tab_size - column % tab_size;
311             }
312           while (column < next_tab_column)
313             {
314               putchar (' ');
315               ++column;
316             }
317         }
318       else
319         {
320           if (convert)
321             {
322               if (c == '\b')
323                 {
324                   if (column > 0)
325                     --column;
326                 }
327               else
328                 {
329                   ++column;
330                   if (convert_entire_line == 0)
331                     convert = 0;
332                 }
333             }
334           putchar (c);
335         }
336     }
337 }
338
339 /* Close the old stream pointer FP if it is non-NULL,
340    and return a new one opened to read the next input file.
341    Open a filename of `-' as the standard input.
342    Return NULL if there are no more input files.  */
343
344 static FILE *
345 next_file (fp)
346      FILE *fp;
347 {
348   static char *prev_file;
349   char *file;
350
351   if (fp)
352     {
353       if (ferror (fp))
354         {
355           error (0, errno, "%s", prev_file);
356           exit_status = 1;
357         }
358       if (fp == stdin)
359         clearerr (fp);          /* Also clear EOF. */
360       else if (fclose (fp) == EOF)
361         {
362           error (0, errno, "%s", prev_file);
363           exit_status = 1;
364         }
365     }
366
367   while ((file = *file_list++) != NULL)
368     {
369       if (file[0] == '-' && file[1] == '\0')
370         {
371           have_read_stdin = 1;
372           prev_file = file;
373           return stdin;
374         }
375       fp = fopen (file, "r");
376       if (fp)
377         {
378           prev_file = file;
379           return fp;
380         }
381       error (0, errno, "%s", file);
382       exit_status = 1;
383     }
384   return NULL;
385 }
386
387 static void
388 usage (status)
389      int status;
390 {
391   if (status != 0)
392     fprintf (stderr, "Try `%s --help' for more information.\n",
393              program_name);
394   else
395     {
396       printf ("\
397 Usage: %s [OPTION]... [FILE]...\n\
398 ",
399               program_name);
400       printf ("\
401 \n\
402   -i, --initial       do not convert TABs after non whitespace\n\
403   -t, --tabs=NUMBER   have tabs NUMBER characters apart, not 8\n\
404   -t, --tabs=LIST     use comma separated list of explicit tab positions\n\
405       --help          display this help and exit\n\
406       --version       output version information and exit\n\
407 \n\
408 Instead of -t NUMBER or -t LIST, -NUMBER or -LIST may be used.  With\n\
409 no FILE, or when FILE is -, read standard input.\n\
410 ");
411     }
412   exit (status);
413 }