*** empty log message ***
[platform/upstream/coreutils.git] / src / unexpand.c
index 8ba563a..d22aead 100644 (file)
@@ -1,5 +1,5 @@
-/* unexpand - convert spaces to tabs
-   Copyright (C) 89, 91, 1995-2003 Free Software Foundation, Inc.
+/* unexpand - convert blanks to tabs
+   Copyright (C) 89, 91, 1995-2006 Free Software Foundation, Inc.
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -13,7 +13,7 @@
 
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software Foundation,
-   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
 
 /* By default, convert only maximal strings of initial blanks and tabs
    into tabs.
    --tabs=tab1[,tab2[,...]]
    -t tab1[,tab2[,...]]
    -tab1[,tab2[,...]]  If only one tab stop is given, set the tabs tab1
-                       spaces apart instead of the default 8.  Otherwise,
+                       columns apart instead of the default 8.  Otherwise,
                        set the tabs at columns tab1, tab2, etc. (numbered from
-                       0); replace any tabs beyond the tabstops given with
-                       single spaces.
+                       0); preserve any blanks beyond the tab stops given.
    --all
-   -a                  Use tabs wherever they would replace 2 or more spaces,
+   -a                  Use tabs wherever they would replace 2 or more blanks,
                        not just at the beginnings of lines.
 
    David MacKenzie <djm@gnu.ai.mit.edu> */
 #include <sys/types.h>
 #include "system.h"
 #include "error.h"
-#include "posixver.h"
+#include "quote.h"
+#include "xstrndup.h"
 
 /* The official name of this program (e.g., no `g' prefix).  */
 #define PROGRAM_NAME "unexpand"
 
-#define WRITTEN_BY _("Written by David MacKenzie.")
+#define AUTHORS "David MacKenzie"
 
 /* The number of bytes added at a time to the amount of memory
-   allocated for the output line. */
+   allocated for the output line.  */
 #define OUTPUT_BLOCK 256
 
-/* The number of bytes added at a time to the amount of memory
-   allocated for the list of tabstops. */
-#define TABLIST_BLOCK 256
-
-/* A sentinel value that's placed at the end of the list of tab stops.
-   This value must be a large number, but not so large that adding the
-   length of a line to it would cause the column variable to overflow.  */
-#define TAB_STOP_SENTINEL INT_MAX
-
-/* The name this program was run with. */
+/* The name this program was run with.  */
 char *program_name;
 
-/* If nonzero, convert blanks even after nonblank characters have been
-   read on the line. */
-static int convert_entire_line;
+/* If true, convert blanks even after nonblank characters have been
+   read on the line.  */
+static bool convert_entire_line;
 
-/* If nonzero, the size of all tab stops.  If zero, use `tab_list' instead. */
-static int tab_size;
+/* If nonzero, the size of all tab stops.  If zero, use `tab_list' instead.  */
+static size_t tab_size;
+
+/* The maximum distance between tab stops.  */
+static size_t max_column_width;
 
 /* Array of the explicit column numbers of the tab stops;
    after `tab_list' is exhausted, the rest of the line is printed
-   unchanged.  The first column is column 0. */
-static int *tab_list;
+   unchanged.  The first column is column 0.  */
+static uintmax_t *tab_list;
+
+/* The number of allocated entries in `tab_list'.  */
+static size_t n_tabs_allocated;
 
 /* The index of the first invalid element of `tab_list',
-   where the next element can be added. */
-static int first_free_tab;
+   where the next element can be added.  */
+static size_t first_free_tab;
 
-/* Null-terminated array of input filenames. */
+/* Null-terminated array of input filenames.  */
 static char **file_list;
 
-/* Default for `file_list' if no files are given on the command line. */
+/* Default for `file_list' if no files are given on the command line.  */
 static char *stdin_argv[] =
 {
   "-", NULL
 };
 
-/* Nonzero if we have ever read standard input. */
-static int have_read_stdin;
+/* True if we have ever read standard input.  */
+static bool have_read_stdin;
 
-/* Status to return to the system. */
+/* The desired exit status.  */
 static int exit_status;
 
 /* For long options that have no equivalent short option, use a
@@ -113,55 +110,122 @@ static struct option const longopts[] =
   {NULL, 0, NULL, 0}
 };
 
-/* Add tab stop TABVAL to the end of `tab_list', except
-   if TABVAL is -1, do nothing. */
+void
+usage (int status)
+{
+  if (status != EXIT_SUCCESS)
+    fprintf (stderr, _("Try `%s --help' for more information.\n"),
+            program_name);
+  else
+    {
+      printf (_("\
+Usage: %s [OPTION]... [FILE]...\n\
+"),
+             program_name);
+      fputs (_("\
+Convert blanks in each FILE to tabs, writing to standard output.\n\
+With no FILE, or when FILE is -, read standard input.\n\
+\n\
+"), stdout);
+      fputs (_("\
+Mandatory arguments to long options are mandatory for short options too.\n\
+"), stdout);
+      fputs (_("\
+  -a, --all        convert all blanks, instead of just initial blanks\n\
+      --first-only  convert only leading sequences of blanks (overrides -a)\n\
+  -t, --tabs=N     have tabs N characters apart instead of 8 (enables -a)\n\
+  -t, --tabs=LIST  use comma separated LIST of tab positions (enables -a)\n\
+"), stdout);
+      fputs (HELP_OPTION_DESCRIPTION, stdout);
+      fputs (VERSION_OPTION_DESCRIPTION, stdout);
+      printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+    }
+  exit (status);
+}
+
+/* Add tab stop TABVAL to the end of `tab_list'.  */
 
 static void
-add_tabstop (int tabval)
+add_tab_stop (uintmax_t tabval)
 {
-  if (tabval == -1)
-    return;
-  if (first_free_tab % TABLIST_BLOCK == 0)
-    tab_list = xrealloc (tab_list, first_free_tab + TABLIST_BLOCK);
+  uintmax_t prev_column = first_free_tab ? tab_list[first_free_tab - 1] : 0;
+  uintmax_t column_width = prev_column <= tabval ? tabval - prev_column : 0;
+
+  if (first_free_tab == n_tabs_allocated)
+    tab_list = X2NREALLOC (tab_list, &n_tabs_allocated);
   tab_list[first_free_tab++] = tabval;
+
+  if (max_column_width < column_width)
+    {
+      if (SIZE_MAX < column_width)
+       error (EXIT_FAILURE, 0, _("tabs are too far apart"));
+      max_column_width = column_width;
+    }
 }
 
-/* Add the comma or blank separated list of tabstops STOPS
-   to the list of tabstops. */
+/* Add the comma or blank separated list of tab stops STOPS
+   to the list of tab stops.  */
 
 static void
-parse_tabstops (const char *stops)
+parse_tab_stops (char const *stops)
 {
-  int tabval = -1;
+  bool have_tabval = false;
+  uintmax_t tabval IF_LINT (= 0);
+  char const *num_start IF_LINT (= NULL);
+  bool ok = true;
 
   for (; *stops; stops++)
     {
-      if (*stops == ',' || ISBLANK (*stops))
+      if (*stops == ',' || ISBLANK (to_uchar (*stops)))
        {
-         add_tabstop (tabval);
-         tabval = -1;
+         if (have_tabval)
+           add_tab_stop (tabval);
+         have_tabval = false;
        }
       else if (ISDIGIT (*stops))
        {
-         if (tabval == -1)
-           tabval = 0;
-         tabval = tabval * 10 + *stops - '0';
+         if (!have_tabval)
+           {
+             tabval = 0;
+             have_tabval = true;
+             num_start = stops;
+           }
+
+         /* Detect overflow.  */
+         if (!DECIMAL_DIGIT_ACCUMULATE (tabval, *stops - '0', uintmax_t))
+           {
+             size_t len = strspn (num_start, "0123456789");
+             char *bad_num = xstrndup (num_start, len);
+             error (0, 0, _("tab stop is too large %s"), quote (bad_num));
+             free (bad_num);
+             ok = false;
+             stops = num_start + len - 1;
+           }
        }
       else
-       error (EXIT_FAILURE, 0, _("tab size contains an invalid character"));
+       {
+         error (0, 0, _("tab size contains invalid character(s): %s"),
+                quote (stops));
+         ok = false;
+         break;
+       }
     }
 
-  add_tabstop (tabval);
+  if (!ok)
+    exit (EXIT_FAILURE);
+
+  if (have_tabval)
+    add_tab_stop (tabval);
 }
 
-/* Check that the list of tabstops TABS, with ENTRIES entries,
-   contains only nonzero, ascending values. */
+/* Check that the list of tab stops TABS, with ENTRIES entries,
+   contains only nonzero, ascending values.  */
 
 static void
-validate_tabstops (const int *tabs, int entries)
+validate_tab_stops (uintmax_t const *tabs, size_t entries)
 {
-  int prev_tab = 0;
-  int i;
+  uintmax_t prev_tab = 0;
+  size_t i;
 
   for (i = 0; i < entries; i++)
     {
@@ -189,22 +253,22 @@ next_file (FILE *fp)
       if (ferror (fp))
        {
          error (0, errno, "%s", prev_file);
-         exit_status = 1;
+         exit_status = EXIT_FAILURE;
        }
-      if (fp == stdin)
-       clearerr (fp);          /* Also clear EOF. */
-      else if (fclose (fp) == EOF)
+      if (STREQ (prev_file, "-"))
+       clearerr (fp);          /* Also clear EOF.  */
+      else if (fclose (fp) != 0)
        {
          error (0, errno, "%s", prev_file);
-         exit_status = 1;
+         exit_status = EXIT_FAILURE;
        }
     }
 
   while ((file = *file_list++) != NULL)
     {
-      if (file[0] == '-' && file[1] == '\0')
+      if (STREQ (file, "-"))
        {
-         have_read_stdin = 1;
+         have_read_stdin = true;
          prev_file = file;
          return stdin;
        }
@@ -215,197 +279,190 @@ next_file (FILE *fp)
          return fp;
        }
       error (0, errno, "%s", file);
-      exit_status = 1;
+      exit_status = EXIT_FAILURE;
     }
   return NULL;
 }
 
-/* Change spaces to tabs, writing to stdout.
-   Read each file in `file_list', in order. */
+/* Change blanks to tabs, writing to stdout.
+   Read each file in `file_list', in order.  */
 
 static void
 unexpand (void)
 {
-  FILE *fp;                    /* Input stream. */
-  int c;                       /* Each input character. */
-  /* Index in `tab_list' of next tabstop: */
-  int tab_index = 0;           /* For calculating width of pending tabs. */
-  int print_tab_index = 0;     /* For printing as many tabs as possible. */
-  unsigned int column = 0;     /* Column on screen of next char. */
-  int next_tab_column;         /* Column the next tab stop is on. */
-  int convert = 1;             /* If nonzero, perform translations. */
-  unsigned int pending = 0;    /* Pending columns of blanks. */
-
-  fp = next_file ((FILE *) NULL);
-  if (fp == NULL)
+  /* Input stream.  */
+  FILE *fp = next_file (NULL);
+
+  /* The array of pending blanks.  In non-POSIX locales, blanks can
+     include characters other than spaces, so the blanks must be
+     stored, not merely counted.  */
+  char *pending_blank;
+
+  if (!fp)
     return;
 
-  /* Binary I/O will preserve the original EOL style (DOS/Unix) of files.  */
-  SET_BINARY2 (fileno (fp), STDOUT_FILENO);
+  /* The worst case is a non-blank character, then one blank, then a
+     tab stop, then MAX_COLUMN_WIDTH - 1 blanks, then a non-blank; so
+     allocate MAX_COLUMN_WIDTH bytes to store the blanks.  */
+  pending_blank = xmalloc (max_column_width);
 
   for (;;)
     {
-      c = getc (fp);
+      /* Input character, or EOF.  */
+      int c;
 
-      if (c == ' ' && convert && column < TAB_STOP_SENTINEL)
-       {
-         ++pending;
-         ++column;
-       }
-      else if (c == '\t' && convert)
-       {
-         if (tab_size == 0)
-           {
-             /* Do not let tab_index == first_free_tab;
-                stop when it is 1 less. */
-             while (tab_index < first_free_tab - 1
-                    && column >= tab_list[tab_index])
-               tab_index++;
-             next_tab_column = tab_list[tab_index];
-             if (tab_index < first_free_tab - 1)
-               tab_index++;
-             if (column >= next_tab_column)
-               {
-                 convert = 0;  /* Ran out of tab stops. */
-                 goto flush_pend;
-               }
-           }
-         else
-           {
-             next_tab_column = column + tab_size - column % tab_size;
-           }
-         pending += next_tab_column - column;
-         column = next_tab_column;
-       }
-      else
+      /* If true, perform translations.  */
+      bool convert = true;
+
+
+      /* The following variables have valid values only when CONVERT
+        is true:  */
+
+      /* Column of next input character.  */
+      uintmax_t column = 0;
+
+      /* Column the next input tab stop is on.  */
+      uintmax_t next_tab_column = 0;
+
+      /* Index in TAB_LIST of next tab stop to examine.  */
+      size_t tab_index = 0;
+
+      /* If true, the first pending blank came just before a tab stop.  */
+      bool one_blank_before_tab_stop = false;
+
+      /* If true, the previous input character was a blank.  This is
+        initially true, since initial strings of blanks are treated
+        as if the line was preceded by a blank.  */
+      bool prev_blank = true;
+
+      /* Number of pending columns of blanks.  */
+      size_t pending = 0;
+
+
+      /* Convert a line of text.  */
+
+      do
        {
-       flush_pend:
-         /* Flush pending spaces.  Print as many tabs as possible,
-            then print the rest as spaces. */
-         if (pending == 1)
-           {
-             putchar (' ');
-             pending = 0;
-           }
-         column -= pending;
-         while (pending > 0)
+         while ((c = getc (fp)) < 0 && (fp = next_file (fp)))
+           continue;
+
+         if (convert)
            {
-             if (tab_size == 0)
-               {
-                 /* Do not let print_tab_index == first_free_tab;
-                    stop when it is 1 less. */
-                 while (print_tab_index < first_free_tab - 1
-                        && column >= tab_list[print_tab_index])
-                   print_tab_index++;
-                 next_tab_column = tab_list[print_tab_index];
-                 if (print_tab_index < first_free_tab - 1)
-                   print_tab_index++;
-               }
-             else
+             bool blank = ISBLANK (c);
+
+             if (blank)
                {
-                 next_tab_column = column + tab_size - column % tab_size;
+                 if (next_tab_column <= column)
+                   {
+                     if (tab_size)
+                       next_tab_column =
+                         column + (tab_size - column % tab_size);
+                     else
+                       for (;;)
+                         if (tab_index == first_free_tab)
+                           {
+                             convert = false;
+                             break;
+                           }
+                         else
+                           {
+                             uintmax_t tab = tab_list[tab_index++];
+                             if (column < tab)
+                               {
+                                 next_tab_column = tab;
+                                 break;
+                               }
+                           }
+                   }
+
+                 if (convert)
+                   {
+                     if (next_tab_column < column)
+                       error (EXIT_FAILURE, 0, _("input line is too long"));
+
+                     if (c == '\t')
+                       {
+                         column = next_tab_column;
+
+                         /* Discard pending blanks, unless it was a single
+                            blank just before the previous tab stop.  */
+                         if (! (pending == 1 && one_blank_before_tab_stop))
+                           {
+                             pending = 0;
+                             one_blank_before_tab_stop = false;
+                           }
+                       }
+                     else
+                       {
+                         column++;
+
+                         if (! (prev_blank && column == next_tab_column))
+                           {
+                             /* It is not yet known whether the pending blanks
+                                will be replaced by tabs.  */
+                             if (column == next_tab_column)
+                               one_blank_before_tab_stop = true;
+                             pending_blank[pending++] = c;
+                             prev_blank = true;
+                             continue;
+                           }
+
+                         /* Replace the pending blanks by a tab or two.  */
+                         pending_blank[0] = c = '\t';
+                         pending = one_blank_before_tab_stop;
+                       }
+                   }
                }
-             if (next_tab_column - column <= pending)
+             else if (c == '\b')
                {
-                 putchar ('\t');
-                 pending -= next_tab_column - column;
-                 column = next_tab_column;
+                 /* Go back one column, and force recalculation of the
+                    next tab stop.  */
+                 column -= !!column;
+                 next_tab_column = column;
+                 tab_index -= !!tab_index;
                }
              else
                {
-                 --print_tab_index;
-                 column += pending;
-                 while (pending != 0)
-                   {
-                     putchar (' ');
-                     pending--;
-                   }
+                 column++;
+                 if (!column)
+                   error (EXIT_FAILURE, 0, _("input line is too long"));
                }
-           }
 
-         if (c == EOF)
-           {
-             fp = next_file (fp);
-             if (fp == NULL)
-               break;          /* No more files. */
-             else
+             if (pending)
                {
-                 SET_BINARY2 (fileno (fp), STDOUT_FILENO);
-                 continue;
+                 if (fwrite (pending_blank, 1, pending, stdout) != pending)
+                   error (EXIT_FAILURE, errno, _("write error"));
+                 pending = 0;
+                 one_blank_before_tab_stop = false;
                }
-           }
 
-         if (convert)
-           {
-             if (c == '\b')
-               {
-                 if (column > 0)
-                   --column;
-               }
-             else
-               {
-                 ++column;
-                 if (convert_entire_line == 0)
-                   convert = 0;
-               }
+             prev_blank = blank;
+             convert &= convert_entire_line | blank;
            }
 
-         putchar (c);
-
-         if (c == '\n')
+         if (c < 0)
            {
-             tab_index = print_tab_index = 0;
-             column = pending = 0;
-             convert = 1;
+             free (pending_blank);
+             return;
            }
-       }
-    }
-}
 
-void
-usage (int status)
-{
-  if (status != 0)
-    fprintf (stderr, _("Try `%s --help' for more information.\n"),
-            program_name);
-  else
-    {
-      printf (_("\
-Usage: %s [OPTION]... [FILE]...\n\
-"),
-             program_name);
-      fputs (_("\
-Convert spaces in each FILE to tabs, writing to standard output.\n\
-With no FILE, or when FILE is -, read standard input.\n\
-\n\
-"), stdout);
-      fputs (_("\
-Mandatory arguments to long options are mandatory for short options too.\n\
-"), stdout);
-      fputs (_("\
-  -a, --all        convert all whitespace, instead of just initial whitespace\n\
-      --first-only convert only leading sequences of whitespace (overrides -a)\n\
-  -t, --tabs=N     have tabs N characters apart instead of 8 (enables -a)\n\
-  -t, --tabs=LIST  use comma separated LIST of tab positions (enables -a)\n\
-"), stdout);
-      fputs (HELP_OPTION_DESCRIPTION, stdout);
-      fputs (VERSION_OPTION_DESCRIPTION, stdout);
-      printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+         if (putchar (c) < 0)
+           error (EXIT_FAILURE, errno, _("write error"));
+       }
+      while (c != '\n');
     }
-  exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
 }
 
 int
 main (int argc, char **argv)
 {
-  int tabval = -1;             /* Value of tabstop being read, or -1. */
-  int c;                       /* Option character. */
-
-  /* If nonzero, cancel the effect of any -a (explicit or implicit in -t),
-     so that only leading white space will be considered.  */
-  int convert_first_only = 0;
+  bool have_tabval = false;
+  uintmax_t tabval IF_LINT (= 0);
+  int c;
 
-  bool obsolete_tablist = false;
+  /* If true, cancel the effect of any -a (explicit or implicit in -t),
+     so that only leading blanks will be considered.  */
+  bool convert_first_only = false;
 
   initialize_main (&argc, &argv);
   program_name = argv[0];
@@ -415,9 +472,9 @@ main (int argc, char **argv)
 
   atexit (close_stdout);
 
-  have_read_stdin = 0;
-  exit_status = 0;
-  convert_entire_line = 0;
+  have_read_stdin = false;
+  exit_status = EXIT_SUCCESS;
+  convert_entire_line = false;
   tab_list = NULL;
   first_free_tab = 0;
 
@@ -426,67 +483,58 @@ main (int argc, char **argv)
     {
       switch (c)
        {
-       case 0:
-         break;
-
        case '?':
          usage (EXIT_FAILURE);
        case 'a':
-         convert_entire_line = 1;
+         convert_entire_line = true;
          break;
        case 't':
-         convert_entire_line = 1;
-         parse_tabstops (optarg);
+         convert_entire_line = true;
+         parse_tab_stops (optarg);
          break;
        case CONVERT_FIRST_ONLY_OPTION:
-         convert_first_only = 1;
+         convert_first_only = true;
          break;
        case ',':
-         add_tabstop (tabval);
-         tabval = -1;
-         obsolete_tablist = true;
+         if (have_tabval)
+           add_tab_stop (tabval);
+         have_tabval = false;
          break;
        case_GETOPT_HELP_CHAR;
-       case_GETOPT_VERSION_CHAR (PROGRAM_NAME, WRITTEN_BY);
+       case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
        default:
-         if (tabval == -1)
-           tabval = 0;
-         tabval = tabval * 10 + c - '0';
-         obsolete_tablist = true;
+         if (!have_tabval)
+           {
+             tabval = 0;
+             have_tabval = true;
+           }
+         if (!DECIMAL_DIGIT_ACCUMULATE (tabval, c - '0', uintmax_t))
+           error (EXIT_FAILURE, 0, _("tab stop value is too large"));
          break;
        }
     }
 
-  if (obsolete_tablist && 200112 <= posix2_version ())
-    {
-      error (0, 0,
-            _("`-LIST' option is obsolete; use `--first-only -t LIST'"));
-      usage (EXIT_FAILURE);
-    }
-
   if (convert_first_only)
-    convert_entire_line = 0;
+    convert_entire_line = false;
 
-  add_tabstop (tabval);
+  if (have_tabval)
+    add_tab_stop (tabval);
 
-  validate_tabstops (tab_list, first_free_tab);
+  validate_tab_stops (tab_list, first_free_tab);
 
   if (first_free_tab == 0)
-    tab_size = 8;
+    tab_size = max_column_width = 8;
   else if (first_free_tab == 1)
     tab_size = tab_list[0];
   else
-    {
-      /* Append a sentinel to the list of tab stop indices.  */
-      add_tabstop (TAB_STOP_SENTINEL);
-      tab_size = 0;
-    }
+    tab_size = 0;
 
   file_list = (optind < argc ? &argv[optind] : stdin_argv);
 
   unexpand ();
 
-  if (have_read_stdin && fclose (stdin) == EOF)
+  if (have_read_stdin && fclose (stdin) != 0)
     error (EXIT_FAILURE, errno, "-");
-  exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+
+  exit (exit_status);
 }