b90ae1840dbc1b7a462086ea85fcdb4ee76c5326
[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., 51 Franklin Street, Fifth Floor, Boston, MA
18    02110-1301, 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[LONG_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 int cmd_file_type (void);
98
99 /* Parses FILE TYPE command. */
100 int
101 cmd_file_type (void)
102 {
103   static struct file_type_pgm *fty;     /* FIXME: static? WTF? */
104   struct file_handle *fh = NULL;
105
106   /* Initialize. */
107   discard_variables ();
108
109   fty = xmalloc (sizeof *fty);
110   fty->reader = NULL;
111   fty->record.name[0] = 0;
112   fty->case_sbc.name[0] = 0;
113   fty->wild = fty->duplicate = fty->missing = fty->ordered = 0;
114   fty->had_rec_type = 0;
115   fty->recs_head = fty->recs_tail = NULL;
116
117   if (lex_match_id ("MIXED"))
118     fty->type = FTY_MIXED;
119   else if (lex_match_id ("GROUPED"))
120     {
121       fty->type = FTY_GROUPED;
122       fty->wild = 1;
123       fty->duplicate = 1;
124       fty->missing = 1;
125       fty->ordered = 1;
126     }
127   else if (lex_match_id ("NESTED"))
128     fty->type = FTY_NESTED;
129   else
130     {
131       msg (SE, _("MIXED, GROUPED, or NESTED expected."));
132       goto error;
133     }
134
135   while (token != '.')
136     {
137       if (lex_match_id ("FILE"))
138         {
139           lex_match ('=');
140           fh = fh_parse ();
141           if (fh == NULL)
142             goto error;
143         }
144       else if (lex_match_id ("RECORD"))
145         {
146           lex_match ('=');
147           if (!parse_col_spec (&fty->record, "####RECD"))
148             goto error;
149         }
150       else if (lex_match_id ("CASE"))
151         {
152           if (fty->type == FTY_MIXED)
153             {
154               msg (SE, _("The CASE subcommand is not valid on FILE TYPE "
155                          "MIXED."));
156               goto error;
157             }
158           
159           lex_match ('=');
160           if (!parse_col_spec (&fty->case_sbc, "####CASE"))
161             goto error;
162         }
163       else if (lex_match_id ("WILD"))
164         {
165           lex_match ('=');
166           if (lex_match_id ("WARN"))
167             fty->wild = 1;
168           else if (lex_match_id ("NOWARN"))
169             fty->wild = 0;
170           else
171             {
172               msg (SE, _("WARN or NOWARN expected after WILD."));
173               goto error;
174             }
175         }
176       else if (lex_match_id ("DUPLICATE"))
177         {
178           if (fty->type == FTY_MIXED)
179             {
180               msg (SE, _("The DUPLICATE subcommand is not valid on "
181                          "FILE TYPE MIXED."));
182               goto error;
183             }
184
185           lex_match ('=');
186           if (lex_match_id ("WARN"))
187             fty->duplicate = 1;
188           else if (lex_match_id ("NOWARN"))
189             fty->duplicate = 0;
190           else if (lex_match_id ("CASE"))
191             {
192               if (fty->type != FTY_NESTED)
193                 {
194                   msg (SE, _("DUPLICATE=CASE is only valid on "
195                              "FILE TYPE NESTED."));
196                   goto error;
197                 }
198               
199               fty->duplicate = 2;
200             }
201           else
202             {
203               msg (SE, _("WARN%s expected after DUPLICATE."),
204                    (fty->type == FTY_NESTED ? _(", NOWARN, or CASE")
205                     : _(" or NOWARN")));
206               goto error;
207             }
208         }
209       else if (lex_match_id ("MISSING"))
210         {
211           if (fty->type == FTY_MIXED)
212             {
213               msg (SE, _("The MISSING subcommand is not valid on "
214                          "FILE TYPE MIXED."));
215               goto error;
216             }
217           
218           lex_match ('=');
219           if (lex_match_id ("NOWARN"))
220             fty->missing = 0;
221           else if (lex_match_id ("WARN"))
222             fty->missing = 1;
223           else
224             {
225               msg (SE, _("WARN or NOWARN after MISSING."));
226               goto error;
227             }
228         }
229       else if (lex_match_id ("ORDERED"))
230         {
231           if (fty->type != FTY_GROUPED)
232             {
233               msg (SE, _("ORDERED is only valid on FILE TYPE GROUPED."));
234               goto error;
235             }
236           
237           lex_match ('=');
238           if (lex_match_id ("YES"))
239             fty->ordered = 1;
240           else if (lex_match_id ("NO"))
241             fty->ordered = 0;
242           else
243             {
244               msg (SE, _("YES or NO expected after ORDERED."));
245               goto error;
246             }
247         }
248       else
249         {
250           lex_error (_("while expecting a valid subcommand"));
251           goto error;
252         }
253     }
254
255   if (fty->record.name[0] == 0)
256     {
257       msg (SE, _("The required RECORD subcommand was not present."));
258       goto error;
259     }
260
261   if (fty->type == FTY_GROUPED)
262     {
263       if (fty->case_sbc.name[0] == 0)
264         {
265           msg (SE, _("The required CASE subcommand was not present."));
266           goto error;
267         }
268       
269       if (!strcasecmp (fty->case_sbc.name, fty->record.name))
270         {
271           msg (SE, _("CASE and RECORD must specify different variable "
272                      "names."));
273           goto error;
274         }
275     }
276
277   fty->reader = dfm_open_reader (fh);
278   if (fty->reader == NULL)
279     goto error;
280   default_handle = fh;
281
282   create_col_var (&fty->record);
283   if (fty->case_sbc.name[0])
284     create_col_var (&fty->case_sbc);
285   vfm_source = create_case_source (&file_type_source_class, fty);
286
287   return CMD_SUCCESS;
288
289  error:
290   free (fty);
291   return CMD_FAILURE;
292 }
293
294 /* Creates a variable with attributes specified by struct col_spec C, and
295    stores it into C->V. */
296 static void
297 create_col_var (struct col_spec *c)
298 {
299   int width;
300
301   if (formats[c->fmt].cat & FCAT_STRING)
302     width = c->nc;
303   else
304     width = 0;
305   c->v = dict_create_var (default_dict, c->name, width);
306 }
307
308 /* Parses variable, column, type specifications for a variable. */
309 static int
310 parse_col_spec (struct col_spec *c, const char *def_name)
311 {
312   struct fmt_spec spec;
313
314   /* Name. */
315   if (token == T_ID)
316     {
317       strcpy (c->name, tokid);
318       lex_get ();
319     }
320   else
321     strcpy (c->name, def_name);
322
323   /* First column. */
324   if (!lex_force_int ())
325     return 0;
326   c->fc = lex_integer ();
327   if (c->fc < 1)
328     {
329       msg (SE, _("Column value must be positive."));
330       return 0;
331     }
332   lex_get ();
333
334   /* Last column. */
335   lex_negative_to_dash ();
336   if (lex_match ('-'))
337     {
338       if (!lex_force_int ())
339         return 0;
340       c->nc = lex_integer ();
341       lex_get ();
342
343       if (c->nc < c->fc)
344         {
345           msg (SE, _("Ending column precedes beginning column."));
346           return 0;
347         }
348       
349       c->nc -= c->fc - 1;
350     }
351   else
352     c->nc = 1;
353
354   /* Format specifier. */
355   if (lex_match ('('))
356     {
357       const char *cp;
358       if (!lex_force_id ())
359         return 0;
360       c->fmt = parse_format_specifier_name (&cp, 0);
361       if (c->fmt == -1)
362         return 0;
363       if (*cp)
364         {
365           msg (SE, _("Bad format specifier name."));
366           return 0;
367         }
368       lex_get ();
369       if (!lex_force_match (')'))
370         return 0;
371     }
372   else
373     c->fmt = FMT_F;
374
375   spec.type = c->fmt;
376   spec.w = c->nc;
377   spec.d = 0;
378   return check_input_specifier (&spec, 1);
379 }
380 \f
381 /* RECORD TYPE. */
382
383 /* Parse the RECORD TYPE command. */
384 int
385 cmd_record_type (void)
386 {
387   struct file_type_pgm *fty;
388   struct record_type *rct;
389
390   /* Make sure we're inside a FILE TYPE structure. */
391   if (pgm_state != STATE_INPUT
392       || !case_source_is_class (vfm_source, &file_type_source_class))
393     {
394       msg (SE, _("This command may only appear within a "
395                  "FILE TYPE/END FILE TYPE structure."));
396       return CMD_FAILURE;
397     }
398
399   fty = vfm_source->aux;
400
401   /* Initialize the record_type structure. */
402   rct = xmalloc (sizeof *rct);
403   rct->next = NULL;
404   rct->flags = 0;
405   if (fty->duplicate)
406     rct->flags |= RCT_DUPLICATE;
407   if (fty->missing)
408     rct->flags |= RCT_MISSING;
409   rct->v = NULL;
410   rct->nv = 0;
411   rct->ft = n_trns;
412   if (fty->case_sbc.name[0])
413     rct->case_sbc = fty->case_sbc;
414
415   if (fty->recs_tail && (fty->recs_tail->flags & RCT_OTHER))
416     {
417       msg (SE, _("OTHER may appear only on the last RECORD TYPE command."));
418       goto error;
419     }
420       
421   if (fty->recs_tail)
422     {
423       fty->recs_tail->lt = n_trns - 1;
424       if (!(fty->recs_tail->flags & RCT_SKIP)
425           && fty->recs_tail->ft == fty->recs_tail->lt)
426         {
427           msg (SE, _("No input commands (DATA LIST, REPEATING DATA) "
428                      "for above RECORD TYPE."));
429           goto error;
430         }
431     }
432
433   /* Parse record type values. */
434   if (lex_match_id ("OTHER"))
435     rct->flags |= RCT_OTHER;
436   else
437     {
438       int mv = 0;
439
440       while (lex_is_number () || token == T_STRING)
441         {
442           if (rct->nv >= mv)
443             {
444               mv += 16;
445               rct->v = xrealloc (rct->v, mv * sizeof *rct->v);
446             }
447
448           if (formats[fty->record.fmt].cat & FCAT_STRING)
449             {
450               if (!lex_force_string ())
451                 goto error;
452               rct->v[rct->nv].c = xmalloc (fty->record.nc + 1);
453               buf_copy_str_rpad (rct->v[rct->nv].c, fty->record.nc + 1,
454                                  ds_c_str (&tokstr));
455             }
456           else
457             {
458               if (!lex_force_num ())
459                 goto error;
460               rct->v[rct->nv].f = tokval;
461             }
462           rct->nv++;
463           lex_get ();
464
465           lex_match (',');
466         }
467     }
468
469   /* Parse the rest of the subcommands. */
470   while (token != '.')
471     {
472       if (lex_match_id ("SKIP"))
473         rct->flags |= RCT_SKIP;
474       else if (lex_match_id ("CASE"))
475         {
476           if (fty->type == FTY_MIXED)
477             {
478               msg (SE, _("The CASE subcommand is not allowed on "
479                          "the RECORD TYPE command for FILE TYPE MIXED."));
480               goto error;
481             }
482
483           lex_match ('=');
484           if (!parse_col_spec (&rct->case_sbc, ""))
485             goto error;
486           if (rct->case_sbc.name[0])
487             {
488               msg (SE, _("No variable name may be specified for the "
489                          "CASE subcommand on RECORD TYPE."));
490               goto error;
491             }
492           
493           if ((formats[rct->case_sbc.fmt].cat ^ formats[fty->case_sbc.fmt].cat)
494               & FCAT_STRING)
495             {
496               msg (SE, _("The CASE column specification on RECORD TYPE "
497                          "must give a format specifier that is the "
498                          "same type as that of the CASE column "
499                          "specification given on FILE TYPE."));
500               goto error;
501             }
502         }
503       else if (lex_match_id ("DUPLICATE"))
504         {
505           lex_match ('=');
506           if (lex_match_id ("WARN"))
507             rct->flags |= RCT_DUPLICATE;
508           else if (lex_match_id ("NOWARN"))
509             rct->flags &= ~RCT_DUPLICATE;
510           else
511             {
512               msg (SE, _("WARN or NOWARN expected on DUPLICATE "
513                          "subcommand."));
514               goto error;
515             }
516         }
517       else if (lex_match_id ("MISSING"))
518         {
519           lex_match ('=');
520           if (lex_match_id ("WARN"))
521             rct->flags |= RCT_MISSING;
522           else if (lex_match_id ("NOWARN"))
523             rct->flags &= ~RCT_MISSING;
524           else
525             {
526               msg (SE, _("WARN or NOWARN expected on MISSING subcommand."));
527               goto error;
528             }
529         }
530       else if (lex_match_id ("SPREAD"))
531         {
532           lex_match ('=');
533           if (lex_match_id ("YES"))
534             rct->flags |= RCT_SPREAD;
535           else if (lex_match_id ("NO"))
536             rct->flags &= ~RCT_SPREAD;
537           else
538             {
539               msg (SE, _("YES or NO expected on SPREAD subcommand."));
540               goto error;
541             }
542         }
543       else
544         {
545           lex_error (_("while expecting a valid subcommand"));
546           goto error;
547         }
548     }
549
550   if (fty->recs_head)
551     fty->recs_tail = fty->recs_tail->next = xmalloc (sizeof *fty->recs_tail);
552   else
553     fty->recs_head = fty->recs_tail = xmalloc (sizeof *fty->recs_tail);
554   memcpy (fty->recs_tail, &rct, sizeof *fty->recs_tail);
555
556   return CMD_SUCCESS;
557
558  error:
559   if (formats[fty->record.fmt].cat & FCAT_STRING) 
560     {
561       int i;
562       
563       for (i = 0; i < rct->nv; i++)
564         free (rct->v[i].c); 
565     }
566   free (rct->v);
567   free (rct);
568
569   return CMD_FAILURE;
570 }
571 \f
572 /* END FILE TYPE. */
573
574 int cmd_end_file_type (void);
575 int
576 cmd_end_file_type (void)
577 {
578   struct file_type_pgm *fty;
579
580   if (pgm_state != STATE_INPUT
581       || case_source_is_class (vfm_source, &file_type_source_class))
582     {
583       msg (SE, _("This command may only appear within a "
584                  "FILE TYPE/END FILE TYPE structure."));
585       return CMD_FAILURE;
586     }
587   fty = vfm_source->aux;
588   fty->case_size = dict_get_case_size (default_dict);
589
590   if (fty->recs_tail)
591     {
592       fty->recs_tail->lt = n_trns - 1;
593       if (!(fty->recs_tail->flags & RCT_SKIP)
594           && fty->recs_tail->ft == fty->recs_tail->lt)
595         {
596           msg (SE, _("No input commands (DATA LIST, REPEATING DATA) "
597                      "on above RECORD TYPE."));
598           goto fail;
599         }
600     }
601   else
602     {
603       msg (SE, _("No commands between FILE TYPE and END FILE TYPE."));
604       goto fail;
605     }
606
607   f_trns = n_trns;
608
609   return lex_end_of_command ();
610
611  fail:
612   /* Come here on discovering catastrophic error. */
613   err_cond_fail ();
614   discard_variables ();
615   return CMD_FAILURE;
616 }
617 \f
618 /* FILE TYPE runtime. */
619
620 /*static void read_from_file_type_mixed(void);
621    static void read_from_file_type_grouped(void);
622    static void read_from_file_type_nested(void); */
623
624 /* Reads any number of cases into case C and calls write_case()
625    for each one.  Compare data-list.c:read_from_data_list. */
626 static void
627 file_type_source_read (struct case_source *source,
628                        struct ccase *c,
629                        write_case_func *write_case UNUSED,
630                        write_case_data wc_data UNUSED)
631 {
632   struct file_type_pgm *fty = source->aux;
633   struct fmt_spec format;
634
635   dfm_push (fty->reader);
636
637   format.type = fty->record.fmt;
638   format.w = fty->record.nc;
639   format.d = 0;
640   while (!dfm_eof (fty->reader))
641     {
642       struct fixed_string line;
643       struct record_type *iter;
644       union value v;
645       int i;
646
647       dfm_expand_tabs (fty->reader);
648       dfm_get_record (fty->reader, &line);
649       if (formats[fty->record.fmt].cat & FCAT_STRING)
650         {
651           struct data_in di;
652           
653           v.c = case_data_rw (c, fty->record.v->fv)->s;
654
655           data_in_finite_line (&di, ls_c_str (&line), ls_length (&line),
656                                fty->record.fc, fty->record.fc + fty->record.nc);
657           di.v = (union value *) v.c;
658           di.flags = 0;
659           di.f1 = fty->record.fc;
660           di.format = format;
661           data_in (&di);
662
663           for (iter = fty->recs_head; iter; iter = iter->next)
664             {
665               if (iter->flags & RCT_OTHER)
666                 goto found;
667               for (i = 0; i < iter->nv; i++)
668                 if (!memcmp (iter->v[i].c, v.c, fty->record.nc))
669                   goto found;
670             }
671           if (fty->wild)
672             msg (SW, _("Unknown record type \"%.*s\"."), fty->record.nc, v.c);
673         }
674       else
675         {
676           struct data_in di;
677
678           data_in_finite_line (&di, ls_c_str (&line), ls_length (&line),
679                                fty->record.fc, fty->record.fc + fty->record.nc);
680           di.v = &v;
681           di.flags = 0;
682           di.f1 = fty->record.fc;
683           di.format = format;
684           data_in (&di);
685
686           case_data_rw (c, fty->record.v->fv)->f = v.f;
687           for (iter = fty->recs_head; iter; iter = iter->next)
688             {
689               if (iter->flags & RCT_OTHER)
690                 goto found;
691               for (i = 0; i < iter->nv; i++)
692                 if (iter->v[i].f == v.f)
693                   goto found;
694             }
695           if (fty->wild)
696             msg (SW, _("Unknown record type %g."), v.f);
697         }
698       dfm_forward_record (fty->reader);
699       continue;
700
701     found:
702       /* Arrive here if there is a matching record_type, which is in
703          iter. */
704       dfm_forward_record (fty->reader);
705     }
706
707 /*  switch(fty->type)
708    {
709    case FTY_MIXED: read_from_file_type_mixed(); break;
710    case FTY_GROUPED: read_from_file_type_grouped(); break;
711    case FTY_NESTED: read_from_file_type_nested(); break;
712    default: assert(0);
713    } */
714
715   dfm_pop (fty->reader);
716 }
717
718 static void
719 file_type_source_destroy (struct case_source *source)
720 {
721   struct file_type_pgm *fty = source->aux;
722   struct record_type *iter, *next;
723
724   cancel_transformations ();
725   dfm_close_reader (fty->reader);
726   for (iter = fty->recs_head; iter; iter = next)
727     {
728       next = iter->next;
729       free (iter);
730     }
731 }
732
733 const struct case_source_class file_type_source_class =
734   {
735     "FILE TYPE",
736     NULL,
737     file_type_source_read,
738     file_type_source_destroy,
739   };