Clean up output subsystem.
[pspp-builds.git] / src / libpspp / str.c
index 530d1cecded30897c41c1d1e1f0787a50e0aa99c..c72f19969ec8f1090f958e39c36a276f6b5b1f8f 100644 (file)
 #include <stdlib.h>
 #include "alloc.h"
 #include "message.h"
-\f
-/* sprintf() wrapper functions for convenience. */
-
-#if !__GNUC__
-char *
-spprintf (char *buf, const char *format,...)
-{
-#if HAVE_GOOD_SPRINTF
-  int count;
-#endif
-  va_list args;
-
-  va_start (args, format);
-#if HAVE_GOOD_SPRINTF
-  count =
-#endif
-    vsprintf (buf, format, args);
-  va_end (args);
-
-#if HAVE_GOOD_SPRINTF
-  return &buf[count];
-#else
-  return strchr (buf, 0);
-#endif
-}
-#endif /* !__GNUC__ */
-
-#if !__GNUC__ && !HAVE_GOOD_SPRINTF
-int
-nsprintf (char *buf, const char *format,...)
-{
-  va_list args;
-
-  va_start (args, format);
-  vsprintf (buf, format, args);
-  va_end (args);
-
-  return strlen (buf);
-}
-
-int
-nvsprintf (char *buf, const char *format, va_list args)
-{
-  vsprintf (buf, format, args);
-  return strlen (buf);
-}
-#endif /* Not GNU C and not good sprintf(). */
+#include "minmax.h"
+#include "size_max.h"
 \f
 /* Reverses the order of NBYTES bytes at address P, thus converting
    between little- and big-endian byte orders.  */
@@ -284,7 +239,7 @@ void
 ds_create (struct string *st, const char *s)
 {
   st->length = strlen (s);
-  st->capacity = 8 + st->length * 2;
+  st->capacity = MAX (8, st->length * 2);
   st->string = xmalloc (st->capacity + 1);
   strcpy (st->string, s);
 }
@@ -294,43 +249,23 @@ void
 ds_init (struct string *st, size_t capacity)
 {
   st->length = 0;
-  if (capacity > 8)
-    st->capacity = capacity;
-  else
-    st->capacity = 8;
+  st->capacity = MAX (8, capacity);
   st->string = xmalloc (st->capacity + 1);
 }
 
-/* Replaces the contents of ST with STRING.  STRING may overlap with
-   ST. */
+/* Frees ST. */
 void
-ds_replace (struct string *st, const char *string)
+ds_destroy (struct string *st)
 {
-  size_t new_length = strlen (string);
-  if (new_length > st->capacity) 
+  if (st != NULL) 
     {
-      /* The new length is longer than the allocated length, so
-         there can be no overlap. */
+      free (st->string);
+      st->string = NULL;
       st->length = 0;
-      ds_concat (st, string, new_length);
-    }
-  else
-    {
-      /* Overlap is possible, but the new string will fit in the
-         allocated space, so we can just copy data. */
-      st->length = new_length;
-      memmove (st->string, string, st->length);
+      st->capacity = 0; 
     }
 }
 
-/* Frees ST. */
-void
-ds_destroy (struct string *st)
-{
-  free (st->string);
-  st->string = NULL;
-}
-
 /* Swaps the contents of strings A and B. */
 void
 ds_swap (struct string *a, struct string *b) 
@@ -340,6 +275,56 @@ ds_swap (struct string *a, struct string *b)
   *b = tmp;
 }
 
+/* Initializes DST with the CNT characters from SRC starting at
+   position IDX. */
+void
+ds_init_substring (struct string *dst,
+                   const struct string *src, size_t idx, size_t cnt)
+{
+  assert (dst != src);
+  ds_init (dst, cnt);
+  ds_assign_substring (dst, src, idx, cnt);
+}
+
+/* Copies SRC into DST.
+   DST and SRC may be the same string. */
+void
+ds_assign_string (struct string *dst, const struct string *src) 
+{
+  ds_assign_buffer (dst, ds_data (src), ds_length (src));
+}
+
+/* Replaces DST by CNT characters from SRC starting at position
+   IDX.
+   DST and SRC may be the same string. */
+void
+ds_assign_substring (struct string *dst,
+                     const struct string *src, size_t idx, size_t cnt) 
+{
+  if (idx < src->length)
+    ds_assign_buffer (dst, src->string + idx, MIN (cnt, src->length - idx));
+  else 
+    ds_clear (dst);
+}
+
+/* Replaces DST by the LENGTH characters in SRC.
+   SRC may be a substring within DST. */
+void
+ds_assign_buffer (struct string *dst, const char *src, size_t length)
+{
+  dst->length = length;
+  ds_extend (dst, length);
+  memmove (dst->string, src, length);
+}
+
+/* Replaces DST by null-terminated string SRC.  SRC may overlap
+   with DST. */
+void
+ds_assign_c_str (struct string *dst, const char *src)
+{
+  ds_assign_buffer (dst, src, strlen (src));
+}
+
 /* Truncates ST to zero length. */
 void
 ds_clear (struct string *st)
@@ -347,20 +332,48 @@ ds_clear (struct string *st)
   st->length = 0;
 }
 
+/* Ensures that ST can hold at least MIN_CAPACITY characters plus a null
+   terminator. */
+void
+ds_extend (struct string *st, size_t min_capacity)
+{
+  if (min_capacity > st->capacity)
+    {
+      st->capacity *= 2;
+      if (st->capacity < min_capacity)
+       st->capacity = 2 * min_capacity;
+
+      st->string = xrealloc (st->string, st->capacity + 1);
+    }
+}
+
+/* Shrink ST to the minimum capacity need to contain its content. */
+void
+ds_shrink (struct string *st)
+{
+  if (st->capacity != st->length)
+    {
+      st->capacity = st->length;
+      st->string = xrealloc (st->string, st->capacity + 1);
+    }
+}
+
+/* Truncates ST to at most LENGTH characters long. */
+void
+ds_truncate (struct string *st, size_t length)
+{
+  if (st->length > length)
+    st->length = length;
+}
+
 /* Pad ST on the right with copies of PAD until ST is at least
    LENGTH characters in size.  If ST is initially LENGTH
    characters or longer, this is a no-op. */
 void
 ds_rpad (struct string *st, size_t length, char pad) 
 {
-  assert (st != NULL);
-  if (st->length < length) 
-    {
-      if (st->capacity < length)
-        ds_extend (st, length);
-      memset (&st->string[st->length], pad, length - st->length);
-      st->length = length;
-    }
+  if (length > st->length)
+    ds_putc_multiple (st, pad, length - st->length);
 }
 
 /* Removes trailing spaces from ST.
@@ -377,6 +390,27 @@ ds_rtrim_spaces (struct string *st)
   return cnt;
 }
 
+/* Removes leading spaces from ST.
+   Returns number of spaces removed. */
+int
+ds_ltrim_spaces (struct string *st) 
+{
+  size_t cnt = 0;
+  while (isspace (ds_at (st, cnt)))
+    cnt++;
+  if (cnt > 0)
+    ds_assign_substring (st, st, cnt, SIZE_MAX);
+  return cnt;
+}
+
+/* Trims leading and trailing spaces from ST. */
+void
+ds_trim_spaces (struct string *st) 
+{
+  ds_rtrim_spaces (st);
+  ds_ltrim_spaces (st);
+}
+
 /* If the last character in ST is C, removes it and returns true.
    Otherwise, returns false without modifying ST. */
 bool
@@ -392,39 +426,50 @@ ds_chomp (struct string *st, char c_)
     return false;
 }
 
-/* Ensures that ST can hold at least MIN_CAPACITY characters plus a null
-   terminator. */
-void
-ds_extend (struct string *st, size_t min_capacity)
-{
-  if (min_capacity > st->capacity)
-    {
-      st->capacity *= 2;
-      if (st->capacity < min_capacity)
-       st->capacity = min_capacity * 2;
-      
-      st->string = xrealloc (st->string, st->capacity + 1);
-    }
-}
+/* Divides ST into tokens separated by any of the DELIMITERS.
+   Each call replaces TOKEN by the next token in ST, or by an
+   empty string if no tokens remain.  Returns true if a token was
+   obtained, false otherwise.
 
-/* Shrink ST to the minimum capacity need to contain its content. */
-void
-ds_shrink (struct string *st)
+   Before the first call, initialize *SAVE_IDX to 0.  Do not
+   modify *SAVE_IDX between calls.
+
+   ST divides into exactly one more tokens than it contains
+   delimiters.  That is, a delimiter at the start or end of ST or
+   a pair of adjacent delimiters yields an empty token, and the
+   empty string contains a single token. */
+bool
+ds_separate (const struct string *st, struct string *token,
+             const char *delimiters, size_t *save_idx)
 {
-  if (st->capacity != st->length)
+  if (*save_idx <= ds_length (st))
     {
-      st->capacity = st->length;
-      st->string = xrealloc (st->string, st->capacity + 1);
+      size_t length = ds_cspan (st, *save_idx, delimiters);
+      ds_assign_substring (token, st, *save_idx, length);
+      *save_idx += length + 1;
+      return true;
     }
+  else 
+    return false;
 }
 
-/* Truncates ST to at most LENGTH characters long. */
-void
-ds_truncate (struct string *st, size_t length)
+/* Divides ST into tokens separated by any of the DELIMITERS,
+   merging adjacent delimiters so that the empty string is never
+   produced as a token.  Each call replaces TOKEN by the next
+   token in ST, or by an empty string if no tokens remain.
+   Returns true if a token was obtained, false otherwise.
+
+   Before the first call, initialize *SAVE_IDX to 0.  Do not
+   modify *SAVE_IDX between calls. */
+bool
+ds_tokenize (const struct string *st, struct string *token,
+             const char *delimiters, size_t *save_idx)
 {
-  if (length >= st->length)
-    return;
-  st->length = length;
+  size_t start = *save_idx + ds_span (st, *save_idx, delimiters);
+  size_t length = ds_cspan (st, start, delimiters);
+  ds_assign_substring (token, st, start, length);
+  *save_idx = start + length;
+  return length > 0;
 }
 
 /* Returns true if ST is empty, false otherwise. */
@@ -441,6 +486,33 @@ ds_length (const struct string *st)
   return st->length;
 }
 
+/* Returns the value of ST as a null-terminated string. */
+char *
+ds_c_str (const struct string *st_)
+{
+  struct string *st = (struct string *) st_;
+  if (st->string == NULL) 
+    ds_extend (st, 1);
+  st->string[st->length] = '\0';
+  return st->string;
+}
+
+/* Returns the string data inside ST. */
+char *
+ds_data (const struct string *st)
+{
+  return st->string;
+}
+
+/* Returns a pointer to the null terminator ST.
+   This might not be an actual null character unless ds_c_str() has
+   been called since the last modification to ST. */
+char *
+ds_end (const struct string *st)
+{
+  return st->string + st->length;
+}
+
 /* Returns the allocation size of ST. */
 size_t
 ds_capacity (const struct string *st)
@@ -448,12 +520,21 @@ ds_capacity (const struct string *st)
   return st->capacity;
 }
 
+/* Returns the character in position IDX in ST, as a value in the
+   range of unsigned char.  Returns EOF if IDX is out of the
+   range of indexes for ST. */
+int
+ds_at (const struct string *st, size_t idx) 
+{
+  return idx < st->length ? (unsigned char) st->string[idx] : EOF;
+}
+
 /* Returns the first character in ST as a value in the range of
    unsigned char.  Returns EOF if ST is the empty string. */
 int
 ds_first (const struct string *st) 
 {
-  return st->length > 0 ? (unsigned char) st->string[0] : EOF;
+  return ds_at (st, 0);
 }
 
 /* Returns the last character in ST as a value in the range of
@@ -464,28 +545,117 @@ ds_last (const struct string *st)
   return st->length > 0 ? (unsigned char) st->string[st->length - 1] : EOF;
 }
 
-/* Returns the value of ST as a null-terminated string. */
-char *
-ds_c_str (const struct string *st)
+/* Returns the number of consecutive characters starting at OFS
+   in ST that are in SKIP_SET.  (The null terminator is not
+   considered to be part of SKIP_SET.) */
+size_t
+ds_span (const struct string *st, size_t ofs, const char skip_set[])
 {
-  ((char *) st->string)[st->length] = '\0';
-  return st->string;
+  size_t i;
+  for (i = ofs; i < st->length; i++) 
+    {
+      int c = st->string[i];
+      if (strchr (skip_set, c) == NULL || c == '\0')
+        break; 
+    }
+  return i - ofs;
 }
 
-/* Returns the string data inside ST. */
-char *
-ds_data (const struct string *st)
+/* Returns the number of consecutive characters starting at OFS
+   in ST that are not in STOP_SET.  (The null terminator is not
+   considered to be part of STOP_SET.) */
+size_t
+ds_cspan (const struct string *st, size_t ofs, const char stop_set[])
 {
-  return st->string;
+  size_t i;
+  for (i = ofs; i < st->length; i++) 
+    {
+      int c = st->string[i];
+      if (strchr (stop_set, c) != NULL)
+        break; 
+    }
+  return i - ofs;
 }
 
-/* Returns a pointer to the null terminator ST.
-   This might not be an actual null character unless ds_c_str() has
-   been called since the last modification to ST. */
-char *
-ds_end (const struct string *st)
+/* Appends to ST a newline-terminated line read from STREAM.
+   Newline is the last character of ST on return, unless an I/O error
+   or end of file is encountered after reading some characters.
+   Returns true if a line is successfully read, false if no characters at
+   all were read before an I/O error or end of file was
+   encountered. */
+bool
+ds_gets (struct string *st, FILE *stream)
 {
-  return st->string + st->length;
+  int c;
+
+  c = getc (stream);
+  if (c == EOF)
+    return false;
+
+  for (;;)
+    {
+      ds_putc (st, c);
+      if (c == '\n')
+       return true;
+
+      c = getc (stream);
+      if (c == EOF)
+       return true;
+    }
+}
+
+/* Removes a comment introduced by `#' from ST,
+   ignoring occurrences inside quoted strings. */
+static void
+remove_comment (struct string *st)
+{
+  char *cp;
+  int quote = 0;
+      
+  for (cp = ds_c_str (st); cp < ds_end (st); cp++)
+    if (quote)
+      {
+        if (*cp == quote)
+          quote = 0;
+        else if (*cp == '\\')
+          cp++;
+      }
+    else if (*cp == '\'' || *cp == '"')
+      quote = *cp;
+    else if (*cp == '#')
+      {
+        ds_truncate (st, cp - ds_c_str (st));
+        break;
+      }
+}
+
+/* Reads a line from STREAM into ST, then preprocesses as follows:
+
+   - Splices lines terminated with `\'.
+
+   - Deletes comments introduced by `#' outside of single or double
+     quotes.
+
+   - Deletes trailing white space.  
+
+   Returns true if a line was successfully read, false on
+   failure.  If LINE_NUMBER is non-null, then *LINE_NUMBER is
+   incremented by the number of lines read. */
+bool
+ds_get_config_line (FILE *stream, struct string *st, int *line_number)
+{
+  ds_clear (st);
+  do
+    {
+      if (!ds_gets (st, stream))
+        return false;
+      (*line_number)++;
+      ds_rtrim_spaces (st);
+    }
+  while (ds_chomp (st, '\\'));
+  remove_comment (st);
+  return true;
 }
 
 /* Concatenates S onto ST. */
@@ -511,9 +681,6 @@ ds_concat (struct string *st, const char *buf, size_t len)
   st->length += len;
 }
 
-void ds_vprintf (struct string *st, const char *format, va_list args);
-
-
 /* Formats FORMAT as a printf string and appends the result to ST. */
 void
 ds_printf (struct string *st, const char *format, ...)
@@ -521,48 +688,48 @@ ds_printf (struct string *st, const char *format, ...)
   va_list args;
 
   va_start (args, format);
-  ds_vprintf(st,format,args);
+  ds_vprintf(st, format, args);
   va_end (args);
-
 }
 
 /* Formats FORMAT as a printf string and appends the result to ST. */
 void
-ds_vprintf (struct string *st, const char *format, va_list args)
+ds_vprintf (struct string *st, const char *format, va_list args_)
 {
-  /* Fscking glibc silently changed behavior between 2.0 and 2.1.
-     Fsck fsck fsck.  Before, it returned -1 on buffer overflow.  Now,
-     it returns the number of characters (not bytes) that would have
-     been written. */
-
   int avail, needed;
-  va_list a1;
+  va_list args;
 
-  va_copy(a1, args);
+#ifndef va_copy
+#define va_copy(DST, SRC) (DST) = (SRC)
+#endif
+
+  va_copy (args, args_);
   avail = st->capacity - st->length + 1;
   needed = vsnprintf (st->string + st->length, avail, format, args);
-
+  va_end (args);
 
   if (needed >= avail)
     {
       ds_extend (st, st->length + needed);
       
-      vsprintf (st->string + st->length, format, a1);
+      va_copy (args, args_);
+      vsprintf (st->string + st->length, format, args);
+      va_end (args);
     }
-  else
-    while (needed == -1)
-      {
-       va_list a2;
-       va_copy(a2, a1);
-
-       ds_extend (st, (st->capacity + 1) * 2);
-       avail = st->capacity - st->length + 1;
-
-       needed = vsnprintf (st->string + st->length, avail, format, a2);
-       va_end(a2);
+  else 
+    {
+      /* Some old libc's returned -1 when the destination string
+         was too short. */
+      while (needed == -1)
+        {
+          ds_extend (st, (st->capacity + 1) * 2);
+          avail = st->capacity - st->length + 1;
 
-      }
-  va_end(a1);
+          va_copy (args, args_);
+          needed = vsnprintf (st->string + st->length, avail, format, args);
+          va_end (args);
+        } 
+    }
 
   st->length += needed;
 }
@@ -571,110 +738,20 @@ ds_vprintf (struct string *st, const char *format, va_list args)
 void
 ds_putc (struct string *st, int ch)
 {
-  if (st->length == st->capacity)
+  if (st->length >= st->capacity)
     ds_extend (st, st->length + 1);
   st->string[st->length++] = ch;
 }
 
-/* Appends to ST a newline-terminated line read from STREAM.
-   Newline is the last character of ST on return, unless an I/O error
-   or end of file is encountered after reading some characters.
-   Returns 1 if a line is successfully read, or 0 if no characters at
-   all were read before an I/O error or end of file was
-   encountered. */
-int
-ds_gets (struct string *st, FILE *stream)
+/* Appends CNT copies of character CH to ST. */
+void
+ds_putc_multiple (struct string *st, int ch, size_t cnt) 
 {
-  int c;
-
-  c = getc (stream);
-  if (c == EOF)
-    return 0;
-
-  for (;;)
-    {
-      ds_putc (st, c);
-      if (c == '\n')
-       return 1;
-
-      c = getc (stream);
-      if (c == EOF)
-       return 1;
-    }
+  ds_extend (st, st->length + cnt);
+  memset (&st->string[st->length], ch, cnt);
+  st->length += cnt;
 }
 
-/* Reads a line from STREAM into ST, then preprocesses as follows:
-
-   - Splices lines terminated with `\'.
-
-   - Deletes comments introduced by `#' outside of single or double
-     quotes.
-
-   - Trailing whitespace will be deleted.  
-
-   Increments cust_ln as appropriate.
-
-   Returns nonzero only if a line was successfully read. */
-int
-ds_get_config_line (FILE *stream, struct string *st, struct file_locator *where)
-{
-  /* Read the first line. */
-  ds_clear (st);
-  where->line_number++;
-  if (!ds_gets (st, stream))
-    return 0;
-
-  /* Read additional lines, if any. */
-  for (;;)
-    {
-      /* Remove trailing whitespace. */
-      {
-       char *s = ds_c_str (st);
-       size_t len = ds_length (st);
-      
-       while (len > 0 && isspace ((unsigned char) s[len - 1]))
-         len--;
-       ds_truncate (st, len);
-      }
-
-      /* Check for trailing \.  Remove if found, bail otherwise. */
-      if (ds_length (st) == 0 || ds_c_str (st)[ds_length (st) - 1] != '\\')
-       break;
-      ds_truncate (st, ds_length (st) - 1);
-
-      /* Append another line and go around again. */
-      {
-       int success = ds_gets (st, stream);
-       where->line_number++;
-       if (!success)
-         return 1;
-      }
-    }
-
-  /* Find a comment and remove. */
-  {
-    char *cp;
-    int quote = 0;
-      
-    for (cp = ds_c_str (st); *cp; cp++)
-      if (quote)
-       {
-         if (*cp == quote)
-           quote = 0;
-         else if (*cp == '\\')
-           cp++;
-       }
-      else if (*cp == '\'' || *cp == '"')
-       quote = *cp;
-      else if (*cp == '#')
-       {
-         ds_truncate (st, cp - ds_c_str (st));
-         break;
-       }
-  }
-
-  return 1;
-}
 \f
 /* Lengthed strings. */