GET DATA: Improve coding style and tests.
[pspp] / src / language / data-io / get-data.c
index daec18f41da340c9d0e5b9c882eae4e3c2fbc53d..f35cb2f2aa77637e349f507e6fff01b691eca919 100644 (file)
@@ -1,6 +1,6 @@
 /* PSPP - a program for statistical analysis.
    Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012,
-                 2013 Free Software Foundation, Inc.
+                 2013, 2015, 2016 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
@@ -51,195 +51,161 @@ 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)
 {
-  struct spreadsheet_read_options opts;
-  char *tok = NULL;
-  lex_force_match (lexer, T_SLASH);
-
-  if (!lex_force_match_id (lexer, "TYPE"))
-    goto error;
-
-  lex_force_match (lexer, T_EQUALS);
+  if (!lex_force_match_phrase (lexer, "/TYPE="))
+    return CMD_FAILURE;
 
-  tok = strdup (lex_tokcstr (lexer));
   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;
-      struct casereader *reader = NULL;
-      struct dictionary *dict = NULL;
-
-      if (!parse_spreadsheet (lexer, &filename, &opts))
-       goto error;
-
-      if ( 0 == strncasecmp (tok, "GNM", 3))
-       {
-         struct spreadsheet *spreadsheet = gnumeric_probe (filename, true);
-         if (spreadsheet == NULL)
-           goto error;
-         reader = gnumeric_make_reader (spreadsheet, &opts);
-         dict = spreadsheet->dict;
-       }
-      else if (0 == strncasecmp (tok, "ODS", 3))
-       {
-         struct spreadsheet *spreadsheet = ods_probe (filename, true);
-         if (spreadsheet == NULL)
-           goto error;
-         reader = ods_make_reader (spreadsheet, &opts);
-         dict = spreadsheet->dict;
-         ods_destroy (spreadsheet);
-       }
+}
 
-      free (filename);
+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 (reader)
-       {
-         dataset_set_dict (ds, dict);
-         dataset_set_source (ds, reader);
-         free (tok);
-         destroy_spreadsheet_read_info (&opts);
-         return CMD_SUCCESS;
-       }
+  bool ok = false;
+  struct spreadsheet *spreadsheet = probe (filename, true);
+  if (!spreadsheet)
+    {
+      msg (SE, _("error reading file `%s'"), filename);
+      goto done;
     }
-  else
-    msg (SE, _("Unsupported TYPE %s."), tok);
 
+  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;
+    }
+  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);
-
-  lex_force_match (lexer, T_SLASH);
-
-  if (!lex_force_match_id (lexer, "CONNECT"))
-    goto error;
+  if (!lex_force_match_phrase (lexer, "/CONNECT=") || !lex_force_string (lexer))
+    return CMD_FAILURE;
 
-  lex_force_match (lexer, T_EQUALS);
-
-  if (!lex_force_string (lexer))
-    goto error;
-
-  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);
 
-  while (lex_match (lexer, T_SLASH) )
+  while (lex_match (lexer, T_SLASH))
     {
-      if ( lex_match_id (lexer, "ASSUMEDSTRWIDTH"))
+      if (lex_match_id (lexer, "ASSUMEDSTRWIDTH"))
        {
          lex_match (lexer, T_EQUALS);
-         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"))
+      else if (lex_match_id (lexer, "BSIZE"))
        {
          lex_match (lexer, T_EQUALS);
-         psql.bsize = lex_integer (lexer);
-         lex_get (lexer);
-       }
-      else if ( lex_match_id (lexer, "UNENCRYPTED"))
-       {
-         psql.allow_clear = true;
+          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;
       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, 
+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;
-
-  lex_force_match (lexer, T_SLASH);
-
-  if (!lex_force_match_id (lexer, "FILE"))
-    goto error;
-
-  lex_force_match (lexer, T_EQUALS);
-
-  if (!lex_force_string (lexer))
+  *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;
 
-  *filename  = utf8_to_filename (lex_tokcstr (lexer));
-
+  *filename = utf8_to_filename (lex_tokcstr (lexer));
   lex_get (lexer);
 
-  while (lex_match (lexer, T_SLASH) )
+  while (lex_match (lexer, T_SLASH))
     {
-      if ( lex_match_id (lexer, "ASSUMEDSTRWIDTH"))
+      if (lex_match_id (lexer, "ASSUMEDSTRWIDTH"))
        {
          lex_match (lexer, T_EQUALS);
-         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));
@@ -249,18 +215,14 @@ parse_spreadsheet (struct lexer *lexer, char **filename,
            }
          else if (lex_match_id (lexer, "INDEX"))
            {
+              if (!lex_force_int_range (lexer, "INDEX", 1, INT_MAX))
+                goto error;
              opts->sheet_index = lex_integer (lexer);
-             if (opts->sheet_index <= 0)
-               {
-                 msg (SE, _("The sheet index must be greater than or equal to 1"));
-                 goto error;
-               }
              lex_get (lexer);
            }
          else
            {
-             msg (SE, _("%s must be followed by either \"%s\" or \"%s\"."),
-                  "/SHEET", "NAME", "INDEX");
+              lex_error_expecting (lexer, "NAME", "INDEX");
              goto error;
            }
        }
@@ -269,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));
@@ -282,8 +242,7 @@ parse_spreadsheet (struct lexer *lexer, char **filename,
            }
          else
            {
-             msg (SE, _("%s must be followed by either \"%s\" or \"%s\"."),
-                  "/CELLRANGE", "FULL", "RANGE");
+              lex_error_expecting (lexer, "FULL", "RANGE");
              goto error;
            }
        }
@@ -291,24 +250,20 @@ parse_spreadsheet (struct lexer *lexer, char **filename,
        {
          lex_match (lexer, T_EQUALS);
 
-         if ( lex_match_id (lexer, "ON"))
-           {
-             opts->read_names = true;
-           }
+         if (lex_match_id (lexer, "ON"))
+            opts->read_names = true;
          else if (lex_match_id (lexer, "OFF"))
-           {
-             opts->read_names = false;
-           }
+            opts->read_names = false;
          else
            {
-             msg (SE, _("%s must be followed by either \"%s\" or \"%s\"."),
-                  "/READNAMES", "ON", "OFF");
+              lex_error_expecting (lexer, "ON", "OFF");
              goto error;
            }
        }
       else
        {
-         lex_error (lexer, NULL);
+         lex_error_expecting (lexer, "ASSUMEDSTRWIDTH", "SHEET", "CELLRANGE",
+                               "READNAMES");
          goto error;
        }
     }
@@ -316,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;
@@ -344,31 +305,23 @@ 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;
-
-  lex_force_match (lexer, T_SLASH);
-
-  if (!lex_force_match_id (lexer, "FILE"))
+  if (!lex_force_match_phrase (lexer, "/FILE="))
     goto error;
-  lex_force_match (lexer, T_EQUALS);
   fh = fh_parse (lexer, FH_REF_FILE | FH_REF_INLINE, NULL);
   if (fh == NULL)
     goto error;
 
-  parser = data_parser_create (dict);
-  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 ());
+  data_parser_set_quote_escape (parser, true);
   data_parser_set_empty_line_has_field (parser, true);
 
   for (;;)
@@ -393,13 +346,16 @@ 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", NULL_SENTINEL);
+              lex_error_expecting (lexer, "FIXED", "DELIMITED");
               goto error;
             }
           if (!ok)
@@ -408,19 +364,16 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds)
       else if (lex_match_id (lexer, "FIRSTCASE"))
         {
          lex_match (lexer, T_EQUALS);
-          if (!lex_force_int (lexer))
+          if (!lex_force_int_range (lexer, "FIRSTCASE", 1, INT_MAX))
             goto error;
-          if (lex_integer (lexer) < 1)
-            {
-              msg (SE, _("Value of FIRSTCASE must be 1 or greater."));
-              goto error;
-            }
           data_parser_set_skip (parser, lex_integer (lexer) - 1);
           lex_get (lexer);
         }
       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"))
@@ -437,66 +390,51 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds)
             }
           else
             {
-              lex_error_expecting (lexer, "LINE", "VARIABLES", NULL_SENTINEL);
+              lex_error_expecting (lexer, "LINE", "VARIABLES");
               goto error;
             }
         }
       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 (lexer))
+          if (!lex_force_int_range (lexer, "FIXCASE", 1, INT_MAX))
             goto error;
-          if (lex_integer (lexer) < 1)
-            {
-              msg (SE, _("Value of FIXCASE must be at least 1."));
-              goto error;
-            }
           data_parser_set_records (parser, lex_integer (lexer));
           lex_get (lexer);
         }
       else if (lex_match_id (lexer, "IMPORTCASES"))
         {
+          int start_ofs = lex_ofs (lexer) - 1;
           lex_match (lexer, T_EQUALS);
           if (lex_match (lexer, T_ALL))
             {
-              data_parser_set_case_limit (parser, -1);
-              data_parser_set_case_percent (parser, 100);
+              /* Nothing to do. */
             }
           else if (lex_match_id (lexer, "FIRST"))
             {
               if (!lex_force_int (lexer))
                 goto error;
-              if (lex_integer (lexer) < 1)
-                {
-                  msg (SE, _("Value of FIRST must be at least 1."));
-                  goto error;
-                }
-              data_parser_set_case_limit (parser, lex_integer (lexer));
               lex_get (lexer);
             }
           else if (lex_match_id (lexer, "PERCENT"))
             {
               if (!lex_force_int (lexer))
                 goto error;
-              if (lex_integer (lexer) < 1 || lex_integer (lexer) > 100)
-                {
-                  msg (SE, _("Value of PERCENT must be between 1 and 100."));
-                  goto error;
-                }
-              data_parser_set_case_percent (parser, lex_integer (lexer));
               lex_get (lexer);
             }
+          lex_ofs_msg (lexer, SW, start_ofs, lex_ofs (lexer) - 1,
+                       _("Ignoring obsolete IMPORTCASES subcommand.  (N OF "
+                         "CASES or SAMPLE may be used to substitute.)"));
         }
       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);
 
@@ -504,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 = " ";
@@ -522,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);
 
@@ -533,120 +476,122 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds)
           if (settings_get_syntax () == COMPATIBLE
               && ss_length (lex_tokss (lexer)) != 1)
             {
-              msg (SE, _("In compatible syntax mode, the QUALIFIER string "
-                         "must contain exactly one character."));
+              lex_error (lexer, _("In compatible syntax mode, the QUALIFIER "
+                                  "string must contain exactly one character."));
               goto error;
             }
 
           data_parser_set_quotes (parser, lex_tokss (lexer));
           lex_get (lexer);
         }
-      else if (settings_get_syntax () == ENHANCED
-               && lex_match_id (lexer, "ESCAPE"))
-        data_parser_set_quote_escape (parser, true);
       else if (lex_match_id (lexer, "VARIABLES"))
         break;
       else
         {
-          lex_error_expecting (lexer, "VARIABLES", NULL_SENTINEL);
+          lex_error_expecting (lexer, "VARIABLES");
           goto error;
         }
     }
   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 (lexer))
+          if (!lex_force_int_range (lexer, NULL, record,
+                                    data_parser_get_records (parser)))
             goto error;
-          if (lex_integer (lexer) < record)
-            {
-              msg (SE, _("The record number specified, %ld, is at or "
-                         "before the previous record, %d.  Data "
-                         "fields must be listed in order of "
-                         "increasing record number."),
-                   lex_integer (lexer), record);
-              goto error;
-            }
-          if (lex_integer (lexer) > data_parser_get_records (parser))
-            {
-              msg (SE, _("The record number specified, %ld, exceeds "
-                         "the number of records per case specified "
-                         "on FIXCASE, %d."),
-                   lex_integer (lexer), data_parser_get_records (parser));
-              goto error;
-            }
           record = lex_integer (lexer);
           lex_get (lexer);
         }
 
-      if (!lex_force_id (lexer)
-          || !dict_id_is_valid (dict, lex_tokcstr (lexer), true))
+      int name_ofs = lex_ofs (lexer);
+      if (!lex_force_id (lexer))
         goto error;
       name = xstrdup (lex_tokcstr (lexer));
+      char *error = dict_id_is_valid__ (dict, name);
+      if (error)
+        {
+          lex_error (lexer, "%s", error);
+          free (error);
+         goto error;
+       }
       lex_get (lexer);
 
+      struct fmt_spec input, output;
+      int fc, lc;
       if (type == DP_DELIMITED)
         {
-          if (!parse_format_specifier (lexer, &input)
-              || !fmt_check_input (&input))
+          if (!parse_format_specifier (lexer, &input))
             goto error;
-
-          output = fmt_for_output_from_input (&input);
+          error = fmt_check_input__ (&input);
+          if (error)
+           {
+              lex_next_error (lexer, -1, -1, "%s", error);
+              free (error);
+             goto error;
+           }
+          output = fmt_for_output_from_input (&input,
+                                              settings_get_fmt_settings ());
         }
       else
         {
-          char fmt_type_name[FMT_TYPE_LEN_MAX + 1];
-          enum fmt_type fmt_type;
-          int w, d;
-
+          int start_ofs = lex_ofs (lexer);
           if (!parse_column_range (lexer, 0, &fc, &lc, NULL))
             goto error;
 
           /* Accept a format (e.g. F8.2) or just a type name (e.g. DOLLAR).  */
+          char fmt_type_name[FMT_TYPE_LEN_MAX + 1];
+          uint16_t w;
+          uint8_t d;
           if (!parse_abstract_format_specifier (lexer, fmt_type_name, &w, &d))
             goto error;
+
+          enum fmt_type fmt_type;
           if (!fmt_from_name (fmt_type_name, &fmt_type))
             {
-              msg (SE, _("Unknown format type `%s'."), fmt_type_name);
+              lex_next_error (lexer, -1, -1,
+                              _("Unknown format type `%s'."), fmt_type_name);
               goto error;
             }
+          int end_ofs = lex_ofs (lexer) - 1;
 
           /* Compose input format. */
-          input.type = fmt_type;
-          input.w = lc - fc + 1;
-          input.d = 0;
-          if (!fmt_check_input (&input))
-            goto error;
+          input = (struct fmt_spec) { .type = fmt_type, .w = lc - fc + 1 };
+          error = fmt_check_input__ (&input);
+          if (error)
+            {
+              lex_ofs_error (lexer, start_ofs, end_ofs, "%s", error);
+              free (error);
+              goto error;
+            }
 
           /* Compose output format. */
           if (w != 0)
             {
-              output.type = fmt_type;
-              output.w = w;
-              output.d = d;
-              if (!fmt_check_output (&output))
-                goto error;
+              output = (struct fmt_spec) { .type = fmt_type, .w = w, .d = d };
+              error = fmt_check_output__ (&output);
+              if (error)
+                {
+                  lex_ofs_error (lexer, start_ofs, end_ofs, "%s", error);
+                  free (error);
+                  goto error;
+                }
             }
           else
-            output = fmt_for_output_from_input (&input);
+            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)
         {
-          msg (SE, _("%s is a duplicate variable name."), name);
+          lex_ofs_error (lexer, name_ofs, name_ofs,
+                         _("%s is a duplicate variable name."), name);
           goto error;
         }
       var_set_both_formats (v, &output);
-
       if (type == DP_DELIMITED)
         data_parser_add_delimited_field (parser, &input,
                                          var_get_case_index (v),
@@ -659,26 +604,25 @@ 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);
+  data_parser_make_active_file (parser, ds, reader, dict, NULL, NULL);
   fh_unref (fh);
   free (encoding);
   return CMD_SUCCESS;
 
  error:
   data_parser_destroy (parser);
-  dict_destroy (dict);
+  dict_unref (dict);
   fh_unref (fh);
   free (name);
   free (encoding);
   return CMD_CASCADING_FAILURE;
 }
 
-
-static void 
+static void
 destroy_spreadsheet_read_info (struct spreadsheet_read_options *opts)
 {
   free (opts->cell_range);