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