GET DATA: Improve coding style and tests.
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 6 Nov 2022 19:33:59 +0000 (11:33 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 6 Nov 2022 19:46:01 +0000 (11:46 -0800)
src/data/psql-reader.c
src/data/psql-reader.h
src/language/data-io/get-data.c
tests/automake.mk
tests/language/data-io/get-data.at [new file with mode: 0644]

index ae56d18212b5eb86873835e6e8dc150285f544b6..e372a859a98ea661be660c479e4be83184a9d638 100644 (file)
@@ -234,7 +234,6 @@ psql_open_reader (struct psql_read_info *info, struct dictionary **dict)
   const char *encoding;
 
   struct psql_reader *r = XZALLOC (struct psql_reader);
-  struct string query ;
 
   r->conn = PQconnectdb (info->conninfo);
   if (NULL == r->conn)
@@ -305,21 +304,19 @@ psql_open_reader (struct psql_read_info *info, struct dictionary **dict)
   }
 
   const int version = PQserverVersion (r->conn);
-  ds_init_empty (&query);
   /*
     Versions before 9.1 don't have the REPEATABLE READ isolation level.
     However according to <a12321aabb@gmail.com> if the server is in the
     "hot standby" mode then SERIALIZABLE won't work.
    */
-  ds_put_c_format (&query,
-                  "BEGIN READ ONLY ISOLATION LEVEL %s; "
-                  "DECLARE  pspp BINARY CURSOR FOR ",
-                  (version < 90100) ? "SERIALIZABLE" : "REPEATABLE READ");
+  char *query = xasprintf (
+    "BEGIN READ ONLY ISOLATION LEVEL %s; "
+    "DECLARE  pspp BINARY CURSOR FOR %s",
+    (version < 90100) ? "SERIALIZABLE" : "REPEATABLE READ",
+    info->sql);
+  qres = PQexec (r->conn, query);
+  free (query);
 
-  ds_put_substring (&query, info->sql.ss);
-
-  qres = PQexec (r->conn, ds_cstr (&query));
-  ds_destroy (&query);
   if (PQresultStatus (qres) != PGRES_COMMAND_OK)
     {
       msg (ME, _("Error from psql source: %s."),
@@ -339,12 +336,11 @@ psql_open_reader (struct psql_read_info *info, struct dictionary **dict)
      On the other hand, most PSPP functions don't need to know this.
      The GUI is the notable exception.
   */
-  ds_init_cstr (&query, "SELECT count (*) FROM (");
-  ds_put_substring (&query, info->sql.ss);
-  ds_put_cstr (&query, ") stupid_sql_standard");
+  query = xasprintf ("SELECT count (*) FROM (%s) stupid_sql_standard",
+                     info->sql);
+  qres = PQexec (r->conn, query);
+  free (query);
 
-  qres = PQexec (r->conn, ds_cstr (&query));
-  ds_destroy (&query);
   if (PQresultStatus (qres) != PGRES_TUPLES_OK)
     {
       msg (ME, _("Error from psql source: %s."),
index 8fd584c60cdb7cbf7ad1695bf3d6de5e21b1fc6e..685d19a9183f36353f45b036c014bb0e07d5ed66 100644 (file)
@@ -25,7 +25,7 @@ struct casereader;
 struct psql_read_info
 {
   char *conninfo ;
-  struct string sql;
+  char * sql;
   bool allow_clear;
   int str_width;
   int bsize;
index 44741181667d36c38f6955c9896f49de7011e0c3..f35cb2f2aa77637e349f507e6fff01b691eca919 100644 (file)
@@ -51,110 +51,78 @@ static bool parse_spreadsheet (struct lexer *lexer, char **filename,
 
 static void destroy_spreadsheet_read_info (struct spreadsheet_read_options *);
 
-static int parse_get_txt (struct lexer *lexer, struct dataset *);
-static int parse_get_psql (struct lexer *lexer, struct dataset *);
+static int parse_get_txt (struct lexer *, struct dataset *);
+static int parse_get_psql (struct lexer *, struct dataset *);
+static int parse_get_spreadsheet (struct lexer *, struct dataset *,
+                                  struct spreadsheet *(*probe)(
+                                    const char *filename, bool report_errors));
 
 int
 cmd_get_data (struct lexer *lexer, struct dataset *ds)
 {
-  char *tok = NULL;
-  struct spreadsheet_read_options opts;
-
-  opts.sheet_name = NULL;
-  opts.sheet_index = -1;
-  opts.cell_range = NULL;
-  opts.read_names = false;
-  opts.asw = -1;
-
-  if (! lex_force_match (lexer, T_SLASH))
-    goto error;
-
-  if (!lex_force_match_id (lexer, "TYPE"))
-    goto error;
-
-  if (!lex_force_match (lexer, T_EQUALS))
-    goto error;
-
-  const char *s = lex_tokcstr (lexer);
-
-  if (s)
-    tok = strdup (s);
+  if (!lex_force_match_phrase (lexer, "/TYPE="))
+    return CMD_FAILURE;
 
   if (lex_match_id (lexer, "TXT"))
-    {
-      free (tok);
-      return parse_get_txt (lexer, ds);
-    }
+    return parse_get_txt (lexer, ds);
   else if (lex_match_id (lexer, "PSQL"))
+    return parse_get_psql (lexer, ds);
+  else if (lex_match_id (lexer, "GNM"))
+    return parse_get_spreadsheet (lexer, ds, gnumeric_probe);
+  else if (lex_match_id (lexer, "ODS"))
+    return parse_get_spreadsheet (lexer, ds, ods_probe);
+  else
     {
-      free (tok);
-      return parse_get_psql (lexer, ds);
+      lex_error_expecting (lexer, "TXT", "PSQL", "GNM", "ODS");
+      return CMD_FAILURE;
     }
-  else if (lex_match_id (lexer, "GNM") ||
-      lex_match_id (lexer, "ODS"))
-    {
-      char *filename = NULL;
-      if (!parse_spreadsheet (lexer, &filename, &opts))
-       goto error;
+}
 
-      struct spreadsheet *spreadsheet = NULL;
-      if (0 == strncasecmp (tok, "GNM", 3))
-        spreadsheet = gnumeric_probe (filename, true);
-      else if (0 == strncasecmp (tok, "ODS", 3))
-        spreadsheet = ods_probe (filename, true);
+static int
+parse_get_spreadsheet (struct lexer *lexer, struct dataset *ds,
+                       struct spreadsheet *(*probe)(
+                         const char *filename, bool report_errors))
+{
+  struct spreadsheet_read_options opts;
+  char *filename;
+  if (!parse_spreadsheet (lexer, &filename, &opts))
+    return CMD_FAILURE;
 
-      if (spreadsheet == NULL)
-        {
-          msg (SE, _("error reading file `%s'"), filename);
-          free (filename);
-          goto error;
-        }
-      free (filename);
+  bool ok = false;
+  struct spreadsheet *spreadsheet = probe (filename, true);
+  if (!spreadsheet)
+    {
+      msg (SE, _("error reading file `%s'"), filename);
+      goto done;
+    }
 
-      struct casereader *reader = spreadsheet_make_reader (spreadsheet, &opts);
-      if (reader)
-       {
-         dataset_set_dict (ds, dict_clone (spreadsheet->dict));
-         dataset_set_source (ds, reader);
-         free (tok);
-         destroy_spreadsheet_read_info (&opts);
-         spreadsheet_unref (spreadsheet);
-         return CMD_SUCCESS;
-       }
-      spreadsheet_unref (spreadsheet);
+  struct casereader *reader = spreadsheet_make_reader (spreadsheet, &opts);
+  if (reader)
+    {
+      dataset_set_dict (ds, dict_clone (spreadsheet->dict));
+      dataset_set_source (ds, reader);
+      ok = true;
     }
-  else
-    lex_error_expecting (lexer, "TXT", "PSQL", "GNM", "ODS");
+  spreadsheet_unref (spreadsheet);
 
- error:
+done:
+  free (filename);
   destroy_spreadsheet_read_info (&opts);
-  free (tok);
-  return CMD_FAILURE;
+  return ok ? CMD_SUCCESS : CMD_FAILURE;
 }
 
 static int
 parse_get_psql (struct lexer *lexer, struct dataset *ds)
 {
-  struct psql_read_info psql;
-  psql.allow_clear = false;
-  psql.conninfo = NULL;
-  psql.str_width = -1;
-  psql.bsize = -1;
-  ds_init_empty (&psql.sql);
-
-  if (! lex_force_match (lexer, T_SLASH))
-    goto error;
-
-  if (!lex_force_match_id (lexer, "CONNECT"))
-    goto error;
-
-  if (! lex_force_match (lexer, T_EQUALS))
-    goto error;
-
-  if (!lex_force_string (lexer))
-    goto error;
+  if (!lex_force_match_phrase (lexer, "/CONNECT=") || !lex_force_string (lexer))
+    return CMD_FAILURE;
 
-  psql.conninfo = ss_xstrdup (lex_tokss (lexer));
+  struct psql_read_info psql = {
+    .str_width = -1,
+    .bsize = -1,
+    .conninfo = ss_xstrdup (lex_tokss (lexer)),
+  };
+  bool ok = false;
 
   lex_get (lexer);
 
@@ -163,83 +131,63 @@ parse_get_psql (struct lexer *lexer, struct dataset *ds)
       if (lex_match_id (lexer, "ASSUMEDSTRWIDTH"))
        {
          lex_match (lexer, T_EQUALS);
-          if (lex_force_int_range (lexer, "ASSUMEDSTRWIDTH", 1, 32767))
-            {
-              psql.str_width = lex_integer (lexer);
-              lex_get (lexer);
-            }
+          if (!lex_force_int_range (lexer, "ASSUMEDSTRWIDTH", 1, 32767))
+            goto done;
+          psql.str_width = lex_integer (lexer);
+          lex_get (lexer);
        }
       else if (lex_match_id (lexer, "BSIZE"))
        {
          lex_match (lexer, T_EQUALS);
-          if (lex_force_int_range (lexer, "BSIZE", 1, INT_MAX))
-            {
-              psql.bsize = lex_integer (lexer);
-              lex_get (lexer);
-            }
+          if (!lex_force_int_range (lexer, "BSIZE", 1, INT_MAX))
+            goto done;
+          psql.bsize = lex_integer (lexer);
+          lex_get (lexer);
        }
       else if (lex_match_id (lexer, "UNENCRYPTED"))
-       {
-         psql.allow_clear = true;
-       }
+        psql.allow_clear = true;
       else if (lex_match_id (lexer, "SQL"))
        {
          lex_match (lexer, T_EQUALS);
-         if (! lex_force_string (lexer))
-           goto error;
+         if (!lex_force_string (lexer))
+           goto done;
 
-         ds_put_substring (&psql.sql, lex_tokss (lexer));
+          free (psql.sql);
+          psql.sql = ss_xstrdup (lex_tokss (lexer));
          lex_get (lexer);
        }
      }
-  {
-    struct dictionary *dict = NULL;
-    struct casereader *reader = psql_open_reader (&psql, &dict);
-
-    if (reader)
-      {
-        dataset_set_dict (ds, dict);
-        dataset_set_source (ds, reader);
-      }
-  }
-
-  ds_destroy (&psql.sql);
-  free (psql.conninfo);
 
-  return CMD_SUCCESS;
-
- error:
+  struct dictionary *dict = NULL;
+  struct casereader *reader = psql_open_reader (&psql, &dict);
+  if (reader)
+    {
+      dataset_set_dict (ds, dict);
+      dataset_set_source (ds, reader);
+    }
 
-  ds_destroy (&psql.sql);
+ done:
   free (psql.conninfo);
+  free (psql.sql);
 
-  return CMD_FAILURE;
+  return ok ? CMD_SUCCESS : CMD_FAILURE;
 }
 
 static bool
 parse_spreadsheet (struct lexer *lexer, char **filename,
                   struct spreadsheet_read_options *opts)
 {
-  opts->sheet_index = 1;
-  opts->sheet_name = NULL;
-  opts->cell_range = NULL;
-  opts->read_names = true;
-  opts->asw = -1;
-
-  if (! lex_force_match (lexer, T_SLASH))
+  *opts = (struct spreadsheet_read_options) {
+    .sheet_index = 1,
+    .read_names = true,
+    .asw = -1,
+  };
+  *filename = NULL;
+
+  if (!lex_force_match_phrase (lexer, "/FILE=") || !lex_force_string (lexer))
     goto error;
 
-  if (!lex_force_match_id (lexer, "FILE"))
-    goto error;
-
-  if (! lex_force_match (lexer, T_EQUALS))
-    goto error;
-
-  if (!lex_force_string (lexer))
-    goto error;
-
-  *filename  = utf8_to_filename (lex_tokcstr (lexer));
-
+  *filename = utf8_to_filename (lex_tokcstr (lexer));
   lex_get (lexer);
 
   while (lex_match (lexer, T_SLASH))
@@ -247,18 +195,17 @@ parse_spreadsheet (struct lexer *lexer, char **filename,
       if (lex_match_id (lexer, "ASSUMEDSTRWIDTH"))
        {
          lex_match (lexer, T_EQUALS);
-          if (lex_force_int_range (lexer, "ASSUMEDSTRWIDTH", 1, 32767))
-            {
-              opts->asw = lex_integer (lexer);
-              lex_get (lexer);
-            }
+          if (!lex_force_int_range (lexer, "ASSUMEDSTRWIDTH", 1, 32767))
+            goto error;
+          opts->asw = lex_integer (lexer);
+          lex_get (lexer);
        }
       else if (lex_match_id (lexer, "SHEET"))
        {
          lex_match (lexer, T_EQUALS);
          if (lex_match_id (lexer, "NAME"))
            {
-             if (! lex_force_string (lexer))
+             if (!lex_force_string (lexer))
                goto error;
 
              opts->sheet_name = ss_xstrdup (lex_tokss (lexer));
@@ -284,12 +231,10 @@ parse_spreadsheet (struct lexer *lexer, char **filename,
          lex_match (lexer, T_EQUALS);
 
          if (lex_match_id (lexer, "FULL"))
-           {
-             opts->cell_range = NULL;
-           }
+            opts->cell_range = NULL;
          else if (lex_match_id (lexer, "RANGE"))
            {
-             if (! lex_force_string (lexer))
+             if (!lex_force_string (lexer))
                goto error;
 
              opts->cell_range = ss_xstrdup (lex_tokss (lexer));
@@ -306,13 +251,9 @@ parse_spreadsheet (struct lexer *lexer, char **filename,
          lex_match (lexer, T_EQUALS);
 
          if (lex_match_id (lexer, "ON"))
-           {
-             opts->read_names = true;
-           }
+            opts->read_names = true;
          else if (lex_match_id (lexer, "OFF"))
-           {
-             opts->read_names = false;
-           }
+            opts->read_names = false;
          else
            {
               lex_error_expecting (lexer, "ON", "OFF");
@@ -321,7 +262,8 @@ parse_spreadsheet (struct lexer *lexer, char **filename,
        }
       else
        {
-         lex_error (lexer, NULL);
+         lex_error_expecting (lexer, "ASSUMEDSTRWIDTH", "SHEET", "CELLRANGE",
+                               "READNAMES");
          goto error;
        }
     }
@@ -329,26 +271,32 @@ parse_spreadsheet (struct lexer *lexer, char **filename,
   return true;
 
  error:
+  destroy_spreadsheet_read_info (opts);
+  free (*filename);
   return false;
 }
 
 
 static bool
-set_type (struct data_parser *parser, const char *subcommand,
-          enum data_parser_type type, bool *has_type)
+set_type (struct lexer *lexer, struct data_parser *parser,
+          enum data_parser_type type,
+          int type_start, int type_end, int *type_startp, int *type_endp)
 {
-  if (!*has_type)
+  if (!*type_startp)
     {
       data_parser_set_type (parser, type);
-      *has_type = true;
+      *type_startp = type_start;
+      *type_endp = type_end;
     }
   else if (type != data_parser_get_type (parser))
     {
-      msg (SE, _("%s is allowed only with %s arrangement, but %s arrangement "
-                 "was stated or implied earlier in this command."),
-           subcommand,
-           type == DP_FIXED ? "FIXED" : "DELIMITED",
-           type == DP_FIXED ? "DELIMITED" : "FIXED");
+      msg (SE, _("FIXED and DELIMITED arrangements are mutually exclusive."));
+      lex_ofs_msg (lexer, SN, type_start, type_end,
+                   _("This syntax requires %s arrangement."),
+                   type == DP_FIXED ? "FIXED" : "DELIMITED");
+      lex_ofs_msg (lexer, SN, *type_startp, *type_endp,
+                   _("This syntax requires %s arrangement."),
+                   type == DP_FIXED ? "DELIMITED" : "FIXED");
       return false;
     }
   return true;
@@ -357,30 +305,19 @@ set_type (struct data_parser *parser, const char *subcommand,
 static int
 parse_get_txt (struct lexer *lexer, struct dataset *ds)
 {
-  struct data_parser *parser = NULL;
   struct dictionary *dict = dict_create (get_default_encoding ());
+  struct data_parser *parser = data_parser_create ();
   struct file_handle *fh = NULL;
-  struct dfm_reader *reader = NULL;
   char *encoding = NULL;
   char *name = NULL;
 
-  int record;
-  enum data_parser_type type;
-  bool has_type;
-
-  if (! lex_force_match (lexer, T_SLASH))
-    goto error;
-
-  if (!lex_force_match_id (lexer, "FILE"))
-    goto error;
-  if (! lex_force_match (lexer, T_EQUALS))
+  if (!lex_force_match_phrase (lexer, "/FILE="))
     goto error;
   fh = fh_parse (lexer, FH_REF_FILE | FH_REF_INLINE, NULL);
   if (fh == NULL)
     goto error;
 
-  parser = data_parser_create ();
-  has_type = false;
+  int type_start = 0, type_end = 0;
   data_parser_set_type (parser, DP_DELIMITED);
   data_parser_set_span (parser, false);
   data_parser_set_quotes (parser, ss_empty ());
@@ -409,10 +346,13 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds)
 
          lex_match (lexer, T_EQUALS);
           if (lex_match_id (lexer, "FIXED"))
-            ok = set_type (parser, "ARRANGEMENT=FIXED", DP_FIXED, &has_type);
+            ok = set_type (lexer, parser, DP_FIXED,
+                           lex_ofs (lexer) - 3, lex_ofs (lexer) - 1,
+                           &type_start, &type_end);
           else if (lex_match_id (lexer, "DELIMITED"))
-            ok = set_type (parser, "ARRANGEMENT=DELIMITED",
-                           DP_DELIMITED, &has_type);
+            ok = set_type (lexer, parser, DP_DELIMITED,
+                           lex_ofs (lexer) - 3, lex_ofs (lexer) - 1,
+                           &type_start, &type_end);
           else
             {
               lex_error_expecting (lexer, "FIXED", "DELIMITED");
@@ -431,7 +371,9 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds)
         }
       else if (lex_match_id_n (lexer, "DELCASE", 4))
         {
-          if (!set_type (parser, "DELCASE", DP_DELIMITED, &has_type))
+          if (!set_type (lexer, parser, DP_DELIMITED,
+                         lex_ofs (lexer) - 1, lex_ofs (lexer) - 1,
+                         &type_start, &type_end))
             goto error;
           lex_match (lexer, T_EQUALS);
           if (lex_match_id (lexer, "LINE"))
@@ -454,7 +396,9 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds)
         }
       else if (lex_match_id (lexer, "FIXCASE"))
         {
-          if (!set_type (parser, "FIXCASE", DP_FIXED, &has_type))
+          if (!set_type (lexer, parser, DP_FIXED,
+                         lex_ofs (lexer) - 1, lex_ofs (lexer) - 1,
+                         &type_start, &type_end))
             goto error;
           lex_match (lexer, T_EQUALS);
           if (!lex_force_int_range (lexer, "FIXCASE", 1, INT_MAX))
@@ -488,12 +432,9 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds)
         }
       else if (lex_match_id_n (lexer, "DELIMITERS", 4))
         {
-          struct string hard_seps = DS_EMPTY_INITIALIZER;
-          const char *soft_seps = "";
-          struct substring s;
-          int c;
-
-          if (!set_type (parser, "DELIMITERS", DP_DELIMITED, &has_type))
+          if (!set_type (lexer, parser, DP_DELIMITED,
+                         lex_ofs (lexer) - 1, lex_ofs (lexer) - 1,
+                         &type_start, &type_end))
             goto error;
           lex_match (lexer, T_EQUALS);
 
@@ -501,11 +442,14 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds)
             goto error;
 
           /* XXX should support multibyte UTF-8 characters */
-          s = lex_tokss (lexer);
+          struct substring s = lex_tokss (lexer);
+          struct string hard_seps = DS_EMPTY_INITIALIZER;
+          const char *soft_seps = "";
           if (ss_match_string (&s, ss_cstr ("\\t")))
             ds_put_cstr (&hard_seps, "\t");
           if (ss_match_string (&s, ss_cstr ("\\\\")))
             ds_put_cstr (&hard_seps, "\\");
+          int c;
           while ((c = ss_get_byte (&s)) != EOF)
             if (c == ' ')
               soft_seps = " ";
@@ -519,7 +463,9 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds)
         }
       else if (lex_match_id (lexer, "QUALIFIERS"))
         {
-          if (!set_type (parser, "QUALIFIERS", DP_DELIMITED, &has_type))
+          if (!set_type (lexer, parser, DP_DELIMITED,
+                         lex_ofs (lexer) - 1, lex_ofs (lexer) - 1,
+                         &type_start, &type_end))
             goto error;
           lex_match (lexer, T_EQUALS);
 
@@ -548,14 +494,10 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds)
     }
   lex_match (lexer, T_EQUALS);
 
-  record = 1;
-  type = data_parser_get_type (parser);
+  int record = 1;
+  enum data_parser_type type = data_parser_get_type (parser);
   do
     {
-      struct fmt_spec input, output;
-      struct variable *v;
-      int fc, lc;
-
       while (type == DP_FIXED && lex_match (lexer, T_SLASH))
         {
           if (!lex_force_int_range (lexer, NULL, record,
@@ -577,6 +519,9 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds)
          goto error;
        }
       lex_get (lexer);
+
+      struct fmt_spec input, output;
+      int fc, lc;
       if (type == DP_DELIMITED)
         {
           if (!parse_format_specifier (lexer, &input))
@@ -618,7 +563,7 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds)
           error = fmt_check_input__ (&input);
           if (error)
             {
-              lex_next_error (lexer, start_ofs, end_ofs, "%s", error);
+              lex_ofs_error (lexer, start_ofs, end_ofs, "%s", error);
               free (error);
               goto error;
             }
@@ -630,7 +575,7 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds)
               error = fmt_check_output__ (&output);
               if (error)
                 {
-                  lex_next_error (lexer, start_ofs, end_ofs, "%s", error);
+                  lex_ofs_error (lexer, start_ofs, end_ofs, "%s", error);
                   free (error);
                   goto error;
                 }
@@ -639,8 +584,8 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds)
             output = fmt_for_output_from_input (&input,
                                                 settings_get_fmt_settings ());
         }
-      v = dict_create_var (dict, name, fmt_var_width (&input));
-      if (v == NULL)
+      struct variable *v = dict_create_var (dict, name, fmt_var_width (&input));
+      if (!v)
         {
           lex_ofs_error (lexer, name_ofs, name_ofs,
                          _("%s is a duplicate variable name."), name);
@@ -659,8 +604,8 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds)
     }
   while (lex_token (lexer) != T_ENDCMD);
 
-  reader = dfm_open_reader (fh, lexer, encoding);
-  if (reader == NULL)
+  struct dfm_reader *reader = dfm_open_reader (fh, lexer, encoding);
+  if (!reader)
     goto error;
 
   data_parser_make_active_file (parser, ds, reader, dict, NULL, NULL);
@@ -677,7 +622,6 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds)
   return CMD_CASCADING_FAILURE;
 }
 
-
 static void
 destroy_spreadsheet_read_info (struct spreadsheet_read_options *opts)
 {
index 6cfb3e0eee07bce3965425f318d4aecfe09aeab2..679be89537cba15ceb86e27b2ccc2e548d0b248e 100644 (file)
@@ -357,6 +357,7 @@ TESTSUITE_AT = \
        tests/language/data-io/data-reader.at \
        tests/language/data-io/dataset.at \
        tests/language/data-io/file-handle.at \
+       tests/language/data-io/get-data.at \
        tests/language/data-io/get-data-spreadsheet.at \
        tests/language/data-io/get-data-psql.at \
        tests/language/data-io/get-data-txt.at \
diff --git a/tests/language/data-io/get-data.at b/tests/language/data-io/get-data.at
new file mode 100644 (file)
index 0000000..1785b5b
--- /dev/null
@@ -0,0 +1,302 @@
+AT_BANNER([GET DATA])
+
+AT_SETUP([GET DATA syntax errors])
+AT_DATA([get-data.sps], [dnl
+GET DATA **.
+GET DATA / **.
+GET DATA /TYPE **.
+
+GET DATA /TYPE=TXT **.
+GET DATA /TYPE=TXT/ **.
+GET DATA /TYPE=TXT/FILE **.
+GET DATA /TYPE=TXT/FILE='x.txt' **.
+GET DATA /TYPE=TXT/FILE='x.txt' /ENCODING=**.
+GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=**.
+GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /ARRANGEMENT=DELIMITED.
+GET DATA /TYPE=TXT/FILE='x.txt' /FIRSTCASE=**.
+GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /DELCASE=LINE.
+GET DATA /TYPE=TXT/FILE='x.txt' /DELCASE=VARIABLES **.
+GET DATA /TYPE=TXT/FILE='x.txt' /DELCASE=**.
+GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=DELIMITED /FIXCASE=1.
+GET DATA /TYPE=TXT/FILE='x.txt' /FIXCASE=**.
+GET DATA /TYPE=TXT/FILE='x.txt' /IMPORTCASES=FIRST **.
+GET DATA /TYPE=TXT/FILE='x.txt' /IMPORTCASES=PERCENT **.
+GET DATA /TYPE=TXT/FILE='x.txt' /IMPORTCASES=ALL.
+GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /DELIMITERS=' '.
+GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /QUALIFIER='"'.
+GET DATA /TYPE=TXT/FILE='x.txt' /QUALIFIER='"' + "'".
+GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /VARIABLES / **.
+GET DATA /TYPE=TXT/FILE='x.txt' /VARIABLES **.
+GET DATA /TYPE=TXT/FILE='x.txt' /VARIABLES a_very_long_name_that_exceeds_the_64_byte_limit_for_variable_names.
+GET DATA /TYPE=TXT/FILE='x.txt' /VARIABLES x **.
+GET DATA /TYPE=TXT/FILE='x.txt' /VARIABLES x F1.2.
+GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /VARIABLES x **.
+GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /VARIABLES x 1-5 **.
+GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /VARIABLES x 1-5 FOO.
+GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /VARIABLES x 1-5 DATE.
+GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /VARIABLES x 1-5 DOLLAR1.2.
+GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /VARIABLES x 1-5 F x 6-10 F.
+
+GET DATA /TYPE=PSQL **.
+GET DATA /TYPE=PSQL/ **.
+GET DATA /TYPE=PSQL/CONNECT **.
+GET DATA /TYPE=PSQL/CONNECT='db'/ASSUMEDSTRWIDTH=**.
+GET DATA /TYPE=PSQL/CONNECT='db'/BSIZE=**.
+GET DATA /TYPE=PSQL/CONNECT='db'/SQL=**.
+
+GET DATA /TYPE=GNM **.
+GET DATA /TYPE=GNM/ **.
+GET DATA /TYPE=GNM/FILE **.
+GET DATA /TYPE=GNM/FILE= **.
+GET DATA /TYPE=GNM/FILE='x.gnumeric'/ASSUMEDSTRWIDTH=**.
+GET DATA /TYPE=GNM/FILE='x.gnumeric'/SHEET=NAME **.
+GET DATA /TYPE=GNM/FILE='x.gnumeric'/SHEET=INDEX **.
+GET DATA /TYPE=GNM/FILE='x.gnumeric'/SHEET=**.
+GET DATA /TYPE=GNM/FILE='x.gnumeric'/CELLRANGE=RANGE **.
+GET DATA /TYPE=GNM/FILE='x.gnumeric'/CELLRANGE=**.
+GET DATA /TYPE=GNM/FILE='x.gnumeric'/READNAMES=**.
+GET DATA /TYPE=GNM/FILE='x.gnumeric'/ **.
+])
+AT_DATA([insert.sps], [dnl
+INSERT FILE='get-data.sps' ERROR=IGNORE.
+])
+AT_CHECK([pspp -x compatible --testing-mode -O format=csv insert.sps], [1], [dnl
+"get-data.sps:1.10-1.11: error: GET DATA: Syntax error expecting `/TYPE='.
+    1 | GET DATA **.
+      |          ^~"
+
+"get-data.sps:2.10-2.13: error: GET DATA: Syntax error expecting `/TYPE='.
+    2 | GET DATA / **.
+      |          ^~~~"
+
+"get-data.sps:3.10-3.17: error: GET DATA: Syntax error expecting `/TYPE='.
+    3 | GET DATA /TYPE **.
+      |          ^~~~~~~~"
+
+"get-data.sps:5.20-5.21: error: GET DATA: Syntax error expecting `/FILE='.
+    5 | GET DATA /TYPE=TXT **.
+      |                    ^~"
+
+"get-data.sps:6.19-6.22: error: GET DATA: Syntax error expecting `/FILE='.
+    6 | GET DATA /TYPE=TXT/ **.
+      |                   ^~~~"
+
+"get-data.sps:7.19-7.26: error: GET DATA: Syntax error expecting `/FILE='.
+    7 | GET DATA /TYPE=TXT/FILE **.
+      |                   ^~~~~~~~"
+
+"get-data.sps:8.33-8.34: error: GET DATA: Syntax error expecting `/'.
+    8 | GET DATA /TYPE=TXT/FILE='x.txt' **.
+      |                                 ^~"
+
+"get-data.sps:9.43-9.44: error: GET DATA: Syntax error expecting string.
+    9 | GET DATA /TYPE=TXT/FILE='x.txt' /ENCODING=**.
+      |                                           ^~"
+
+"get-data.sps:10.46-10.47: error: GET DATA: Syntax error expecting FIXED or DELIMITED.
+   10 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=**.
+      |                                              ^~"
+
+get-data.sps:11: error: GET DATA: FIXED and DELIMITED arrangements are mutually exclusive.
+
+"get-data.sps:11.53-11.73: note: GET DATA: This syntax requires DELIMITED arrangement.
+   11 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /ARRANGEMENT=DELIMITED.
+      |                                                     ^~~~~~~~~~~~~~~~~~~~~"
+
+"get-data.sps:11.34-11.50: note: GET DATA: This syntax requires FIXED arrangement.
+   11 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /ARRANGEMENT=DELIMITED.
+      |                                  ^~~~~~~~~~~~~~~~~"
+
+"get-data.sps:12.44-12.45: error: GET DATA: Syntax error expecting positive integer for FIRSTCASE.
+   12 | GET DATA /TYPE=TXT/FILE='x.txt' /FIRSTCASE=**.
+      |                                            ^~"
+
+get-data.sps:13: error: GET DATA: FIXED and DELIMITED arrangements are mutually exclusive.
+
+"get-data.sps:13.53-13.59: note: GET DATA: This syntax requires DELIMITED arrangement.
+   13 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /DELCASE=LINE.
+      |                                                     ^~~~~~~"
+
+"get-data.sps:13.34-13.50: note: GET DATA: This syntax requires FIXED arrangement.
+   13 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /DELCASE=LINE.
+      |                                  ^~~~~~~~~~~~~~~~~"
+
+"get-data.sps:14.52-14.53: error: GET DATA: Syntax error expecting integer.
+   14 | GET DATA /TYPE=TXT/FILE='x.txt' /DELCASE=VARIABLES **.
+      |                                                    ^~"
+
+"get-data.sps:15.42-15.43: error: GET DATA: Syntax error expecting LINE or VARIABLES.
+   15 | GET DATA /TYPE=TXT/FILE='x.txt' /DELCASE=**.
+      |                                          ^~"
+
+get-data.sps:16: error: GET DATA: FIXED and DELIMITED arrangements are mutually exclusive.
+
+"get-data.sps:16.57-16.63: note: GET DATA: This syntax requires FIXED arrangement.
+   16 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=DELIMITED /FIXCASE=1.
+      |                                                         ^~~~~~~"
+
+"get-data.sps:16.34-16.54: note: GET DATA: This syntax requires DELIMITED arrangement.
+   16 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=DELIMITED /FIXCASE=1.
+      |                                  ^~~~~~~~~~~~~~~~~~~~~"
+
+"get-data.sps:17.42-17.43: error: GET DATA: Syntax error expecting positive integer for FIXCASE.
+   17 | GET DATA /TYPE=TXT/FILE='x.txt' /FIXCASE=**.
+      |                                          ^~"
+
+"get-data.sps:18.52-18.53: error: GET DATA: Syntax error expecting integer.
+   18 | GET DATA /TYPE=TXT/FILE='x.txt' /IMPORTCASES=FIRST **.
+      |                                                    ^~"
+
+"get-data.sps:19.54-19.55: error: GET DATA: Syntax error expecting integer.
+   19 | GET DATA /TYPE=TXT/FILE='x.txt' /IMPORTCASES=PERCENT **.
+      |                                                      ^~"
+
+"get-data.sps:20.34-20.48: warning: GET DATA: Ignoring obsolete IMPORTCASES subcommand.  (N OF CASES or SAMPLE may be used to substitute.).
+   20 | GET DATA /TYPE=TXT/FILE='x.txt' /IMPORTCASES=ALL.
+      |                                  ^~~~~~~~~~~~~~~"
+
+"get-data.sps:20.49: error: GET DATA: Syntax error expecting `/'.
+   20 | GET DATA /TYPE=TXT/FILE='x.txt' /IMPORTCASES=ALL.
+      |                                                 ^"
+
+get-data.sps:21: error: GET DATA: FIXED and DELIMITED arrangements are mutually exclusive.
+
+"get-data.sps:21.53-21.62: note: GET DATA: This syntax requires DELIMITED arrangement.
+   21 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /DELIMITERS=' '.
+      |                                                     ^~~~~~~~~~"
+
+"get-data.sps:21.34-21.50: note: GET DATA: This syntax requires FIXED arrangement.
+   21 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /DELIMITERS=' '.
+      |                                  ^~~~~~~~~~~~~~~~~"
+
+get-data.sps:22: error: GET DATA: FIXED and DELIMITED arrangements are mutually exclusive.
+
+"get-data.sps:22.53-22.61: note: GET DATA: This syntax requires DELIMITED arrangement.
+   22 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /QUALIFIER='""'.
+      |                                                     ^~~~~~~~~"
+
+"get-data.sps:22.34-22.50: note: GET DATA: This syntax requires FIXED arrangement.
+   22 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /QUALIFIER='""'.
+      |                                  ^~~~~~~~~~~~~~~~~"
+
+"get-data.sps:23.44-23.52: error: GET DATA: In compatible syntax mode, the QUALIFIER string must contain exactly one character.
+   23 | GET DATA /TYPE=TXT/FILE='x.txt' /QUALIFIER='""' + ""'"".
+      |                                            ^~~~~~~~~"
+
+"get-data.sps:24.65-24.66: error: GET DATA: Syntax error expecting integer.
+   24 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /VARIABLES / **.
+      |                                                                 ^~"
+
+"get-data.sps:25.44-25.45: error: GET DATA: Syntax error expecting identifier.
+   25 | GET DATA /TYPE=TXT/FILE='x.txt' /VARIABLES **.
+      |                                            ^~"
+
+"get-data.sps:26.44-26.109: error: GET DATA: Identifier `a_very_long_name_that_exceeds_the_64_byte_limit_for_variable_names' exceeds 64-byte limit.
+   26 | GET DATA /TYPE=TXT/FILE='x.txt' /VARIABLES a_very_long_name_that_exceeds_the_64_byte_limit_for_variable_names.
+      |                                            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+
+"get-data.sps:27.46-27.47: error: GET DATA: Syntax error expecting valid format specifier.
+   27 | GET DATA /TYPE=TXT/FILE='x.txt' /VARIABLES x **.
+      |                                              ^~"
+
+"get-data.sps:28.46-28.49: error: GET DATA: Input format F1.2 specifies 2 decimal places, but width 1 allows at most 1 decimals.
+   28 | GET DATA /TYPE=TXT/FILE='x.txt' /VARIABLES x F1.2.
+      |                                              ^~~~"
+
+"get-data.sps:29.65-29.66: error: GET DATA: Syntax error expecting integer.
+   29 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /VARIABLES x **.
+      |                                                                 ^~"
+
+"get-data.sps:30.69-30.70: error: GET DATA: Syntax error expecting valid format specifier.
+   30 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /VARIABLES x 1-5 **.
+      |                                                                     ^~"
+
+"get-data.sps:31.69-31.71: error: GET DATA: Unknown format type `FOO'.
+   31 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /VARIABLES x 1-5 FOO.
+      |                                                                     ^~~"
+
+"get-data.sps:32.65-32.72: error: GET DATA: Input format DATE5 specifies width 5, but DATE requires a width between 8 and 40.
+   32 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /VARIABLES x 1-5 DATE.
+      |                                                                 ^~~~~~~~"
+
+"get-data.sps:33.65-33.77: error: GET DATA: Output format DOLLAR1.2 specifies width 1, but DOLLAR requires a width between 2 and 40.
+   33 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /VARIABLES x 1-5 DOLLAR1.2.
+      |                                                                 ^~~~~~~~~~~~~"
+
+"get-data.sps:34.71: error: GET DATA: x is a duplicate variable name.
+   34 | GET DATA /TYPE=TXT/FILE='x.txt' /ARRANGEMENT=FIXED /VARIABLES x 1-5 F x 6-10 F.
+      |                                                                       ^"
+
+"get-data.sps:36.21-36.22: error: GET DATA: Syntax error expecting `/CONNECT='.
+   36 | GET DATA /TYPE=PSQL **.
+      |                     ^~"
+
+"get-data.sps:37.20-37.23: error: GET DATA: Syntax error expecting `/CONNECT='.
+   37 | GET DATA /TYPE=PSQL/ **.
+      |                    ^~~~"
+
+"get-data.sps:38.20-38.30: error: GET DATA: Syntax error expecting `/CONNECT='.
+   38 | GET DATA /TYPE=PSQL/CONNECT **.
+      |                    ^~~~~~~~~~~"
+
+"get-data.sps:39.50-39.51: error: GET DATA: Syntax error expecting integer between 1 and 32767 for ASSUMEDSTRWIDTH.
+   39 | GET DATA /TYPE=PSQL/CONNECT='db'/ASSUMEDSTRWIDTH=**.
+      |                                                  ^~"
+
+"get-data.sps:40.40-40.41: error: GET DATA: Syntax error expecting positive integer for BSIZE.
+   40 | GET DATA /TYPE=PSQL/CONNECT='db'/BSIZE=**.
+      |                                        ^~"
+
+"get-data.sps:41.38-41.39: error: GET DATA: Syntax error expecting string.
+   41 | GET DATA /TYPE=PSQL/CONNECT='db'/SQL=**.
+      |                                      ^~"
+
+"get-data.sps:43.20-43.21: error: GET DATA: Syntax error expecting `/FILE='.
+   43 | GET DATA /TYPE=GNM **.
+      |                    ^~"
+
+"get-data.sps:44.19-44.22: error: GET DATA: Syntax error expecting `/FILE='.
+   44 | GET DATA /TYPE=GNM/ **.
+      |                   ^~~~"
+
+"get-data.sps:45.19-45.26: error: GET DATA: Syntax error expecting `/FILE='.
+   45 | GET DATA /TYPE=GNM/FILE **.
+      |                   ^~~~~~~~"
+
+"get-data.sps:46.26-46.27: error: GET DATA: Syntax error expecting string.
+   46 | GET DATA /TYPE=GNM/FILE= **.
+      |                          ^~"
+
+"get-data.sps:47.54-47.55: error: GET DATA: Syntax error expecting integer between 1 and 32767 for ASSUMEDSTRWIDTH.
+   47 | GET DATA /TYPE=GNM/FILE='x.gnumeric'/ASSUMEDSTRWIDTH=**.
+      |                                                      ^~"
+
+"get-data.sps:48.49-48.50: error: GET DATA: Syntax error expecting string.
+   48 | GET DATA /TYPE=GNM/FILE='x.gnumeric'/SHEET=NAME **.
+      |                                                 ^~"
+
+"get-data.sps:49.50-49.51: error: GET DATA: Syntax error expecting positive integer for INDEX.
+   49 | GET DATA /TYPE=GNM/FILE='x.gnumeric'/SHEET=INDEX **.
+      |                                                  ^~"
+
+"get-data.sps:50.44-50.45: error: GET DATA: Syntax error expecting NAME or INDEX.
+   50 | GET DATA /TYPE=GNM/FILE='x.gnumeric'/SHEET=**.
+      |                                            ^~"
+
+"get-data.sps:51.54-51.55: error: GET DATA: Syntax error expecting string.
+   51 | GET DATA /TYPE=GNM/FILE='x.gnumeric'/CELLRANGE=RANGE **.
+      |                                                      ^~"
+
+"get-data.sps:52.48-52.49: error: GET DATA: Syntax error expecting FULL or RANGE.
+   52 | GET DATA /TYPE=GNM/FILE='x.gnumeric'/CELLRANGE=**.
+      |                                                ^~"
+
+"get-data.sps:53.48-53.49: error: GET DATA: Syntax error expecting ON or OFF.
+   53 | GET DATA /TYPE=GNM/FILE='x.gnumeric'/READNAMES=**.
+      |                                                ^~"
+
+"get-data.sps:54.39-54.40: error: GET DATA: Syntax error expecting ASSUMEDSTRWIDTH, SHEET, CELLRANGE, or READNAMES.
+   54 | GET DATA /TYPE=GNM/FILE='x.gnumeric'/ **.
+      |                                       ^~"
+])
+AT_CLEANUP
\ No newline at end of file