DATA LIST: Fix crash with a bare / at the end of the command.
authorBen Pfaff <blp@cs.stanford.edu>
Tue, 7 Mar 2023 01:00:13 +0000 (17:00 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Tue, 7 Mar 2023 01:00:13 +0000 (17:00 -0800)
Embarrassingly, this was in an example from the manual.

Reported by knassen@chartermi.net.

doc/data-io.texi
src/language/commands/data-list.c
tests/language/commands/data-list.at

index 9f5b8dfbeec3303c19ffe29b89249d96e8c2a67f..a325dde38588859791cb1422ebf3b627cee7e041 100644 (file)
@@ -394,6 +394,7 @@ applies to later FORTRAN and columnar specifiers.
 
 @enumerate
 @item
+@c Update the corresponding test in tests/language/commands/data-list.at if you change this.
 @example
 DATA LIST TABLE /NAME 1-10 (A) INFO1 TO INFO3 12-17 (1).
 
@@ -435,8 +436,9 @@ The @code{TABLE} keyword causes @pspp{} to print out a table
 describing the four variables defined.
 
 @item
+@c Update the corresponding test in tests/language/commands/data-list.at if you change this.
 @example
-DAT LIS FIL="survey.dat"
+DATA LIST FILE="survey.dat"
         /ID 1-5 NAME 7-36 (A) SURNAME 38-67 (A) MINITIAL 69 (A)
         /Q01 TO Q50 7-56
         /.
@@ -470,9 +472,6 @@ record.
 Cases are separated by a blank record.
 
 Data is read from file @file{survey.dat} in the current directory.
-
-This example shows keywords abbreviated to their first 3 letters.
-
 @end enumerate
 
 @node DATA LIST FREE
index 35f153e4f8c7cc17dc9bd67489c530640c0f765f..d6a5d0fd55278ec9086ea47b7f482e05e830d7a0 100644 (file)
@@ -341,12 +341,36 @@ parse_fixed (struct lexer *lexer, struct dictionary *dict,
   int record = 0;
   int column = 1;
 
-  do
+  int start = lex_ofs (lexer);
+  while (lex_token (lexer) != T_ENDCMD)
     {
-      /* Parse everything. */
-      int records_start = lex_ofs (lexer);
-      if (!parse_record_placement (lexer, &record, &column))
-        return false;
+      if (lex_match (lexer, T_SLASH))
+        {
+          int records_start = lex_ofs (lexer) - 1;
+          if (lex_is_number (lexer))
+            {
+              if (!lex_force_int_range (lexer, NULL, record + 1, INT_MAX))
+                return false;
+              record = lex_integer (lexer);
+              lex_get (lexer);
+            }
+          else
+            record++;
+          column = 1;
+
+          if (max_records && record > max_records)
+            {
+              lex_ofs_error (lexer, records_start, lex_ofs (lexer) - 1,
+                             _("Cannot advance to record %d when "
+                               "RECORDS=%d is specified."),
+                             record, data_parser_get_records (parser));
+              return false;
+            }
+          if (record > data_parser_get_records (parser))
+            data_parser_set_records (parser, record);
+
+          continue;
+        }
 
       int vars_start = lex_ofs (lexer);
       char **names;
@@ -411,7 +435,7 @@ parse_fixed (struct lexer *lexer, struct dictionary *dict,
 
             if (max_records && record > max_records)
               {
-                lex_ofs_error (lexer, records_start, vars_end,
+                lex_ofs_error (lexer, vars_start, placements_end,
                                _("Cannot place variable %s on record %d when "
                                  "RECORDS=%d is specified."),
                                var_get_name (v), record,
@@ -427,7 +451,14 @@ parse_fixed (struct lexer *lexer, struct dictionary *dict,
           }
       assert (name_idx == n_names);
     }
-  while (lex_token (lexer) != T_ENDCMD);
+
+  if (!data_parser_any_fields (parser))
+    {
+      lex_ofs_error (lexer, start, lex_ofs (lexer) - 1,
+                     _("No fields were specified.  "
+                       "At least one is required."));
+      return false;
+    }
 
   return true;
 }
index 165b4df722ce413aeafcc7738d05e1ac645de4a0..052481f1c5fd0b63bf1f85b3cdaa62901d0f149a 100644 (file)
@@ -446,7 +446,9 @@ INPUT PROGRAM.
 DATA LIST FIXED/y 2 (a).
 DATA LIST FIXED/y 3-4 (a).
 END INPUT PROGRAM.
-DATA LIST FIXED RECORDS=1/2 x 1-2.
+DATA LIST FIXED RECORDS=1/x y(F2/F3).
+DATA LIST FIXED RECORDS=1//.
+DATA LIST FIXED RECORDS=1/.
 ])
 
 AT_CHECK([pspp --testing-mode -O format=csv insert.sps], [1], [dnl
@@ -574,9 +576,120 @@ y,1,2-2,A1
    39 | DATA LIST FIXED/y 3-4 (a).
       |                 ^~~~~~~~~"
 
-"data-list.sps:41.26-41.29: error: DATA LIST: Cannot place variable x on record 2 when RECORDS=1 is specified.
-   41 | DATA LIST FIXED RECORDS=1/2 x 1-2.
-      |                          ^~~~"
+"data-list.sps:41.27-41.36: error: DATA LIST: Cannot place variable y on record 2 when RECORDS=1 is specified.
+   41 | DATA LIST FIXED RECORDS=1/x y(F2/F3).
+      |                           ^~~~~~~~~~"
+
+"data-list.sps:42.27: error: DATA LIST: Cannot advance to record 2 when RECORDS=1 is specified.
+   42 | DATA LIST FIXED RECORDS=1//.
+      |                           ^"
+
+"data-list.sps:43.26: error: DATA LIST: No fields were specified.  At least one is required.
+   43 | DATA LIST FIXED RECORDS=1/.
+      |                          ^"
+])
+AT_CLEANUP
+
+AT_SETUP([DATA LIST FIXED manual example 1])
+AT_DATA([data-list.sps], [dnl
+DATA LIST TABLE /NAME 1-10 (A) INFO1 TO INFO3 12-17 (1).
+
+BEGIN DATA.
+John Smith 102311
+Bob Arnold 122015
+Bill Yates  918 6
+END DATA.
+
+LIST.
+])
+AT_CHECK([pspp data-list.sps -O box=unicode], [0], [dnl
+  Reading 1 record from INLINE.
+╭────────┬──────┬───────┬──────╮
+│Variable│Record│Columns│Format│
+├────────┼──────┼───────┼──────┤
+│NAME    │     1│1-10   │A10   │
+│INFO1   │     1│12-13  │F2.1  │
+│INFO2   │     1│14-15  │F2.1  │
+│INFO3   │     1│16-17  │F2.1  │
+╰────────┴──────┴───────┴──────╯
+
+           Data List
+╭──────────┬─────┬─────┬─────╮
+│   NAME   │INFO1│INFO2│INFO3│
+├──────────┼─────┼─────┼─────┤
+│John Smith│  1.0│  2.3│  1.1│
+│Bob Arnold│  1.2│  2.0│  1.5│
+│Bill Yates│   .9│  1.8│   .6│
+╰──────────┴─────┴─────┴─────╯
 ])
+AT_CLEANUP
 
+AT_SETUP([DATA LIST FIXED manual example 2])
+AT_DATA([data-list.sps], [dnl
+DATA LIST
+        /ID 1-5 NAME 7-36 (A) SURNAME 38-67 (A) MINITIAL 69 (A)
+        /Q01 TO Q50 7-56
+        /.
+])
+AT_CHECK([pspp data-list.sps -O box=unicode], [0], [dnl
+ Reading 3 records from INLINE.
+╭────────┬──────┬───────┬──────╮
+│Variable│Record│Columns│Format│
+├────────┼──────┼───────┼──────┤
+│ID      │     1│1-5    │F5.0  │
+│NAME    │     1│7-36   │A30   │
+│SURNAME │     1│38-67  │A30   │
+│MINITIAL│     1│69-69  │A1    │
+│Q01     │     2│7-7    │F1.0  │
+│Q02     │     2│8-8    │F1.0  │
+│Q03     │     2│9-9    │F1.0  │
+│Q04     │     2│10-10  │F1.0  │
+│Q05     │     2│11-11  │F1.0  │
+│Q06     │     2│12-12  │F1.0  │
+│Q07     │     2│13-13  │F1.0  │
+│Q08     │     2│14-14  │F1.0  │
+│Q09     │     2│15-15  │F1.0  │
+│Q10     │     2│16-16  │F1.0  │
+│Q11     │     2│17-17  │F1.0  │
+│Q12     │     2│18-18  │F1.0  │
+│Q13     │     2│19-19  │F1.0  │
+│Q14     │     2│20-20  │F1.0  │
+│Q15     │     2│21-21  │F1.0  │
+│Q16     │     2│22-22  │F1.0  │
+│Q17     │     2│23-23  │F1.0  │
+│Q18     │     2│24-24  │F1.0  │
+│Q19     │     2│25-25  │F1.0  │
+│Q20     │     2│26-26  │F1.0  │
+│Q21     │     2│27-27  │F1.0  │
+│Q22     │     2│28-28  │F1.0  │
+│Q23     │     2│29-29  │F1.0  │
+│Q24     │     2│30-30  │F1.0  │
+│Q25     │     2│31-31  │F1.0  │
+│Q26     │     2│32-32  │F1.0  │
+│Q27     │     2│33-33  │F1.0  │
+│Q28     │     2│34-34  │F1.0  │
+│Q29     │     2│35-35  │F1.0  │
+│Q30     │     2│36-36  │F1.0  │
+│Q31     │     2│37-37  │F1.0  │
+│Q32     │     2│38-38  │F1.0  │
+│Q33     │     2│39-39  │F1.0  │
+│Q34     │     2│40-40  │F1.0  │
+│Q35     │     2│41-41  │F1.0  │
+│Q36     │     2│42-42  │F1.0  │
+│Q37     │     2│43-43  │F1.0  │
+│Q38     │     2│44-44  │F1.0  │
+│Q39     │     2│45-45  │F1.0  │
+│Q40     │     2│46-46  │F1.0  │
+│Q41     │     2│47-47  │F1.0  │
+│Q42     │     2│48-48  │F1.0  │
+│Q43     │     2│49-49  │F1.0  │
+│Q44     │     2│50-50  │F1.0  │
+│Q45     │     2│51-51  │F1.0  │
+│Q46     │     2│52-52  │F1.0  │
+│Q47     │     2│53-53  │F1.0  │
+│Q48     │     2│54-54  │F1.0  │
+│Q49     │     2│55-55  │F1.0  │
+│Q50     │     2│56-56  │F1.0  │
+╰────────┴──────┴───────┴──────╯
+])
 AT_CLEANUP