Implemented long variable names a la spss V12.
[pspp-builds.git] / src / file-type.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18    02111-1307, USA. */
19
20 #include <config.h>
21 #include <stdlib.h>
22 #include "alloc.h"
23 #include "case.h"
24 #include "command.h"
25 #include "data-in.h"
26 #include "dfm-read.h"
27 #include "dictionary.h"
28 #include "error.h"
29 #include "file-handle.h"
30 #include "format.h"
31 #include "lexer.h"
32 #include "str.h"
33 #include "var.h"
34 #include "vfm.h"
35
36 /* Defines the three types of complex files read by FILE TYPE. */
37 enum
38   {
39     FTY_MIXED,
40     FTY_GROUPED,
41     FTY_NESTED
42   };
43
44 /* Limited variable column specifications. */
45 struct col_spec
46   {
47    char name[SHORT_NAME_LEN + 1];       /* Variable name. */
48     int fc, nc;                 /* First column (1-based), # of columns. */
49     int fmt;                    /* Format type. */
50     struct variable *v;         /* Variable. */
51   };
52
53 /* RCT_* record type constants. */
54 enum
55   {
56     RCT_OTHER = 001,            /* 1=OTHER. */
57     RCT_SKIP = 002,             /* 1=SKIP. */
58     RCT_DUPLICATE = 004,        /* DUPLICATE: 0=NOWARN, 1=WARN. */
59     RCT_MISSING = 010,          /* MISSING: 0=NOWARN, 1=WARN. */
60     RCT_SPREAD = 020            /* SPREAD: 0=NO, 1=YES. */
61   };
62
63 /* Represents a RECORD TYPE command. */
64 struct record_type
65   {
66     struct record_type *next;
67     unsigned flags;             /* RCT_* constants. */
68     union value *v;             /* Vector of values for this record type. */
69     int nv;                     /* Length of vector V. */
70     struct col_spec case_sbc;   /* CASE subcommand. */
71     int ft, lt;                 /* First, last transformation index. */
72   };                            /* record_type */
73
74 /* Represents a FILE TYPE input program.  Does not contain a
75    trns_header because it's never submitted as a transformation. */
76 struct file_type_pgm
77   {
78     int type;                   /* One of the FTY_* constants. */
79     struct dfm_reader *reader;  /* Data file to read. */
80     struct col_spec record;     /* RECORD subcommand. */
81     struct col_spec case_sbc;   /* CASE subcommand. */
82     int wild;                   /* 0=NOWARN, 1=WARN. */
83     int duplicate;              /* 0=NOWARN, 1=WARN. */
84     int missing;                /* 0=NOWARN, 1=WARN, 2=CASE. */
85     int ordered;                /* 0=NO, 1=YES. */
86     int had_rec_type;           /* 1=Had a RECORD TYPE command.
87                                    RECORD TYPE must precede the first
88                                    DATA LIST. */
89     struct record_type *recs_head;      /* List of record types. */
90     struct record_type *recs_tail;      /* Last in list of record types. */
91     size_t case_size;           /* Case size in bytes. */
92   };
93
94 static int parse_col_spec (struct col_spec *, const char *);
95 static void create_col_var (struct col_spec *c);
96
97 /* Parses FILE TYPE command. */
98 int
99 cmd_file_type (void)
100 {
101   static struct file_type_pgm *fty;     /* FIXME: static? WTF? */
102   struct file_handle *fh = NULL;
103
104   /* Initialize. */
105   discard_variables ();
106
107   fty = xmalloc (sizeof *fty);
108   fty->reader = NULL;
109   fty->record.name[0] = 0;
110   fty->case_sbc.name[0] = 0;
111   fty->wild = fty->duplicate = fty->missing = fty->ordered = 0;
112   fty->had_rec_type = 0;
113   fty->recs_head = fty->recs_tail = NULL;
114
115   if (lex_match_id ("MIXED"))
116     fty->type = FTY_MIXED;
117   else if (lex_match_id ("GROUPED"))
118     {
119       fty->type = FTY_GROUPED;
120       fty->wild = 1;
121       fty->duplicate = 1;
122       fty->missing = 1;
123       fty->ordered = 1;
124     }
125   else if (lex_match_id ("NESTED"))
126     fty->type = FTY_NESTED;
127   else
128     {
129       msg (SE, _("MIXED, GROUPED, or NESTED expected."));
130       goto error;
131     }
132
133   while (token != '.')
134     {
135       if (lex_match_id ("FILE"))
136         {
137           lex_match ('=');
138           fh = fh_parse ();
139           if (fh == NULL)
140             goto error;
141         }
142       else if (lex_match_id ("RECORD"))
143         {
144           lex_match ('=');
145           if (!parse_col_spec (&fty->record, "####RECD"))
146             goto error;
147         }
148       else if (lex_match_id ("CASE"))
149         {
150           if (fty->type == FTY_MIXED)
151             {
152               msg (SE, _("The CASE subcommand is not valid on FILE TYPE "
153                          "MIXED."));
154               goto error;
155             }
156           
157           lex_match ('=');
158           if (!parse_col_spec (&fty->case_sbc, "####CASE"))
159             goto error;
160         }
161       else if (lex_match_id ("WILD"))
162         {
163           lex_match ('=');
164           if (lex_match_id ("WARN"))
165             fty->wild = 1;
166           else if (lex_match_id ("NOWARN"))
167             fty->wild = 0;
168           else
169             {
170               msg (SE, _("WARN or NOWARN expected after WILD."));
171               goto error;
172             }
173         }
174       else if (lex_match_id ("DUPLICATE"))
175         {
176           if (fty->type == FTY_MIXED)
177             {
178               msg (SE, _("The DUPLICATE subcommand is not valid on "
179                          "FILE TYPE MIXED."));
180               goto error;
181             }
182
183           lex_match ('=');
184           if (lex_match_id ("WARN"))
185             fty->duplicate = 1;
186           else if (lex_match_id ("NOWARN"))
187             fty->duplicate = 0;
188           else if (lex_match_id ("CASE"))
189             {
190               if (fty->type != FTY_NESTED)
191                 {
192                   msg (SE, _("DUPLICATE=CASE is only valid on "
193                              "FILE TYPE NESTED."));
194                   goto error;
195                 }
196               
197               fty->duplicate = 2;
198             }
199           else
200             {
201               msg (SE, _("WARN%s expected after DUPLICATE."),
202                    (fty->type == FTY_NESTED ? _(", NOWARN, or CASE")
203                     : _(" or NOWARN")));
204               goto error;
205             }
206         }
207       else if (lex_match_id ("MISSING"))
208         {
209           if (fty->type == FTY_MIXED)
210             {
211               msg (SE, _("The MISSING subcommand is not valid on "
212                          "FILE TYPE MIXED."));
213               goto error;
214             }
215           
216           lex_match ('=');
217           if (lex_match_id ("NOWARN"))
218             fty->missing = 0;
219           else if (lex_match_id ("WARN"))
220             fty->missing = 1;
221           else
222             {
223               msg (SE, _("WARN or NOWARN after MISSING."));
224               goto error;
225             }
226         }
227       else if (lex_match_id ("ORDERED"))
228         {
229           if (fty->type != FTY_GROUPED)
230             {
231               msg (SE, _("ORDERED is only valid on FILE TYPE GROUPED."));
232               goto error;
233             }
234           
235           lex_match ('=');
236           if (lex_match_id ("YES"))
237             fty->ordered = 1;
238           else if (lex_match_id ("NO"))
239             fty->ordered = 0;
240           else
241             {
242               msg (SE, _("YES or NO expected after ORDERED."));
243               goto error;
244             }
245         }
246       else
247         {
248           lex_error (_("while expecting a valid subcommand"));
249           goto error;
250         }
251     }
252
253   if (fty->record.name[0] == 0)
254     {
255       msg (SE, _("The required RECORD subcommand was not present."));
256       goto error;
257     }
258
259   if (fty->type == FTY_GROUPED)
260     {
261       if (fty->case_sbc.name[0] == 0)
262         {
263           msg (SE, _("The required CASE subcommand was not present."));
264           goto error;
265         }
266       
267       if (!strcmp (fty->case_sbc.name, fty->record.name))
268         {
269           msg (SE, _("CASE and RECORD must specify different variable "
270                      "names."));
271           goto error;
272         }
273     }
274
275   fty->reader = dfm_open_reader (fh);
276   if (fty->reader == NULL)
277     goto error;
278   default_handle = fh;
279
280   create_col_var (&fty->record);
281   if (fty->case_sbc.name[0])
282     create_col_var (&fty->case_sbc);
283   vfm_source = create_case_source (&file_type_source_class, fty);
284
285   return CMD_SUCCESS;
286
287  error:
288   free (fty);
289   return CMD_FAILURE;
290 }
291
292 /* Creates a variable with attributes specified by struct col_spec C, and
293    stores it into C->V. */
294 static void
295 create_col_var (struct col_spec *c)
296 {
297   int width;
298
299   if (formats[c->fmt].cat & FCAT_STRING)
300     width = c->nc;
301   else
302     width = 0;
303   c->v = dict_create_var (default_dict, c->name, width);
304 }
305
306 /* Parses variable, column, type specifications for a variable. */
307 static int
308 parse_col_spec (struct col_spec *c, const char *def_name)
309 {
310   struct fmt_spec spec;
311
312   /* Name. */
313   if (token == T_ID)
314     {
315       strcpy (c->name, tokid);
316       lex_get ();
317     }
318   else
319     strcpy (c->name, def_name);
320
321   /* First column. */
322   if (!lex_force_int ())
323     return 0;
324   c->fc = lex_integer ();
325   if (c->fc < 1)
326     {
327       msg (SE, _("Column value must be positive."));
328       return 0;
329     }
330   lex_get ();
331
332   /* Last column. */
333   lex_negative_to_dash ();
334   if (lex_match ('-'))
335     {
336       if (!lex_force_int ())
337         return 0;
338       c->nc = lex_integer ();
339       lex_get ();
340
341       if (c->nc < c->fc)
342         {
343           msg (SE, _("Ending column precedes beginning column."));
344           return 0;
345         }
346       
347       c->nc -= c->fc - 1;
348     }
349   else
350     c->nc = 1;
351
352   /* Format specifier. */
353   if (lex_match ('('))
354     {
355       const char *cp;
356       if (!lex_force_id ())
357         return 0;
358       c->fmt = parse_format_specifier_name (&cp, 0);
359       if (c->fmt == -1)
360         return 0;
361       if (*cp)
362         {
363           msg (SE, _("Bad format specifier name."));
364           return 0;
365         }
366       lex_get ();
367       if (!lex_force_match (')'))
368         return 0;
369     }
370   else
371     c->fmt = FMT_F;
372
373   spec.type = c->fmt;
374   spec.w = c->nc;
375   spec.d = 0;
376   return check_input_specifier (&spec, 1);
377 }
378 \f
379 /* RECORD TYPE. */
380
381 /* Parse the RECORD TYPE command. */
382 int
383 cmd_record_type (void)
384 {
385   struct file_type_pgm *fty;
386   struct record_type *rct;
387
388   /* Make sure we're inside a FILE TYPE structure. */
389   if (pgm_state != STATE_INPUT
390       || !case_source_is_class (vfm_source, &file_type_source_class))
391     {
392       msg (SE, _("This command may only appear within a "
393                  "FILE TYPE/END FILE TYPE structure."));
394       return CMD_FAILURE;
395     }
396
397   fty = vfm_source->aux;
398
399   /* Initialize the record_type structure. */
400   rct = xmalloc (sizeof *rct);
401   rct->next = NULL;
402   rct->flags = 0;
403   if (fty->duplicate)
404     rct->flags |= RCT_DUPLICATE;
405   if (fty->missing)
406     rct->flags |= RCT_MISSING;
407   rct->v = NULL;
408   rct->nv = 0;
409   rct->ft = n_trns;
410   if (fty->case_sbc.name[0])
411     rct->case_sbc = fty->case_sbc;
412
413   if (fty->recs_tail && (fty->recs_tail->flags & RCT_OTHER))
414     {
415       msg (SE, _("OTHER may appear only on the last RECORD TYPE command."));
416       goto error;
417     }
418       
419   if (fty->recs_tail)
420     {
421       fty->recs_tail->lt = n_trns - 1;
422       if (!(fty->recs_tail->flags & RCT_SKIP)
423           && fty->recs_tail->ft == fty->recs_tail->lt)
424         {
425           msg (SE, _("No input commands (DATA LIST, REPEATING DATA) "
426                      "for above RECORD TYPE."));
427           goto error;
428         }
429     }
430
431   /* Parse record type values. */
432   if (lex_match_id ("OTHER"))
433     rct->flags |= RCT_OTHER;
434   else
435     {
436       int mv = 0;
437
438       while (lex_is_number () || token == T_STRING)
439         {
440           if (rct->nv >= mv)
441             {
442               mv += 16;
443               rct->v = xrealloc (rct->v, mv * sizeof *rct->v);
444             }
445
446           if (formats[fty->record.fmt].cat & FCAT_STRING)
447             {
448               if (!lex_force_string ())
449                 goto error;
450               rct->v[rct->nv].c = xmalloc (fty->record.nc + 1);
451               st_bare_pad_copy (rct->v[rct->nv].c, ds_c_str (&tokstr),
452                                 fty->record.nc + 1);
453             }
454           else
455             {
456               if (!lex_force_num ())
457                 goto error;
458               rct->v[rct->nv].f = tokval;
459             }
460           rct->nv++;
461           lex_get ();
462
463           lex_match (',');
464         }
465     }
466
467   /* Parse the rest of the subcommands. */
468   while (token != '.')
469     {
470       if (lex_match_id ("SKIP"))
471         rct->flags |= RCT_SKIP;
472       else if (lex_match_id ("CASE"))
473         {
474           if (fty->type == FTY_MIXED)
475             {
476               msg (SE, _("The CASE subcommand is not allowed on "
477                          "the RECORD TYPE command for FILE TYPE MIXED."));
478               goto error;
479             }
480
481           lex_match ('=');
482           if (!parse_col_spec (&rct->case_sbc, ""))
483             goto error;
484           if (rct->case_sbc.name[0])
485             {
486               msg (SE, _("No variable name may be specified for the "
487                          "CASE subcommand on RECORD TYPE."));
488               goto error;
489             }
490           
491           if ((formats[rct->case_sbc.fmt].cat ^ formats[fty->case_sbc.fmt].cat)
492               & FCAT_STRING)
493             {
494               msg (SE, _("The CASE column specification on RECORD TYPE "
495                          "must give a format specifier that is the "
496                          "same type as that of the CASE column "
497                          "specification given on FILE TYPE."));
498               goto error;
499             }
500         }
501       else if (lex_match_id ("DUPLICATE"))
502         {
503           lex_match ('=');
504           if (lex_match_id ("WARN"))
505             rct->flags |= RCT_DUPLICATE;
506           else if (lex_match_id ("NOWARN"))
507             rct->flags &= ~RCT_DUPLICATE;
508           else
509             {
510               msg (SE, _("WARN or NOWARN expected on DUPLICATE "
511                          "subcommand."));
512               goto error;
513             }
514         }
515       else if (lex_match_id ("MISSING"))
516         {
517           lex_match ('=');
518           if (lex_match_id ("WARN"))
519             rct->flags |= RCT_MISSING;
520           else if (lex_match_id ("NOWARN"))
521             rct->flags &= ~RCT_MISSING;
522           else
523             {
524               msg (SE, _("WARN or NOWARN expected on MISSING subcommand."));
525               goto error;
526             }
527         }
528       else if (lex_match_id ("SPREAD"))
529         {
530           lex_match ('=');
531           if (lex_match_id ("YES"))
532             rct->flags |= RCT_SPREAD;
533           else if (lex_match_id ("NO"))
534             rct->flags &= ~RCT_SPREAD;
535           else
536             {
537               msg (SE, _("YES or NO expected on SPREAD subcommand."));
538               goto error;
539             }
540         }
541       else
542         {
543           lex_error (_("while expecting a valid subcommand"));
544           goto error;
545         }
546     }
547
548   if (fty->recs_head)
549     fty->recs_tail = fty->recs_tail->next = xmalloc (sizeof *fty->recs_tail);
550   else
551     fty->recs_head = fty->recs_tail = xmalloc (sizeof *fty->recs_tail);
552   memcpy (fty->recs_tail, &rct, sizeof *fty->recs_tail);
553
554   return CMD_SUCCESS;
555
556  error:
557   if (formats[fty->record.fmt].cat & FCAT_STRING) 
558     {
559       int i;
560       
561       for (i = 0; i < rct->nv; i++)
562         free (rct->v[i].c); 
563     }
564   free (rct->v);
565   free (rct);
566
567   return CMD_FAILURE;
568 }
569 \f
570 /* END FILE TYPE. */
571
572 int
573 cmd_end_file_type (void)
574 {
575   struct file_type_pgm *fty;
576
577   if (pgm_state != STATE_INPUT
578       || case_source_is_class (vfm_source, &file_type_source_class))
579     {
580       msg (SE, _("This command may only appear within a "
581                  "FILE TYPE/END FILE TYPE structure."));
582       return CMD_FAILURE;
583     }
584   fty = vfm_source->aux;
585   fty->case_size = dict_get_case_size (default_dict);
586
587   if (fty->recs_tail)
588     {
589       fty->recs_tail->lt = n_trns - 1;
590       if (!(fty->recs_tail->flags & RCT_SKIP)
591           && fty->recs_tail->ft == fty->recs_tail->lt)
592         {
593           msg (SE, _("No input commands (DATA LIST, REPEATING DATA) "
594                      "on above RECORD TYPE."));
595           goto fail;
596         }
597     }
598   else
599     {
600       msg (SE, _("No commands between FILE TYPE and END FILE TYPE."));
601       goto fail;
602     }
603
604   f_trns = n_trns;
605
606   return lex_end_of_command ();
607
608  fail:
609   /* Come here on discovering catastrophic error. */
610   err_cond_fail ();
611   discard_variables ();
612   return CMD_FAILURE;
613 }
614 \f
615 /* FILE TYPE runtime. */
616
617 /*static void read_from_file_type_mixed(void);
618    static void read_from_file_type_grouped(void);
619    static void read_from_file_type_nested(void); */
620
621 /* Reads any number of cases into case C and calls write_case()
622    for each one.  Compare data-list.c:read_from_data_list. */
623 static void
624 file_type_source_read (struct case_source *source,
625                        struct ccase *c,
626                        write_case_func *write_case UNUSED,
627                        write_case_data wc_data UNUSED)
628 {
629   struct file_type_pgm *fty = source->aux;
630   struct fmt_spec format;
631
632   dfm_push (fty->reader);
633
634   format.type = fty->record.fmt;
635   format.w = fty->record.nc;
636   format.d = 0;
637   while (!dfm_eof (fty->reader))
638     {
639       struct fixed_string line;
640       struct record_type *iter;
641       union value v;
642       int i;
643
644       dfm_expand_tabs (fty->reader);
645       dfm_get_record (fty->reader, &line);
646       if (formats[fty->record.fmt].cat & FCAT_STRING)
647         {
648           struct data_in di;
649           
650           v.c = case_data_rw (c, fty->record.v->fv)->s;
651
652           data_in_finite_line (&di, ls_c_str (&line), ls_length (&line),
653                                fty->record.fc, fty->record.fc + fty->record.nc);
654           di.v = (union value *) v.c;
655           di.flags = 0;
656           di.f1 = fty->record.fc;
657           di.format = format;
658           data_in (&di);
659
660           for (iter = fty->recs_head; iter; iter = iter->next)
661             {
662               if (iter->flags & RCT_OTHER)
663                 goto found;
664               for (i = 0; i < iter->nv; i++)
665                 if (!memcmp (iter->v[i].c, v.c, fty->record.nc))
666                   goto found;
667             }
668           if (fty->wild)
669             msg (SW, _("Unknown record type \"%.*s\"."), fty->record.nc, v.c);
670         }
671       else
672         {
673           struct data_in di;
674
675           data_in_finite_line (&di, ls_c_str (&line), ls_length (&line),
676                                fty->record.fc, fty->record.fc + fty->record.nc);
677           di.v = &v;
678           di.flags = 0;
679           di.f1 = fty->record.fc;
680           di.format = format;
681           data_in (&di);
682
683           case_data_rw (c, fty->record.v->fv)->f = v.f;
684           for (iter = fty->recs_head; iter; iter = iter->next)
685             {
686               if (iter->flags & RCT_OTHER)
687                 goto found;
688               for (i = 0; i < iter->nv; i++)
689                 if (iter->v[i].f == v.f)
690                   goto found;
691             }
692           if (fty->wild)
693             msg (SW, _("Unknown record type %g."), v.f);
694         }
695       dfm_forward_record (fty->reader);
696       continue;
697
698     found:
699       /* Arrive here if there is a matching record_type, which is in
700          iter. */
701       dfm_forward_record (fty->reader);
702     }
703
704 /*  switch(fty->type)
705    {
706    case FTY_MIXED: read_from_file_type_mixed(); break;
707    case FTY_GROUPED: read_from_file_type_grouped(); break;
708    case FTY_NESTED: read_from_file_type_nested(); break;
709    default: assert(0);
710    } */
711
712   dfm_pop (fty->reader);
713 }
714
715 static void
716 file_type_source_destroy (struct case_source *source)
717 {
718   struct file_type_pgm *fty = source->aux;
719   struct record_type *iter, *next;
720
721   cancel_transformations ();
722   dfm_close_reader (fty->reader);
723   for (iter = fty->recs_head; iter; iter = next)
724     {
725       next = iter->next;
726       free (iter);
727     }
728 }
729
730 const struct case_source_class file_type_source_class =
731   {
732     "FILE TYPE",
733     NULL,
734     file_type_source_read,
735     file_type_source_destroy,
736   };