Sat Dec 27 16:16:49 2003 Ben Pfaff <blp@gnu.org>
[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 "approx.h"
25 #include "command.h"
26 #include "data-in.h"
27 #include "dfm.h"
28 #include "file-handle.h"
29 #include "format.h"
30 #include "lexer.h"
31 #include "str.h"
32 #include "var.h"
33 #include "vfm.h"
34
35 /* Defines the three types of complex files read by FILE TYPE. */
36 enum
37   {
38     FTY_MIXED,
39     FTY_GROUPED,
40     FTY_NESTED
41   };
42
43 /* Limited variable column specifications. */
44 struct col_spec
45   {
46    char name[9];                /* Variable name. */
47     int fc, nc;                 /* First column (1-based), # of columns. */
48     int fmt;                    /* Format type. */
49     struct variable *v;         /* Variable. */
50   };
51
52 /* RCT_* record type constants. */
53 enum
54   {
55     RCT_OTHER = 001,            /* 1=OTHER. */
56     RCT_SKIP = 002,             /* 1=SKIP. */
57     RCT_DUPLICATE = 004,        /* DUPLICATE: 0=NOWARN, 1=WARN. */
58     RCT_MISSING = 010,          /* MISSING: 0=NOWARN, 1=WARN. */
59     RCT_SPREAD = 020            /* SPREAD: 0=NO, 1=YES. */
60   };
61
62 /* Represents a RECORD TYPE command. */
63 struct record_type
64   {
65     struct record_type *next;
66     unsigned flags;             /* RCT_* constants. */
67     union value *v;             /* Vector of values for this record type. */
68     int nv;                     /* Length of vector V. */
69     struct col_spec case_sbc;   /* CASE subcommand. */
70     int ft, lt;                 /* First, last transformation index. */
71   };                            /* record_type */
72
73 /* Represents a FILE TYPE input program.  Does not contain a
74    trns_header because it's never submitted as a transformation. */
75 struct file_type_pgm
76   {
77     int type;                   /* One of the FTY_* constants. */
78     struct file_handle *handle; /* File handle of input file. */
79     struct col_spec record;     /* RECORD subcommand. */
80     struct col_spec case_sbc;   /* CASE subcommand. */
81     int wild;                   /* 0=NOWARN, 1=WARN. */
82     int duplicate;              /* 0=NOWARN, 1=WARN. */
83     int missing;                /* 0=NOWARN, 1=WARN, 2=CASE. */
84     int ordered;                /* 0=NO, 1=YES. */
85     int had_rec_type;           /* 1=Had a RECORD TYPE command.
86                                    RECORD TYPE must precede the first
87                                    DATA LIST. */
88     struct record_type *recs_head;      /* List of record types. */
89     struct record_type *recs_tail;      /* Last in list of record types. */
90   };
91
92 /* Current FILE TYPE input program. */
93 static struct file_type_pgm fty;
94
95 static int parse_col_spec (struct col_spec *, const char *);
96 static void create_col_var (struct col_spec *c);
97
98 /* Parses FILE TYPE command. */
99 int
100 cmd_file_type (void)
101 {
102   /* Initialize. */
103   discard_variables ();
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       return CMD_FAILURE;
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             return CMD_FAILURE;
138         }
139       else if (lex_match_id ("RECORD"))
140         {
141           lex_match ('=');
142           if (!parse_col_spec (&fty.record, "####RECD"))
143             return CMD_FAILURE;
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               return CMD_FAILURE;
152             }
153           
154           lex_match ('=');
155           if (!parse_col_spec (&fty.case_sbc, "####CASE"))
156             return CMD_FAILURE;
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               return CMD_FAILURE;
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               return CMD_FAILURE;
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                   return CMD_FAILURE;
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               return CMD_FAILURE;
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               return CMD_FAILURE;
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               return CMD_FAILURE;
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               return CMD_FAILURE;
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               return CMD_FAILURE;
241             }
242         }
243       else
244         {
245           lex_error (_("while expecting a valid subcommand"));
246           return CMD_FAILURE;
247         }
248     }
249
250   if (fty.record.name[0] == 0)
251     {
252       msg (SE, _("The required RECORD subcommand was not present."));
253       return CMD_FAILURE;
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           return CMD_FAILURE;
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           return CMD_FAILURE;
269         }
270     }
271
272   default_handle = fty.handle;
273
274   vfm_source = &file_type_source;
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
282 /* Creates a variable with attributes specified by struct col_spec C, and
283    stores it into C->V. */
284 static void
285 create_col_var (struct col_spec *c)
286 {
287   int width;
288
289   if (formats[c->fmt].cat & FCAT_STRING)
290     width = c->nc;
291   else
292     width = 0;
293   c->v = dict_create_var (default_dict, c->name, width);
294 }
295
296 /* Parses variable, column, type specifications for a variable. */
297 static int
298 parse_col_spec (struct col_spec *c, const char *def_name)
299 {
300   struct fmt_spec spec;
301
302   if (token == T_ID)
303     {
304       strcpy (c->name, tokid);
305       lex_get ();
306     }
307   else
308     strcpy (c->name, def_name);
309
310   if (!lex_force_int ())
311     return 0;
312   c->fc = lex_integer ();
313   if (c->fc < 1)
314     {
315       msg (SE, _("Column value must be positive."));
316       return 0;
317     }
318   lex_get ();
319
320   lex_negative_to_dash ();
321   if (lex_match ('-'))
322     {
323       if (!lex_force_int ())
324         return 0;
325       c->nc = lex_integer ();
326       lex_get ();
327
328       if (c->nc < c->fc)
329         {
330           msg (SE, _("Ending column precedes beginning column."));
331           return 0;
332         }
333       
334       c->nc -= c->fc - 1;
335     }
336   else
337     c->nc = 1;
338
339   if (lex_match ('('))
340     {
341       const char *cp;
342       if (!lex_force_id ())
343         return 0;
344       c->fmt = parse_format_specifier_name (&cp, 0);
345       if (c->fmt == -1)
346         return 0;
347       if (*cp)
348         {
349           msg (SE, _("Bad format specifier name."));
350           return 0;
351         }
352       lex_get ();
353       if (!lex_force_match (')'))
354         return 0;
355     }
356   else
357     c->fmt = FMT_F;
358
359   spec.type = c->fmt;
360   spec.w = c->nc;
361   spec.d = 0;
362   return check_input_specifier (&spec);
363 }
364 \f
365 /* RECORD TYPE. */
366
367 /* Structure being filled in by internal_cmd_record_type. */
368 static struct record_type rct;
369
370 static int internal_cmd_record_type (void);
371
372 /* Parse the RECORD TYPE command. */
373 int
374 cmd_record_type (void)
375 {
376   int result = internal_cmd_record_type ();
377
378   if (result == CMD_FAILURE)
379     {
380       int i;
381
382       if (formats[fty.record.fmt].cat & FCAT_STRING)
383         for (i = 0; i < rct.nv; i++)
384           free (rct.v[i].c);
385       free (rct.v);
386     }
387
388   return result;
389 }
390
391 static int
392 internal_cmd_record_type (void)
393 {
394   /* Initialize the record_type structure. */
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   /* Make sure we're inside a FILE TYPE structure. */
408   if (pgm_state != STATE_INPUT || vfm_source != &file_type_source)
409     {
410       msg (SE, _("This command may only appear within a "
411                  "FILE TYPE/END FILE TYPE structure."));
412       return CMD_FAILURE;
413     }
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       return CMD_FAILURE;
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           return CMD_FAILURE;
430         }
431     }
432
433   lex_match_id ("RECORD");
434   lex_match_id ("TYPE");
435
436   /* Parse record type values. */
437   if (lex_match_id ("OTHER"))
438     rct.flags |= RCT_OTHER;
439   else
440     {
441       int mv = 0;
442
443       while (token == T_NUM || token == T_STRING)
444         {
445           if (rct.nv >= mv)
446             {
447               mv += 16;
448               rct.v = xrealloc (rct.v, mv * sizeof *rct.v);
449             }
450
451           if (formats[fty.record.fmt].cat & FCAT_STRING)
452             {
453               if (!lex_force_string ())
454                 return CMD_FAILURE;
455               rct.v[rct.nv].c = xmalloc (fty.record.nc + 1);
456               st_bare_pad_copy (rct.v[rct.nv].c, ds_value (&tokstr),
457                                 fty.record.nc + 1);
458             }
459           else
460             {
461               if (!lex_force_num ())
462                 return CMD_FAILURE;
463               rct.v[rct.nv].f = tokval;
464             }
465           rct.nv++;
466           lex_get ();
467
468           lex_match (',');
469         }
470     }
471
472   /* Parse the rest of the subcommands. */
473   while (token != '.')
474     {
475       if (lex_match_id ("SKIP"))
476         rct.flags |= RCT_SKIP;
477       else if (lex_match_id ("CASE"))
478         {
479           if (fty.type == FTY_MIXED)
480             {
481               msg (SE, _("The CASE subcommand is not allowed on "
482                          "the RECORD TYPE command for FILE TYPE MIXED."));
483               return CMD_FAILURE;
484             }
485
486           lex_match ('=');
487           if (!parse_col_spec (&rct.case_sbc, ""))
488             return CMD_FAILURE;
489           if (rct.case_sbc.name[0])
490             {
491               msg (SE, _("No variable name may be specified for the "
492                          "CASE subcommand on RECORD TYPE."));
493               return CMD_FAILURE;
494             }
495           
496           if ((formats[rct.case_sbc.fmt].cat ^ formats[fty.case_sbc.fmt].cat)
497               & FCAT_STRING)
498             {
499               msg (SE, _("The CASE column specification on RECORD TYPE "
500                          "must give a format specifier that is the "
501                          "same type as that of the CASE column "
502                          "specification given on FILE TYPE."));
503               return CMD_FAILURE;
504             }
505         }
506       else if (lex_match_id ("DUPLICATE"))
507         {
508           lex_match ('=');
509           if (lex_match_id ("WARN"))
510             rct.flags |= RCT_DUPLICATE;
511           else if (lex_match_id ("NOWARN"))
512             rct.flags &= ~RCT_DUPLICATE;
513           else
514             {
515               msg (SE, _("WARN or NOWARN expected on DUPLICATE "
516                          "subcommand."));
517               return CMD_FAILURE;
518             }
519         }
520       else if (lex_match_id ("MISSING"))
521         {
522           lex_match ('=');
523           if (lex_match_id ("WARN"))
524             rct.flags |= RCT_MISSING;
525           else if (lex_match_id ("NOWARN"))
526             rct.flags &= ~RCT_MISSING;
527           else
528             {
529               msg (SE, _("WARN or NOWARN expected on MISSING subcommand."));
530               return CMD_FAILURE;
531             }
532         }
533       else if (lex_match_id ("SPREAD"))
534         {
535           lex_match ('=');
536           if (lex_match_id ("YES"))
537             rct.flags |= RCT_SPREAD;
538           else if (lex_match_id ("NO"))
539             rct.flags &= ~RCT_SPREAD;
540           else
541             {
542               msg (SE, _("YES or NO expected on SPREAD subcommand."));
543               return CMD_FAILURE;
544             }
545         }
546       else
547         {
548           lex_error (_("while expecting a valid subcommand"));
549           return CMD_FAILURE;
550         }
551     }
552
553   if (fty.recs_head)
554     fty.recs_tail = fty.recs_tail->next = xmalloc (sizeof *fty.recs_tail);
555   else
556     fty.recs_head = fty.recs_tail = xmalloc (sizeof *fty.recs_tail);
557   memcpy (fty.recs_tail, &rct, sizeof *fty.recs_tail);
558
559   return CMD_SUCCESS;
560 }
561 \f
562 /* END FILE TYPE. */
563
564 int
565 cmd_end_file_type (void)
566 {
567   if (pgm_state != STATE_INPUT || vfm_source != &file_type_source)
568     {
569       msg (SE, _("This command may only appear within a "
570                  "FILE TYPE/END FILE TYPE structure."));
571       return CMD_FAILURE;
572     }
573
574   lex_match_id ("TYPE");
575
576   if (fty.recs_tail)
577     {
578       fty.recs_tail->lt = n_trns - 1;
579       if (!(fty.recs_tail->flags & RCT_SKIP)
580           && fty.recs_tail->ft == fty.recs_tail->lt)
581         {
582           msg (SE, _("No input commands (DATA LIST, REPEATING DATA) "
583                      "on above RECORD TYPE."));
584           goto fail;
585         }
586     }
587   else
588     {
589       msg (SE, _("No commands between FILE TYPE and END FILE TYPE."));
590       goto fail;
591     }
592
593   f_trns = n_trns;
594
595   return lex_end_of_command ();
596
597  fail:
598   /* Come here on discovering catastrophic error. */
599   err_cond_fail ();
600   discard_variables ();
601   return CMD_FAILURE;
602 }
603 \f
604 /* FILE TYPE runtime. */
605
606 /*static void read_from_file_type_mixed(void);
607    static void read_from_file_type_grouped(void);
608    static void read_from_file_type_nested(void); */
609
610 /* Reads any number of cases into temp_case and calls write_case() for
611    each one.  Compare data-list.c:read_from_data_list. */
612 static void
613 file_type_source_read (void)
614 {
615   char *line;
616   int len;
617
618   struct fmt_spec format;
619
620   dfm_push (fty.handle);
621
622   format.type = fty.record.fmt;
623   format.w = fty.record.nc;
624   format.d = 0;
625   while (NULL != (line = dfm_get_record (fty.handle, &len)))
626     {
627       struct record_type *iter;
628       union value v;
629       int i;
630
631       if (formats[fty.record.fmt].cat & FCAT_STRING)
632         {
633           struct data_in di;
634           
635           v.c = temp_case->data[fty.record.v->fv].s;
636
637           data_in_finite_line (&di, line, len,
638                                fty.record.fc, fty.record.fc + fty.record.nc);
639           di.v = (union value *) v.c;
640           di.flags = 0;
641           di.f1 = fty.record.fc;
642           di.format = format;
643           data_in (&di);
644
645           for (iter = fty.recs_head; iter; iter = iter->next)
646             {
647               if (iter->flags & RCT_OTHER)
648                 goto found;
649               for (i = 0; i < iter->nv; i++)
650                 if (!memcmp (iter->v[i].c, v.c, fty.record.nc))
651                   goto found;
652             }
653           if (fty.wild)
654             msg (SW, _("Unknown record type \"%.*s\"."), fty.record.nc, v.c);
655         }
656       else
657         {
658           struct data_in di;
659
660           data_in_finite_line (&di, line, len,
661                                fty.record.fc, fty.record.fc + fty.record.nc);
662           di.v = &v;
663           di.flags = 0;
664           di.f1 = fty.record.fc;
665           di.format = format;
666           data_in (&di);
667
668           memcpy (&temp_case->data[fty.record.v->fv].f, &v.f, sizeof v.f);
669           for (iter = fty.recs_head; iter; iter = iter->next)
670             {
671               if (iter->flags & RCT_OTHER)
672                 goto found;
673               for (i = 0; i < iter->nv; i++)
674                 if (approx_eq (iter->v[i].f, v.f))
675                   goto found;
676             }
677           if (fty.wild)
678             msg (SW, _("Unknown record type %g."), v.f);
679         }
680       dfm_fwd_record (fty.handle);
681       continue;
682
683     found:
684       /* Arrive here if there is a matching record_type, which is in
685          iter. */
686       dfm_fwd_record (fty.handle);
687     }
688
689 /*  switch(fty.type)
690    {
691    case FTY_MIXED: read_from_file_type_mixed(); break;
692    case FTY_GROUPED: read_from_file_type_grouped(); break;
693    case FTY_NESTED: read_from_file_type_nested(); break;
694    default: assert(0);
695    } */
696
697   dfm_pop (fty.handle);
698 }
699
700 static void
701 file_type_source_destroy_source (void)
702 {
703   struct record_type *iter, *next;
704
705   cancel_transformations ();
706   for (iter = fty.recs_head; iter; iter = next)
707     {
708       next = iter->next;
709       free (iter);
710     }
711 }
712
713 struct case_stream file_type_source =
714   {
715     NULL,
716     file_type_source_read,
717     NULL,
718     NULL,
719     file_type_source_destroy_source,
720     NULL,
721     "FILE TYPE",
722   };