Adopt use of gnulib for portability.
[pspp-builds.git] / src / flip.c
index 4ff7045ee2ead1e0d3c70156ea3fe27efea65155..7b33d3dbbbb67ed207e87359a614af0e026f69b3 100644 (file)
 
    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. */
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
 
-/* FIXME: does this work with long string variables? */
-
-#include <config.h>
-#include <assert.h>
+#include "config.h"
+#include "error.h"
 #include <ctype.h>
 #include <errno.h>
 #include <float.h>
 #include <limits.h>
 #include <stdlib.h>
+#include "algorithm.h"
 #include "alloc.h"
+#include "case.h"
 #include "command.h"
+#include "dictionary.h"
 #include "error.h"
 #include "lexer.h"
 #include "misc.h"
 #include "settings.h"
 #include "str.h"
+#include "val.h"
 #include "var.h"
 #include "vfm.h"
 
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
 /* List of variable names. */
 struct varname
   {
     struct varname *next;
-    char name[9];
+    char name[SHORT_NAME_LEN + 1];
   };
 
 /* Represents a FLIP input program. */
 struct flip_pgm 
   {
     struct variable **var;      /* Variables to transpose. */
-    int var_cnt;                /* Number of variables. */
-    struct variable *newnames;  /* Variable containing new variable names. */
-    struct varname *new_names_head, *new_names_tail;
-                                /* New variable names. */
-    int case_count;             /* Number of cases. */
+    int *idx_to_fv;             /* var[]->index to compacted sink case fv. */
+    int var_cnt;                /* Number of elements in `var'. */
+    int case_cnt;               /* Pre-flip case count. */
+    size_t case_size;           /* Post-flip bytes per case. */
+
+    struct variable *new_names; /* Variable containing new variable names. */
+    struct varname *new_names_head; /* First new variable. */
+    struct varname *new_names_tail; /* Last new variable. */
 
+    FILE *file;                 /* Temporary file containing data. */
   };
 
-static void destroy_flip_pgm (struct flip_pgm *flip);
+static void destroy_flip_pgm (struct flip_pgm *);
 static struct case_sink *flip_sink_create (struct flip_pgm *);
+static struct case_source *flip_source_create (struct flip_pgm *);
+static void flip_file (struct flip_pgm *);
+static int build_dictionary (struct flip_pgm *);
+
 static const struct case_source_class flip_source_class;
-static int build_dictionary (struct flip_pgm *flip);
+static const struct case_sink_class flip_sink_class;
 
 /* Parses and executes FLIP. */
 int
@@ -66,14 +83,23 @@ cmd_flip (void)
 {
   struct flip_pgm *flip;
 
+  if (temporary != 0)
+    {
+      msg (SM, _("FLIP ignores TEMPORARY.  "
+                 "Temporary transformations will be made permanent."));
+      cancel_temporary (); 
+    }
+
   flip = xmalloc (sizeof *flip);
   flip->var = NULL;
+  flip->idx_to_fv = dict_get_compacted_idx_to_fv (default_dict);
   flip->var_cnt = 0;
-  flip->newnames = NULL;
-  flip->new_names_head = flip->new_names_tail = NULL;
-  flip->case_count = 0;
+  flip->case_cnt = 0;
+  flip->new_names = NULL;
+  flip->new_names_head = NULL;
+  flip->new_names_tail = NULL;
+  flip->file = NULL;
 
-  lex_match_id ("FLIP");
   lex_match ('/');
   if (lex_match_id ("VARIABLES"))
     {
@@ -89,38 +115,47 @@ cmd_flip (void)
   if (lex_match_id ("NEWNAMES"))
     {
       lex_match ('=');
-      flip->newnames = parse_variable ();
-      if (!flip->newnames)
+      flip->new_names = parse_variable ();
+      if (!flip->new_names)
         goto error;
     }
   else
-    flip->newnames = dict_lookup_var (default_dict, "CASE_LBL");
+    flip->new_names = dict_lookup_var (default_dict, "CASE_LBL");
 
-  if (flip->newnames)
+  if (flip->new_names)
     {
       int i;
       
       for (i = 0; i < flip->var_cnt; i++)
-       if (flip->var[i] == flip->newnames)
+       if (flip->var[i] == flip->new_names)
          {
-           memmove (&flip->var[i], &flip->var[i + 1], sizeof *flip->var * (flip->var_cnt - i - 1));
+            remove_element (flip->var, flip->var_cnt, sizeof *flip->var, i);
            flip->var_cnt--;
            break;
          }
     }
 
-  flip->case_count = 0;
+  /* Read the active file into a flip_sink. */
+  flip->case_cnt = 0;
   temp_trns = temporary = 0;
   vfm_sink = flip_sink_create (flip);
   flip->new_names_tail = NULL;
-  procedure (NULL, NULL, NULL, NULL);
+  procedure (NULL, NULL);
+
+  /* Flip the data we read. */
+  flip_file (flip);
 
+  /* Flip the dictionary. */
   dict_clear (default_dict);
   if (!build_dictionary (flip))
     {
       discard_variables ();
       goto error;
     }
+  flip->case_size = dict_get_case_size (default_dict);
+
+  /* Set up flipped data for reading. */
+  vfm_source = flip_source_create (flip);
 
   return lex_end_of_command ();
 
@@ -129,17 +164,21 @@ cmd_flip (void)
   return CMD_FAILURE;
 }
 
+/* Destroys FLIP. */
 static void
 destroy_flip_pgm (struct flip_pgm *flip) 
 {
   struct varname *iter, *next;
   
   free (flip->var);
+  free (flip->idx_to_fv);
   for (iter = flip->new_names_head; iter != NULL; iter = next) 
     {
       next = iter->next;
       free (iter);
     }
+  if (flip->file != NULL)
+    fclose (flip->file);
   free (flip);
 }
 
@@ -148,33 +187,35 @@ destroy_flip_pgm (struct flip_pgm *flip)
 static int
 make_new_var (char name[])
 {
+  char *cp;
+
+  /* Trim trailing spaces. */
+  cp = strchr (name, '\0');
+  while (cp > name && isspace ((unsigned char) cp[-1]))
+    *--cp = '\0';
+
   /* Fix invalid characters. */
-  {
-    char *cp;
-  
-    for (cp = name; *cp && !isspace (*cp); cp++)
+  for (cp = name; *cp && cp < name + SHORT_NAME_LEN; cp++)
+    if (cp == name) 
       {
-       *cp = toupper ((unsigned char) *cp);
-       if (!isalpha (*cp) && *cp != '@' && *cp != '#'
-           && (cp == name || (*cp != '.' && *cp != '$' && *cp != '_'
-                               && !isdigit (*cp))))
-         {
-           if (cp == name)
-             *cp = 'V';        /* _ not valid in first position. */
-           else
-             *cp = '_';
-         }
+        if (!CHAR_IS_ID1 (*cp) || *cp == '$')
+          *cp = 'V';
       }
-    *cp = 0;
-  }
+    else
+      {
+        if (!CHAR_IS_IDN (*cp))
+          *cp = '_'; 
+      }
+  *cp = '\0';
+  str_uppercase (name);
   
   if (dict_create_var (default_dict, name, 0))
     return 1;
 
   /* Add numeric extensions until acceptable. */
   {
-    int len = (int) strlen (name);
-    char n[9];
+    const int len = (int) strlen (name);
+    char n[SHORT_NAME_LEN + 1];
     int i;
 
     for (i = 1; i < 10000000; i++)
@@ -202,16 +243,16 @@ build_dictionary (struct flip_pgm *flip)
     {
       int i;
       
-      if (flip->case_count > 99999)
+      if (flip->case_cnt > 99999)
        {
          msg (SE, _("Cannot create more than 99999 variable names."));
          return 0;
        }
       
-      for (i = 0; i < flip->case_count; i++)
+      for (i = 0; i < flip->case_cnt; i++)
        {
           struct variable *v;
-         char s[9];
+         char s[SHORT_NAME_LEN + 1];
 
          sprintf (s, "VAR%03d", i);
          v = dict_create_var_assert (default_dict, s, 0);
@@ -233,77 +274,53 @@ build_dictionary (struct flip_pgm *flip)
 struct flip_sink_info 
   {
     struct flip_pgm *flip;              /* FLIP program. */
-    int internal;                      /* Internal or external flip. */
-    char *old_names;                    /* Old variable names. */
-    unsigned long case_cnt;             /* Number of cases. */
-    FILE *file;                         /* Temporary file. */
-  };
-
-/* Source: Cases after transposition. */
-struct flip_source_info 
-  {
-    struct flip_pgm *flip;              /* FLIP program. */
-    char *old_names;                   /* Old variable names. */
-    unsigned long case_cnt;            /* Number of cases. */
-    FILE *file;                         /* Temporary file. */
+    union value *output_buf;            /* Case output buffer. */
   };
 
-static const struct case_sink_class flip_sink_class;
-
-static FILE *flip_file (struct flip_sink_info *info);
-
-/* Creates a flip sink based on FLIP, of which it takes
-   ownership. */
+/* Creates a flip sink based on FLIP. */
 static struct case_sink *
 flip_sink_create (struct flip_pgm *flip) 
 {
   struct flip_sink_info *info = xmalloc (sizeof *info);
+  int i;
 
   info->flip = flip;
-  info->case_cnt = 0;
-  
-  {
-    size_t n = flip->var_cnt;
-    char *p;
-    int i;
-    
-    for (i = 0; i < flip->var_cnt; i++)
-      n += strlen (flip->var[i]->name);
-    p = info->old_names = xmalloc (n);
-    for (i = 0; i < flip->var_cnt; i++)
-      p = stpcpy (p, flip->var[i]->name) + 1;
-  }
+  info->output_buf = xmalloc (sizeof *info->output_buf * flip->var_cnt);
 
-  return create_case_sink (&flip_sink_class, info);
-}
+  flip->file = tmpfile ();
+  if (!flip->file)
+    msg (FE, _("Could not create temporary file for FLIP."));
 
-/* Open the FLIP sink. */
-static void
-flip_sink_open (struct case_sink *sink) 
-{
-  struct flip_sink_info *info = sink->aux;
+  /* Write variable names as first case. */
+  for (i = 0; i < flip->var_cnt; i++) 
+    buf_copy_str_rpad (info->output_buf[i].s, MAX_SHORT_STRING,
+                       flip->var[i]->name);
+  if (fwrite (info->output_buf, sizeof *info->output_buf,
+              flip->var_cnt, flip->file) != (size_t) flip->var_cnt)
+    msg (FE, _("Error writing FLIP file: %s."), strerror (errno));
 
-  info->file = tmpfile ();
-  if (!info->file)
-    msg (FE, _("Could not create temporary file for FLIP."));
+  flip->case_cnt = 1;
+
+  return create_case_sink (&flip_sink_class, default_dict, info);
 }
 
 /* Writes case C to the FLIP sink. */
 static void
-flip_sink_write (struct case_sink *sink, struct ccase *c)
+flip_sink_write (struct case_sink *sink, const struct ccase *c)
 {
   struct flip_sink_info *info = sink->aux;
   struct flip_pgm *flip = info->flip;
+  int i;
   
-  info->case_cnt++;
+  flip->case_cnt++;
 
-  if (flip->newnames)
+  if (flip->new_names != NULL)
     {
       struct varname *v = xmalloc (sizeof (struct varname));
       v->next = NULL;
-      if (flip->newnames->type == NUMERIC) 
+      if (flip->new_names->type == NUMERIC) 
         {
-          double f = c->data[flip->newnames->fv].f;
+          double f = case_num (c, flip->idx_to_fv[flip->new_names->index]);
 
           if (f == SYSMIS)
             strcpy (v->name, "VSYSMIS");
@@ -315,14 +332,14 @@ flip_sink_write (struct case_sink *sink, struct ccase *c)
             {
               char name[INT_DIGITS + 2];
               sprintf (name, "V%d", (int) f);
-              strncpy (v->name, name, 8);
-              name[8] = 0; 
+              str_copy_trunc (v->name, sizeof v->name, name);
             }
         }
       else
        {
-         int width = min (flip->newnames->width, 8);
-         memcpy (v->name, c->data[flip->newnames->fv].s, width);
+         int width = min (flip->new_names->width, MAX_SHORT_STRING);
+         memcpy (v->name, case_str (c, flip->idx_to_fv[flip->new_names->index]),
+                  width);
          v->name[width] = 0;
        }
       
@@ -332,65 +349,28 @@ flip_sink_write (struct case_sink *sink, struct ccase *c)
        flip->new_names_tail->next = v;
       flip->new_names_tail = v;
     }
-  else
-    flip->case_count++;
-
 
   /* Write to external file. */
-  {
-    double *d = local_alloc (sizeof *d * flip->var_cnt);
-    int i;
-
-    for (i = 0; i < flip->var_cnt; i++)
+  for (i = 0; i < flip->var_cnt; i++)
+    {
+      double out;
+      
       if (flip->var[i]->type == NUMERIC)
-       d[i] = c->data[flip->var[i]->fv].f;
+        out = case_num (c, flip->idx_to_fv[flip->var[i]->index]);
       else
-       d[i] = SYSMIS;
+        out = SYSMIS;
+      info->output_buf[i].f = out;
+    }
          
-    if (fwrite (d, sizeof *d, flip->var_cnt, info->file) != (size_t) flip->var_cnt)
-      msg (FE, _("Error writing FLIP file: %s."),
-          strerror (errno));
-
-    local_free (d);
-  }
+  if (fwrite (info->output_buf, sizeof *info->output_buf,
+              flip->var_cnt, flip->file) != (size_t) flip->var_cnt)
+    msg (FE, _("Error writing FLIP file: %s."), strerror (errno));
 }
 
-/* Destroy sink's internal data. */
+/* Transposes the external file into a new file. */
 static void
-flip_sink_destroy (struct case_sink *sink)
-{
-  struct flip_sink_info *info = sink->aux;
-  
-  free (info->old_names);
-  destroy_flip_pgm (info->flip);
-  free (info);
-}
-
-/* Convert the FLIP sink into a source. */
-static struct case_source *
-flip_sink_make_source (struct case_sink *sink)
-{
-  struct flip_sink_info *sink_info = sink->aux;
-  struct flip_source_info *source_info;
-
-  source_info = xmalloc (sizeof *source_info);
-  source_info->flip = sink_info->flip;
-  source_info->old_names = sink_info->old_names;
-  source_info->case_cnt = sink_info->case_cnt;
-  source_info->file = flip_file (sink_info);
-  fclose (sink_info->file);
-
-  free (sink_info);
-
-  return create_case_source (&flip_source_class, source_info);
-}
-
-/* Transposes the external file into a new file and returns a
-   pointer to the transposed file. */
-static FILE *
-flip_file (struct flip_sink_info *info)
+flip_file (struct flip_pgm *flip)
 {
-  struct flip_pgm *flip = info->flip;
   size_t case_bytes;
   size_t case_capacity;
   size_t case_idx;
@@ -399,9 +379,9 @@ flip_file (struct flip_sink_info *info)
 
   /* Allocate memory for many cases. */
   case_bytes = flip->var_cnt * sizeof *input_buf;
-  case_capacity = set_max_workspace / case_bytes;
-  if (case_capacity > info->case_cnt)
-    case_capacity = info->case_cnt;
+  case_capacity = get_max_workspace() / case_bytes;
+  if (case_capacity > flip->case_cnt * 2)
+    case_capacity = flip->case_cnt * 2;
   if (case_capacity < 2)
     case_capacity = 2;
   for (;;)
@@ -424,7 +404,7 @@ flip_file (struct flip_sink_info *info)
   case_capacity /= 2;
   output_buf = input_buf + flip->var_cnt * case_capacity;
 
-  input_file = info->file;
+  input_file = flip->file;
   if (fseek (input_file, 0, SEEK_SET) != 0)
     msg (FE, _("Error rewinding FLIP file: %s."), strerror (errno));
 
@@ -432,9 +412,9 @@ flip_file (struct flip_sink_info *info)
   if (output_file == NULL)
     msg (FE, _("Error creating FLIP source file."));
   
-  for (case_idx = 0; case_idx < info->case_cnt; )
+  for (case_idx = 0; case_idx < flip->case_cnt; )
     {
-      unsigned long read_cases = min (info->case_cnt - case_idx,
+      unsigned long read_cases = min (flip->case_cnt - case_idx,
                                       case_capacity);
       int i;
 
@@ -448,9 +428,18 @@ flip_file (struct flip_sink_info *info)
          for (j = 0; j < read_cases; j++)
            output_buf[j] = input_buf[i + j * flip->var_cnt];
 
-         if (fseek (output_file,
-                     sizeof *input_buf * (case_idx + i * info->case_cnt),
-                     SEEK_SET) != 0)
+#ifndef HAVE_FSEEKO
+#define fseeko fseek
+#endif
+
+#ifndef HAVE_OFF_T
+#define off_t long int
+#endif
+
+         if (fseeko (output_file,
+                      sizeof *input_buf * (case_idx
+                                           + (off_t) i * flip->case_cnt),
+                      SEEK_SET) != 0)
            msg (FE, _("Error seeking FLIP source file: %s."),
                       strerror (errno));
 
@@ -463,63 +452,92 @@ flip_file (struct flip_sink_info *info)
       case_idx += read_cases;
     }
 
+  fclose (input_file);
+  free (input_buf);
+  
   if (fseek (output_file, 0, SEEK_SET) != 0)
     msg (FE, _("Error rewind FLIP source file: %s."), strerror (errno));
+  flip->file = output_file;
+}
 
-  free (input_buf);
-  return output_file;
+/* Destroy sink's internal data. */
+static void
+flip_sink_destroy (struct case_sink *sink)
+{
+  struct flip_sink_info *info = sink->aux;
+
+  free (info->output_buf);
+  free (info);
 }
 
 /* FLIP sink class. */
 static const struct case_sink_class flip_sink_class = 
   {
     "FLIP",
-    flip_sink_open,
+    NULL,
     flip_sink_write,
     flip_sink_destroy,
-    flip_sink_make_source,
+    NULL,
   };
 
-/* Reads the FLIP stream and passes it to WRITE_CASE(). */
+/* Creates and returns a FLIP source based on PGM,
+   which should have already been used as a sink. */
+static struct case_source *
+flip_source_create (struct flip_pgm *pgm)
+{
+  return create_case_source (&flip_source_class, pgm);
+}
+
+/* Reads the FLIP stream.  Copies each case into C and calls
+   WRITE_CASE passing WC_DATA. */
 static void
 flip_source_read (struct case_source *source,
+                  struct ccase *c,
                   write_case_func *write_case, write_case_data wc_data)
 {
-  struct flip_source_info *info = source->aux;
-  struct flip_pgm *flip = info->flip;
+  struct flip_pgm *flip = source->aux;
+  union value *input_buf;
   int i;
-  char *p = info->old_names;
-      
+
+  input_buf = xmalloc (sizeof *input_buf * flip->case_cnt);
   for (i = 0; i < flip->var_cnt; i++)
     {
-      st_bare_pad_copy (temp_case->data[0].s, p, 8);
-      p = strchr (p, 0) + 1;
-
-      if (fread (&temp_case->data[1], sizeof (double), info->case_cnt,
-                 info->file) != info->case_cnt)
-        msg (FE, _("Error reading FLIP source file: %s."),
-             strerror (errno));
+      size_t j;
+      
+      if (fread (input_buf, sizeof *input_buf, flip->case_cnt,
+                 flip->file) != flip->case_cnt) 
+        {
+          if (ferror (flip->file))
+            msg (SE, _("Error reading FLIP temporary file: %s."),
+                 strerror (errno));
+          else if (feof (flip->file))
+            msg (SE, _("Unexpected end of file reading FLIP temporary file."));
+          else
+            assert (0);
+          break;
+        }
 
+      for (j = 0; j < flip->case_cnt; j++)
+        case_data_rw (c, j)->f = input_buf[j].f;
       if (!write_case (wc_data))
-        return;
+        break;
     }
+  free (input_buf);
 }
 
-/* Destroy source's internal data. */
+/* Destroy internal data in SOURCE. */
 static void
 flip_source_destroy (struct case_source *source)
 {
-  struct flip_source_info *info = source->aux;
+  struct flip_pgm *flip = source->aux;
 
-  destroy_flip_pgm (info->flip);
-  free (info->old_names);
-  fclose (info->file);
-  free (info);
+  destroy_flip_pgm (flip);
 }
 
 static const struct case_source_class flip_source_class = 
   {
     "FLIP",
+    NULL,
     flip_source_read,
     flip_source_destroy
   };