PRINT: Improve error messages.
authorBen Pfaff <blp@cs.stanford.edu>
Tue, 13 Sep 2022 01:48:46 +0000 (18:48 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Tue, 13 Sep 2022 01:48:46 +0000 (18:48 -0700)
src/data/format.c
src/data/format.h
src/data/por-file-reader.c
src/data/variable.c
src/language/data-io/print.c
src/language/dictionary/numeric.c
src/language/expressions/evaluate.c
src/language/expressions/parse.c
src/language/stats/ctables.c
src/output/spv/spv.c
tests/language/data-io/print.at

index e999636f036d3c3b2a4ba17c3f62cd6b0c9a220d..5b0242ab473153920ee26d1056d3d9e390790a3c 100644 (file)
@@ -498,9 +498,11 @@ fmt_check_output (const struct fmt_spec *spec)
 
 /* Checks that FORMAT is appropriate for a variable of the given VAR_TYPE and
    returns NULL if so.  Otherwise returns a malloc()'d error message that the
-   caller must eventually free(). */
+   caller must eventually free().  VARNAME is optional and only used in the
+   error message.*/
 char *
-fmt_check_type_compat__ (const struct fmt_spec *format, enum val_type var_type)
+fmt_check_type_compat__ (const struct fmt_spec *format, const char *varname,
+                         enum val_type var_type)
 {
   assert (val_type_is_valid (var_type));
   if ((var_type == VAL_STRING) != (fmt_is_string (format->type) != 0))
@@ -508,21 +510,33 @@ fmt_check_type_compat__ (const struct fmt_spec *format, enum val_type var_type)
       char str[FMT_STRING_LEN_MAX + 1];
       fmt_to_string (format, str);
       if (var_type == VAL_STRING)
-        return xasprintf (_("String variables are not compatible with "
-                            "numeric format %s."), str);
+        {
+          if (varname)
+            return xasprintf (_("String variable %s is not compatible with "
+                                "numeric format %s."), varname, str);
+          else
+            return xasprintf (_("String variables are not compatible with "
+                                "numeric format %s."), str);
+        }
       else
-        return xasprintf (_("Numeric variables are not compatible with "
-                            "string format %s."), str);
+        {
+          if (varname)
+            return xasprintf (_("Numeric variable %s is not compatible with "
+                                "string format %s."), varname, str);
+          else
+            return xasprintf (_("Numeric variables are not compatible with "
+                                "string format %s."), str);
+        }
     }
   return NULL;
 }
 
-/* Returns FORMAT is appropriate for a variable of the given
-   VAR_TYPE and returns true if so, otherwise false. */
+/* Returns FORMAT is appropriate for a variable of the given VAR_TYPE and
+   returns true if so, otherwise false. */
 bool
 fmt_check_type_compat (const struct fmt_spec *format, enum val_type var_type)
 {
-  return error_to_bool (fmt_check_type_compat__ (format, var_type));
+  return error_to_bool (fmt_check_type_compat__ (format, NULL, var_type));
 }
 
 /* Checks that FORMAT is appropriate for a variable of the given WIDTH and
@@ -533,7 +547,8 @@ char *
 fmt_check_width_compat__ (const struct fmt_spec *format, const char *varname,
                           int width)
 {
-  char *error = fmt_check_type_compat__ (format, val_type_from_width (width));
+  char *error = fmt_check_type_compat__ (format, varname,
+                                         val_type_from_width (width));
   if (error)
     return error;
 
@@ -565,10 +580,9 @@ fmt_check_width_compat__ (const struct fmt_spec *format, const char *varname,
 /* Checks that FORMAT is appropriate for a variable of the given WIDTH and
    returns true if so, otherwise false. */
 bool
-fmt_check_width_compat (const struct fmt_spec *format, const char *varname,
-                        int width)
+fmt_check_width_compat (const struct fmt_spec *format, int width)
 {
-  return error_to_bool (fmt_check_width_compat__ (format, varname, width));
+  return error_to_bool (fmt_check_width_compat__ (format, NULL, width));
 }
 
 /* Returns the width corresponding to FORMAT.  The return value
@@ -1044,7 +1058,7 @@ fmt_from_u32 (uint32_t u32, int width, bool loose, struct fmt_spec *f)
   else if (!fmt_check_output (f))
     return false;
 
-  return fmt_check_width_compat (f, NULL, width);
+  return fmt_check_width_compat (f, width);
 }
 
 /* Returns true if TYPE may be used as an input format,
index 5b56e12201dc3ff2a07fb8481fe9b312da56dff9..db40e13dd9fdba28aa6ae291264da2534872b54c 100644 (file)
@@ -95,13 +95,13 @@ bool fmt_check (const struct fmt_spec *, enum fmt_use);
 bool fmt_check_input (const struct fmt_spec *);
 bool fmt_check_output (const struct fmt_spec *);
 bool fmt_check_type_compat (const struct fmt_spec *, enum val_type);
-bool fmt_check_width_compat (const struct fmt_spec *, const char *varname,
-                             int var_width);
+bool fmt_check_width_compat (const struct fmt_spec *, int var_width);
 
 char *fmt_check__ (const struct fmt_spec *, enum fmt_use);
 char *fmt_check_input__ (const struct fmt_spec *);
 char *fmt_check_output__ (const struct fmt_spec *);
-char *fmt_check_type_compat__ (const struct fmt_spec *, enum val_type);
+char *fmt_check_type_compat__ (const struct fmt_spec *, const char *varname,
+                               enum val_type);
 char *fmt_check_width_compat__ (const struct fmt_spec *, const char *varname,
                                 int var_width);
 
index 9bea44ffa47e7635f7444902cb256c7df53df6fb..125d7be2598a37bb516c80b35d3839a794c3811a 100644 (file)
@@ -633,8 +633,7 @@ convert_format (struct pfm_reader *r, const int portable_format[3],
       };
 
       if (fmt_check_output (&format)
-          && fmt_check_width_compat (&format, var_get_name (v),
-                                     var_get_width (v)))
+          && fmt_check_width_compat (&format, var_get_width (v)))
         return format;
 
       if (*report_error)
index 3c6bea271775d273034c637855c39fa5976869a2..87e7d07823f42271707000938d7501a24f562250 100644 (file)
@@ -634,7 +634,7 @@ var_set_print_format_quiet (struct variable *v, const struct fmt_spec *print)
 {
   if (!fmt_equal (&v->print, print))
     {
-      assert (fmt_check_width_compat (print, v->name, v->width));
+      assert (fmt_check_width_compat (print, v->width));
       v->print = *print;
     }
 }
@@ -667,7 +667,7 @@ var_set_write_format_quiet (struct variable *v, const struct fmt_spec *write)
 {
   if (!fmt_equal (&v->write, write))
     {
-      assert (fmt_check_width_compat (write, v->name, v->width));
+      assert (fmt_check_width_compat (write, v->width));
       v->write = *write;
     }
 }
index 91d2d9c180c85748b0b71889e6b468a7ac15a244..ba9d4cbd142b9bc1b60d84e5ca5179877a981ee8 100644 (file)
@@ -35,7 +35,6 @@
 #include "libpspp/assertion.h"
 #include "libpspp/compiler.h"
 #include "libpspp/i18n.h"
-#include "libpspp/ll.h"
 #include "libpspp/message.h"
 #include "libpspp/misc.h"
 #include "libpspp/pool.h"
@@ -62,10 +61,10 @@ enum field_type
 struct prt_out_spec
   {
     /* All fields. */
-    struct ll ll;               /* In struct print_trns `specs' list. */
     enum field_type type;      /* What type of field this is. */
     int record;                 /* 1-based record number. */
     int first_column;          /* 0-based first column. */
+    int start_ofs, end_ofs;
 
     /* PRT_VAR only. */
     const struct variable *var;        /* Associated variable. */
@@ -74,7 +73,7 @@ struct prt_out_spec
     bool sysmis_as_spaces;      /* Output SYSMIS as spaces? */
 
     /* PRT_LITERAL only. */
-    struct string string;       /* String to output. */
+    struct substring string;    /* String to output. */
     int width;                  /* Width of 'string', in display columns. */
   };
 
@@ -86,7 +85,8 @@ struct print_trns
     bool include_prefix;        /* Prefix lines with space? */
     const char *encoding;       /* Encoding to use for output. */
     struct dfm_writer *writer; /* Output file, NULL=listing file. */
-    struct ll_list specs;       /* List of struct prt_out_specs. */
+    struct prt_out_spec *specs;
+    size_t n_specs;
     size_t n_records;           /* Number of records to write. */
   };
 
@@ -99,13 +99,16 @@ enum which_formats
 static const struct trns_class print_binary_trns_class;
 static const struct trns_class print_text_trns_class;
 
-static int internal_cmd_print (struct lexer *, struct dataset *ds,
-                              enum which_formats, bool eject);
-static bool parse_specs (struct lexer *, struct pool *tmp_pool, struct print_trns *,
-                        struct dictionary *dict, enum which_formats);
+static int cmd_print__ (struct lexer *, struct dataset *,
+                        enum which_formats, bool eject);
+static bool parse_specs (struct lexer *, struct pool *tmp_pool,
+                         struct print_trns *, int records_ofs,
+                         struct dictionary *, enum which_formats);
 static void dump_table (struct print_trns *);
 
 static bool print_trns_free (void *trns_);
+
+static const struct prt_out_spec *find_binary_spec (const struct print_trns *);
 \f
 /* Basic parsing. */
 
@@ -113,46 +116,40 @@ static bool print_trns_free (void *trns_);
 int
 cmd_print (struct lexer *lexer, struct dataset *ds)
 {
-  return internal_cmd_print (lexer, ds, PRINT, false);
+  return cmd_print__ (lexer, ds, PRINT, false);
 }
 
 /* Parses PRINT EJECT command. */
 int
 cmd_print_eject (struct lexer *lexer, struct dataset *ds)
 {
-  return internal_cmd_print (lexer, ds, PRINT, true);
+  return cmd_print__ (lexer, ds, PRINT, true);
 }
 
 /* Parses WRITE command. */
 int
 cmd_write (struct lexer *lexer, struct dataset *ds)
 {
-  return internal_cmd_print (lexer, ds, WRITE, false);
+  return cmd_print__ (lexer, ds, WRITE, false);
 }
 
 /* Parses the output commands. */
 static int
-internal_cmd_print (struct lexer *lexer, struct dataset *ds,
-                   enum which_formats which_formats, bool eject)
+cmd_print__ (struct lexer *lexer, struct dataset *ds,
+             enum which_formats which_formats, bool eject)
 {
   bool print_table = false;
-  const struct prt_out_spec *spec;
-  struct print_trns *trns;
   struct file_handle *fh = NULL;
   char *encoding = NULL;
-  struct pool *tmp_pool;
-  bool binary;
 
   /* Fill in prt to facilitate error-handling. */
-  trns = pool_create_container (struct print_trns, pool);
-  trns->eject = eject;
-  trns->writer = NULL;
-  trns->n_records = 0;
-  ll_init (&trns->specs);
-
-  tmp_pool = pool_create_subpool (trns->pool);
+  struct pool *pool = pool_create ();
+  struct print_trns *trns = pool_alloc (pool, sizeof *trns);
+  *trns = (struct print_trns) { .pool = pool, .eject = eject };
+  struct pool *tmp_pool = pool_create_subpool (trns->pool);
 
   /* Parse the command options. */
+  int records_ofs = 0;
   while (lex_token (lexer) != T_SLASH && lex_token (lexer) != T_ENDCMD)
     {
       if (lex_match_id (lexer, "OUTFILE"))
@@ -181,6 +178,7 @@ internal_cmd_print (struct lexer *lexer, struct dataset *ds,
          if (!lex_force_int_range (lexer, "RECORDS", 0, INT_MAX))
            goto error;
          trns->n_records = lex_integer (lexer);
+          records_ofs = lex_ofs (lexer);
          lex_get (lexer);
          lex_match (lexer, T_RPAREN);
        }
@@ -201,7 +199,8 @@ internal_cmd_print (struct lexer *lexer, struct dataset *ds,
   trns->include_prefix = which_formats == PRINT && fh != NULL;
 
   /* Parse variables and strings. */
-  if (!parse_specs (lexer, tmp_pool, trns, dataset_dict (ds), which_formats))
+  if (!parse_specs (lexer, tmp_pool, trns, records_ofs,
+                    dataset_dict (ds), which_formats))
     goto error;
 
   /* Are there any binary formats?
@@ -209,19 +208,12 @@ internal_cmd_print (struct lexer *lexer, struct dataset *ds,
      There are real difficulties figuring out what to do when both binary
      formats and nontrivial encodings enter the picture.  So when binary
      formats are present we fall back to much simpler handling. */
-  binary = false;
-  ll_for_each (spec, struct prt_out_spec, ll, &trns->specs)
-    {
-      if (spec->type == PRT_VAR
-          && fmt_get_category (spec->format.type) == FMT_CAT_BINARY)
-        {
-          binary = true;
-          break;
-        }
-    }
-  if (binary && fh == NULL)
+  const struct prt_out_spec *binary_spec = find_binary_spec (trns);
+  if (binary_spec && !fh)
     {
-      msg (SE, _("%s is required when binary formats are specified."), "OUTFILE");
+      lex_ofs_error (lexer, binary_spec->start_ofs, binary_spec->end_ofs,
+                     _("%s is required when binary formats are specified."),
+                     "OUTFILE");
       goto error;
     }
 
@@ -243,7 +235,7 @@ internal_cmd_print (struct lexer *lexer, struct dataset *ds,
     dump_table (trns);
 
   /* Put the transformation in the queue. */
-  add_transformation (ds, (binary
+  add_transformation (ds, (binary_spec
                            ? &print_binary_trns_class
                            : &print_text_trns_class), trns);
 
@@ -259,9 +251,11 @@ internal_cmd_print (struct lexer *lexer, struct dataset *ds,
 }
 \f
 static bool parse_string_argument (struct lexer *, struct print_trns *,
+                                   size_t *allocated_specs,
                                    int record, int *column);
 static bool parse_variable_argument (struct lexer *, const struct dictionary *,
                                     struct print_trns *,
+                                     size_t *allocated_specs,
                                      struct pool *tmp_pool,
                                      int *record, int *column,
                                      enum which_formats);
@@ -270,8 +264,8 @@ static bool parse_variable_argument (struct lexer *, const struct dictionary *,
    PRINT, PRINT EJECT, or WRITE command into the prt structure.
    Returns success. */
 static bool
-parse_specs (struct lexer *lexer, struct pool *tmp_pool, struct print_trns *trns,
-            struct dictionary *dict,
+parse_specs (struct lexer *lexer, struct pool *tmp_pool,
+             struct print_trns *trns, int records_ofs, struct dictionary *dict,
              enum which_formats which_formats)
 {
   int record = 0;
@@ -283,18 +277,18 @@ parse_specs (struct lexer *lexer, struct pool *tmp_pool, struct print_trns *trns
       return true;
     }
 
+  size_t allocated_specs = 0;
   while (lex_token (lexer) != T_ENDCMD)
     {
-      bool ok;
-
       if (!parse_record_placement (lexer, &record, &column))
         return false;
 
-      if (lex_is_string (lexer))
-       ok = parse_string_argument (lexer, trns, record, &column);
-      else
-       ok = parse_variable_argument (lexer, dict, trns, tmp_pool, &record,
-                                      &column, which_formats);
+      bool ok = (lex_is_string (lexer)
+                 ? parse_string_argument (lexer, trns, &allocated_specs,
+                                          record, &column)
+                 : parse_variable_argument (lexer, dict, trns, &allocated_specs,
+                                            tmp_pool, &record, &column,
+                                            which_formats));
       if (!ok)
        return 0;
 
@@ -302,24 +296,37 @@ parse_specs (struct lexer *lexer, struct pool *tmp_pool, struct print_trns *trns
     }
 
   if (trns->n_records != 0 && trns->n_records != record)
-    msg (SW, _("Output calls for %d records but %zu specified on RECORDS "
-               "subcommand."),
-         record, trns->n_records);
+    lex_ofs_error (lexer, records_ofs, records_ofs,
+                   _("Output calls for %d records but %zu specified on RECORDS "
+                     "subcommand."),
+                   record, trns->n_records);
   trns->n_records = record;
 
   return true;
 }
 
+static struct prt_out_spec *
+add_spec (struct print_trns *trns, size_t *allocated_specs)
+{
+  if (trns->n_specs >= *allocated_specs)
+    trns->specs = pool_2nrealloc (trns->pool, trns->specs, allocated_specs,
+                                  sizeof *trns->specs);
+  return &trns->specs[trns->n_specs++];
+}
+
 /* Parses a string argument to the PRINT commands.  Returns success. */
 static bool
-parse_string_argument (struct lexer *lexer, struct print_trns *trns, int record, int *column)
+parse_string_argument (struct lexer *lexer, struct print_trns *trns,
+                       size_t *allocated_specs, int record, int *column)
 {
-  struct prt_out_spec *spec = pool_alloc (trns->pool, sizeof *spec);
-  spec->type = PRT_LITERAL;
-  spec->record = record;
-  spec->first_column = *column;
-  ds_init_substring (&spec->string, lex_tokss (lexer));
-  ds_register_pool (&spec->string, trns->pool);
+  struct prt_out_spec *spec = add_spec (trns, allocated_specs);
+  *spec = (struct prt_out_spec) {
+    .type = PRT_LITERAL,
+    .record = record,
+    .first_column = *column,
+    .string = ss_clone_pool (lex_tokss (lexer), trns->pool),
+    .start_ofs = lex_ofs (lexer),
+  };
   lex_get (lexer);
 
   /* Parse the included column range. */
@@ -334,15 +341,20 @@ parse_string_argument (struct lexer *lexer, struct print_trns *trns, int record,
 
       spec->first_column = first_column;
       if (range_specified)
-        ds_set_length (&spec->string, last_column - first_column + 1, ' ');
+        {
+          struct string s;
+          ds_init_substring (&s, spec->string);
+          ds_set_length (&s, last_column - first_column + 1, ' ');
+          spec->string = ss_clone_pool (s.ss, trns->pool);
+          ds_destroy (&s);
+        }
     }
+  spec->end_ofs = lex_ofs (lexer) - 1;
 
-  spec->width = u8_strwidth (CHAR_CAST (const uint8_t *,
-                                        ds_cstr (&spec->string)),
-                             UTF8);
+  spec->width = u8_width (CHAR_CAST (const uint8_t *, spec->string.string),
+                          spec->string.length, UTF8);
   *column = spec->first_column + spec->width;
 
-  ll_push_tail (&trns->specs, &spec->ll);
   return true;
 }
 
@@ -351,8 +363,8 @@ parse_string_argument (struct lexer *lexer, struct print_trns *trns, int record,
    Returns success. */
 static bool
 parse_variable_argument (struct lexer *lexer, const struct dictionary *dict,
-                        struct print_trns *trns, struct pool *tmp_pool,
-                         int *record, int *column,
+                        struct print_trns *trns, size_t *allocated_specs,
+                         struct pool *tmp_pool, int *record, int *column,
                          enum which_formats which_formats)
 {
   const struct variable **vars;
@@ -403,7 +415,7 @@ parse_variable_argument (struct lexer *lexer, const struct dictionary *dict,
             return false;
           }
 
-        struct prt_out_spec *spec = pool_alloc (trns->pool, sizeof *spec);
+        struct prt_out_spec *spec = add_spec (trns, allocated_specs);
         *spec = (struct prt_out_spec) {
           .type = PRT_VAR,
           .record = *record,
@@ -421,7 +433,6 @@ parse_variable_argument (struct lexer *lexer, const struct dictionary *dict,
                                && (fmt_get_category (f->type)
                                    != FMT_CAT_BINARY)),
         };
-        ll_push_tail (&trns->specs, &spec->ll);
 
         *column += f->w + add_space;
       }
@@ -443,9 +454,9 @@ dump_table (struct print_trns *trns)
   struct pivot_dimension *variables = pivot_dimension_create (
     table, PIVOT_AXIS_ROW, N_("Variable"));
 
-  struct prt_out_spec *spec;
-  ll_for_each (spec, struct prt_out_spec, ll, &trns->specs)
+  for (size_t i = 0; i < trns->n_specs; i++)
     {
+      const struct prt_out_spec *spec = &trns->specs[i];
       if (spec->type != PRT_VAR)
         continue;
 
@@ -471,6 +482,19 @@ dump_table (struct print_trns *trns)
 
   pivot_table_submit (table);
 }
+
+static const struct prt_out_spec *
+find_binary_spec (const struct print_trns *trns)
+{
+  for (size_t i = 0; i < trns->n_specs; i++)
+    {
+      const struct prt_out_spec *spec = &trns->specs[i];
+      if (spec->type == PRT_VAR
+          && fmt_get_category (spec->format.type) == FMT_CAT_BINARY)
+        return spec;
+    }
+  return NULL;
+}
 \f
 /* Transformation, for all-text output. */
 
@@ -484,15 +508,15 @@ print_text_trns_proc (void *trns_, struct ccase **c,
                       casenumber case_num UNUSED)
 {
   struct print_trns *trns = trns_;
-  struct prt_out_spec *spec;
   struct u8_line line;
 
   bool eject = trns->eject;
   int record = 1;
 
   u8_line_init (&line);
-  ll_for_each (spec, struct prt_out_spec, ll, &trns->specs)
+  for (size_t i = 0; i < trns->n_specs; i++)
     {
+      const struct prt_out_spec *spec = &trns->specs[i];
       int x0 = spec->first_column;
 
       print_text_flush_records (trns, &line, spec->record, &eject, &record);
@@ -530,10 +554,9 @@ print_text_trns_proc (void *trns_, struct ccase **c,
         }
       else
         {
-          const struct string *s = &spec->string;
+          const struct substring *s = &spec->string;
 
-          u8_line_put (&line, x0, x0 + spec->width,
-                       ds_data (s), ds_length (s));
+          u8_line_put (&line, x0, x0 + spec->width, s->string, s->length);
         }
     }
   print_text_flush_records (trns, &line, trns->n_records + 1,
@@ -600,13 +623,12 @@ print_binary_trns_proc (void *trns_, struct ccase **c,
   bool eject = trns->eject;
   char encoded_space = recode_byte (trns->encoding, C_ENCODING, ' ');
   int record = 1;
-  struct prt_out_spec *spec;
-  struct string line;
+  struct string line = DS_EMPTY_INITIALIZER;
 
-  ds_init_empty (&line);
   ds_put_byte (&line, ' ');
-  ll_for_each (spec, struct prt_out_spec, ll, &trns->specs)
+  for (size_t i = 0; i < trns->n_specs; i++)
     {
+      const struct prt_out_spec *spec = &trns->specs[i];
       print_binary_flush_records (trns, &line, spec->record, &eject, &record);
 
       ds_set_length (&line, spec->first_column, encoded_space);
@@ -624,10 +646,10 @@ print_binary_trns_proc (void *trns_, struct ccase **c,
         }
       else
         {
-          ds_put_substring (&line, ds_ss (&spec->string));
+          ds_put_substring (&line, spec->string);
           if (0 != strcmp (trns->encoding, UTF8))
             {
-              size_t length = ds_length (&spec->string);
+              size_t length = spec->string.length;
               char *data = ss_data (ds_tail (&line, length));
              char *s = recode_string (trns->encoding, UTF8, data, length);
              memcpy (data, s, length);
index 07c4193a53a9767dbcd726d00e1746fded31997f..18728014f331f5ba68eabbafafe23467b6f069f1 100644 (file)
@@ -138,7 +138,7 @@ cmd_string (struct lexer *lexer, struct dataset *ds)
           || !parse_format_specifier (lexer, &f))
        goto fail;
 
-      char *error = fmt_check_type_compat__ (&f, VAL_STRING);
+      char *error = fmt_check_type_compat__ (&f, NULL, VAL_STRING);
       if (!error)
         error = fmt_check_output__ (&f);
       if (error)
index 18e794f271701728b0720d38249b69a360f410ca..edc1b4e7ae838a760f2d60125e18f48fcfd52d0c 100644 (file)
@@ -231,7 +231,7 @@ cmd_debug_evaluate (struct lexer *lexer, struct dataset *dsother UNUSED)
             goto done;
           char *error = fmt_check_output__ (&format);
           if (!error)
-            error = fmt_check_type_compat__ (&format, VAL_NUMERIC);
+            error = fmt_check_type_compat__ (&format, NULL, VAL_NUMERIC);
           if (error)
             {
               lex_next_error (lexer, -1, -1, "%s", error);
index 7accce15f19409c3b54550eca1c792dfe9558df7..7b723fb18a7e43fdb99efe2ab9e31660ba63fd59 100644 (file)
@@ -1299,7 +1299,7 @@ no_match (struct expression *e, const char *func_name, struct expr_node *node,
                 char *error = fmt_check__ (f, (ops->args[i] == OP_ni_format
                                                ? FMT_FOR_INPUT : FMT_FOR_OUTPUT));
                 if (!error)
-                  error = fmt_check_type_compat__ (f, VAL_NUMERIC);
+                  error = fmt_check_type_compat__ (f, NULL, VAL_NUMERIC);
                 if (error)
                   {
                     msg_at (SN, expr_location (e, node->args[i]), "%s", error);
index 325eb242de2758b0903bc4a1e8d0478aff732c90..dd0978731bbce5da5c4910ed7e837d9d18334437 100644 (file)
@@ -1293,7 +1293,7 @@ parse_ctables_format_specifier (struct lexer *lexer, struct fmt_spec *format,
 
       char *error = fmt_check_output__ (format);
       if (!error)
-        error = fmt_check_type_compat__ (format, VAL_NUMERIC);
+        error = fmt_check_type_compat__ (format, NULL, VAL_NUMERIC);
       if (error)
         {
           lex_next_error (lexer, -1, -1, "%s", error);
index 4e59fa3a232db181eae59ee18b46e72ddaa2ea42..38abf9775c87cb93ab5e9d385712c8821612d656 100644 (file)
@@ -887,7 +887,7 @@ spv_decode_fmt_spec (uint32_t u32, struct fmt_spec *out)
   if (ok)
     {
       fmt_fix_output (out);
-      ok = fmt_check_width_compat (out, NULL, 0);
+      ok = fmt_check_width_compat (out, 0);
     }
 
   if (!ok)
index 465240cc22837f07ca2044532a5787ad8f057f4c..75ac1d03eb8339a59046410524071dedcaaf5531 100644 (file)
@@ -339,3 +339,60 @@ AT_CHECK([tr '\r' R < crlf.txt], [0], [dnl
  5 R
 ])
 AT_CLEANUP
+
+AT_SETUP([PRINT syntax errors])
+AT_DATA([print.sps], [dnl
+DATA LIST LIST NOTABLE /x.
+PRINT OUTFILE=**.
+PRINT ENCODING=**.
+PRINT RECORDS=-1.
+PRINT **.
+PRINT/ **.
+PRINT/'string' 0.
+PRINT/'string' 5-3.
+PRINT/y.
+PRINT/x 0.
+PRINT/x (A8).
+])
+AT_CHECK([pspp -O format=csv print.sps], [1], [dnl
+"print.sps:2.15-2.16: error: PRINT: Syntax error expecting a file name or handle name.
+    2 | PRINT OUTFILE=**.
+      |               ^~"
+
+"print.sps:3.16-3.17: error: PRINT: Syntax error expecting string.
+    3 | PRINT ENCODING=**.
+      |                ^~"
+
+"print.sps:4.15-4.16: error: PRINT: Syntax error expecting non-negative integer for RECORDS.
+    4 | PRINT RECORDS=-1.
+      |               ^~"
+
+"print.sps:5.7-5.8: error: PRINT: Syntax error expecting OUTFILE, ENCODING, RECORDS, TABLE, or NOTABLE.
+    5 | PRINT **.
+      |       ^~"
+
+"print.sps:6.8-6.9: error: PRINT: Syntax error expecting variable name.
+    6 | PRINT/ **.
+      |        ^~"
+
+"print.sps:7.16: error: PRINT: Column positions for fields must be positive.
+    7 | PRINT/'string' 0.
+      |                ^"
+
+"print.sps:8.16-8.18: error: PRINT: The ending column for a field must be greater than the starting column.
+    8 | PRINT/'string' 5-3.
+      |                ^~~"
+
+"print.sps:9.7: error: PRINT: y is not a variable name.
+    9 | PRINT/y.
+      |       ^"
+
+"print.sps:10.9: error: PRINT: Column positions for fields must be positive.
+   10 | PRINT/x 0.
+      |         ^"
+
+"print.sps:11.9-11.12: error: PRINT: Numeric variable x is not compatible with string format A8.
+   11 | PRINT/x (A8).
+      |         ^~~~"
+])
+AT_CLEANUP