checkin of 0.3.0
[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 type;
288   int width;
289
290   type = (formats[c->fmt].cat & FCAT_STRING) ? ALPHA : NUMERIC;
291   if (type == ALPHA)
292     width = c->nc;
293   else
294     width = 0;
295   c->v = force_create_variable (&default_dict, c->name, type, width);
296 }
297
298 /* Parses variable, column, type specifications for a variable. */
299 static int
300 parse_col_spec (struct col_spec *c, const char *def_name)
301 {
302   struct fmt_spec spec;
303
304   if (token == T_ID)
305     {
306       strcpy (c->name, tokid);
307       lex_get ();
308     }
309   else
310     strcpy (c->name, def_name);
311
312   if (!lex_force_int ())
313     return 0;
314   c->fc = lex_integer ();
315   if (c->fc < 1)
316     {
317       msg (SE, _("Column value must be positive."));
318       return 0;
319     }
320   lex_get ();
321
322   lex_negative_to_dash ();
323   if (lex_match ('-'))
324     {
325       if (!lex_force_int ())
326         return 0;
327       c->nc = lex_integer ();
328       lex_get ();
329
330       if (c->nc < c->fc)
331         {
332           msg (SE, _("Ending column precedes beginning column."));
333           return 0;
334         }
335       
336       c->nc -= c->fc - 1;
337     }
338   else
339     c->nc = 1;
340
341   if (lex_match ('('))
342     {
343       const char *cp;
344       if (!lex_force_id ())
345         return 0;
346       c->fmt = parse_format_specifier_name (&cp, 0);
347       if (c->fmt == -1)
348         return 0;
349       if (*cp)
350         {
351           msg (SE, _("Bad format specifier name."));
352           return 0;
353         }
354       lex_get ();
355       if (!lex_force_match (')'))
356         return 0;
357     }
358   else
359     c->fmt = FMT_F;
360
361   spec.type = c->fmt;
362   spec.w = c->nc;
363   spec.d = 0;
364   return check_input_specifier (&spec);
365 }
366 \f
367 /* RECORD TYPE. */
368
369 /* Structure being filled in by internal_cmd_record_type. */
370 static struct record_type rct;
371
372 static int internal_cmd_record_type (void);
373
374 /* Parse the RECORD TYPE command. */
375 int
376 cmd_record_type (void)
377 {
378   int result = internal_cmd_record_type ();
379
380   if (result == CMD_FAILURE)
381     {
382       int i;
383
384       if (formats[fty.record.fmt].cat & FCAT_STRING)
385         for (i = 0; i < rct.nv; i++)
386           free (rct.v[i].c);
387       free (rct.v);
388     }
389
390   return result;
391 }
392
393 static int
394 internal_cmd_record_type (void)
395 {
396   /* Initialize the record_type structure. */
397   rct.next = NULL;
398   rct.flags = 0;
399   if (fty.duplicate)
400     rct.flags |= RCT_DUPLICATE;
401   if (fty.missing)
402     rct.flags |= RCT_MISSING;
403   rct.v = NULL;
404   rct.nv = 0;
405   rct.ft = n_trns;
406   if (fty.case_sbc.name[0])
407     rct.case_sbc = fty.case_sbc;
408 #if __CHECKER__
409   else
410     memset (&rct.case_sbc, 0, sizeof rct.case_sbc);
411   rct.lt = -1;
412 #endif
413
414   /* Make sure we're inside a FILE TYPE structure. */
415   if (pgm_state != STATE_INPUT || vfm_source != &file_type_source)
416     {
417       msg (SE, _("This command may only appear within a "
418                  "FILE TYPE/END FILE TYPE structure."));
419       return CMD_FAILURE;
420     }
421
422   if (fty.recs_tail && (fty.recs_tail->flags & RCT_OTHER))
423     {
424       msg (SE, _("OTHER may appear only on the last RECORD TYPE command."));
425       return CMD_FAILURE;
426     }
427       
428   if (fty.recs_tail)
429     {
430       fty.recs_tail->lt = n_trns - 1;
431       if (!(fty.recs_tail->flags & RCT_SKIP)
432           && fty.recs_tail->ft == fty.recs_tail->lt)
433         {
434           msg (SE, _("No input commands (DATA LIST, REPEATING DATA) "
435                      "for above RECORD TYPE."));
436           return CMD_FAILURE;
437         }
438     }
439
440   lex_match_id ("RECORD");
441   lex_match_id ("TYPE");
442
443   /* Parse record type values. */
444   if (lex_match_id ("OTHER"))
445     rct.flags |= RCT_OTHER;
446   else
447     {
448       int mv = 0;
449
450       while (token == T_NUM || token == T_STRING)
451         {
452           if (rct.nv >= mv)
453             {
454               mv += 16;
455               rct.v = xrealloc (rct.v, mv * sizeof *rct.v);
456             }
457
458           if (formats[fty.record.fmt].cat & FCAT_STRING)
459             {
460               if (!lex_force_string ())
461                 return CMD_FAILURE;
462               rct.v[rct.nv].c = xmalloc (fty.record.nc + 1);
463               st_bare_pad_copy (rct.v[rct.nv].c, ds_value (&tokstr),
464                                 fty.record.nc + 1);
465             }
466           else
467             {
468               if (!lex_force_num ())
469                 return CMD_FAILURE;
470               rct.v[rct.nv].f = tokval;
471             }
472           rct.nv++;
473           lex_get ();
474
475           lex_match (',');
476         }
477     }
478
479   /* Parse the rest of the subcommands. */
480   while (token != '.')
481     {
482       if (lex_match_id ("SKIP"))
483         rct.flags |= RCT_SKIP;
484       else if (lex_match_id ("CASE"))
485         {
486           if (fty.type == FTY_MIXED)
487             {
488               msg (SE, _("The CASE subcommand is not allowed on "
489                          "the RECORD TYPE command for FILE TYPE MIXED."));
490               return CMD_FAILURE;
491             }
492
493           lex_match ('=');
494           if (!parse_col_spec (&rct.case_sbc, ""))
495             return CMD_FAILURE;
496           if (rct.case_sbc.name[0])
497             {
498               msg (SE, _("No variable name may be specified for the "
499                          "CASE subcommand on RECORD TYPE."));
500               return CMD_FAILURE;
501             }
502           
503           if ((formats[rct.case_sbc.fmt].cat ^ formats[fty.case_sbc.fmt].cat)
504               & FCAT_STRING)
505             {
506               msg (SE, _("The CASE column specification on RECORD TYPE "
507                          "must give a format specifier that is the "
508                          "same type as that of the CASE column "
509                          "specification given on FILE TYPE."));
510               return CMD_FAILURE;
511             }
512         }
513       else if (lex_match_id ("DUPLICATE"))
514         {
515           lex_match ('=');
516           if (lex_match_id ("WARN"))
517             rct.flags |= RCT_DUPLICATE;
518           else if (lex_match_id ("NOWARN"))
519             rct.flags &= ~RCT_DUPLICATE;
520           else
521             {
522               msg (SE, _("WARN or NOWARN expected on DUPLICATE "
523                          "subcommand."));
524               return CMD_FAILURE;
525             }
526         }
527       else if (lex_match_id ("MISSING"))
528         {
529           lex_match ('=');
530           if (lex_match_id ("WARN"))
531             rct.flags |= RCT_MISSING;
532           else if (lex_match_id ("NOWARN"))
533             rct.flags &= ~RCT_MISSING;
534           else
535             {
536               msg (SE, _("WARN or NOWARN expected on MISSING subcommand."));
537               return CMD_FAILURE;
538             }
539         }
540       else if (lex_match_id ("SPREAD"))
541         {
542           lex_match ('=');
543           if (lex_match_id ("YES"))
544             rct.flags |= RCT_SPREAD;
545           else if (lex_match_id ("NO"))
546             rct.flags &= ~RCT_SPREAD;
547           else
548             {
549               msg (SE, _("YES or NO expected on SPREAD subcommand."));
550               return CMD_FAILURE;
551             }
552         }
553       else
554         {
555           lex_error (_("while expecting a valid subcommand"));
556           return CMD_FAILURE;
557         }
558     }
559
560   if (fty.recs_head)
561     fty.recs_tail = fty.recs_tail->next = xmalloc (sizeof *fty.recs_tail);
562   else
563     fty.recs_head = fty.recs_tail = xmalloc (sizeof *fty.recs_tail);
564   memcpy (fty.recs_tail, &rct, sizeof *fty.recs_tail);
565
566   return CMD_SUCCESS;
567 }
568 \f
569 /* END FILE TYPE. */
570
571 int
572 cmd_end_file_type (void)
573 {
574   if (pgm_state != STATE_INPUT || vfm_source != &file_type_source)
575     {
576       msg (SE, _("This command may only appear within a "
577                  "FILE TYPE/END FILE TYPE structure."));
578       return CMD_FAILURE;
579     }
580
581   lex_match_id ("TYPE");
582
583   if (fty.recs_tail)
584     {
585       fty.recs_tail->lt = n_trns - 1;
586       if (!(fty.recs_tail->flags & RCT_SKIP)
587           && fty.recs_tail->ft == fty.recs_tail->lt)
588         {
589           msg (SE, _("No input commands (DATA LIST, REPEATING DATA) "
590                      "on above RECORD TYPE."));
591           goto fail;
592         }
593     }
594   else
595     {
596       msg (SE, _("No commands between FILE TYPE and END FILE TYPE."));
597       goto fail;
598     }
599
600   f_trns = n_trns;
601
602   return lex_end_of_command ();
603
604  fail:
605   /* Come here on discovering catastrophic error. */
606   err_cond_fail ();
607   discard_variables ();
608   return CMD_FAILURE;
609 }
610 \f
611 /* FILE TYPE runtime. */
612
613 /*static void read_from_file_type_mixed(void);
614    static void read_from_file_type_grouped(void);
615    static void read_from_file_type_nested(void); */
616
617 /* Reads any number of cases into temp_case and calls write_case() for
618    each one.  Compare data-list.c:read_from_data_list. */
619 static void
620 file_type_source_read (void)
621 {
622   char *line;
623   int len;
624
625   struct fmt_spec format;
626
627   dfm_push (fty.handle);
628
629   format.type = fty.record.fmt;
630   format.w = fty.record.nc;
631   format.d = 0;
632   while (NULL != (line = dfm_get_record (fty.handle, &len)))
633     {
634       struct record_type *iter;
635       union value v;
636       int i;
637
638       if (formats[fty.record.fmt].cat & FCAT_STRING)
639         {
640           struct data_in di;
641           
642           v.c = temp_case->data[fty.record.v->fv].s;
643
644           data_in_finite_line (&di, line, len,
645                                fty.record.fc, fty.record.fc + fty.record.nc);
646           di.v = (union value *) v.c;
647           di.flags = 0;
648           di.f1 = fty.record.fc;
649           di.format = format;
650           data_in (&di);
651
652           for (iter = fty.recs_head; iter; iter = iter->next)
653             {
654               if (iter->flags & RCT_OTHER)
655                 goto found;
656               for (i = 0; i < iter->nv; i++)
657                 if (!memcmp (iter->v[i].c, v.c, fty.record.nc))
658                   goto found;
659             }
660           if (fty.wild)
661             msg (SW, _("Unknown record type \"%.*s\"."), fty.record.nc, v.c);
662         }
663       else
664         {
665           struct data_in di;
666
667           data_in_finite_line (&di, line, len,
668                                fty.record.fc, fty.record.fc + fty.record.nc);
669           di.v = &v;
670           di.flags = 0;
671           di.f1 = fty.record.fc;
672           di.format = format;
673           data_in (&di);
674
675           memcpy (&temp_case->data[fty.record.v->fv].f, &v.f, sizeof v.f);
676           for (iter = fty.recs_head; iter; iter = iter->next)
677             {
678               if (iter->flags & RCT_OTHER)
679                 goto found;
680               for (i = 0; i < iter->nv; i++)
681                 if (approx_eq (iter->v[i].f, v.f))
682                   goto found;
683             }
684           if (fty.wild)
685             msg (SW, _("Unknown record type %g."), v.f);
686         }
687       dfm_fwd_record (fty.handle);
688       continue;
689
690     found:
691       /* Arrive here if there is a matching record_type, which is in
692          iter. */
693       dfm_fwd_record (fty.handle);
694     }
695
696 /*  switch(fty.type)
697    {
698    case FTY_MIXED: read_from_file_type_mixed(); break;
699    case FTY_GROUPED: read_from_file_type_grouped(); break;
700    case FTY_NESTED: read_from_file_type_nested(); break;
701    default: assert(0);
702    } */
703
704   dfm_pop (fty.handle);
705 }
706
707 static void
708 file_type_source_destroy_source (void)
709 {
710   struct record_type *iter, *next;
711
712   cancel_transformations ();
713   for (iter = fty.recs_head; iter; iter = next)
714     {
715       next = iter->next;
716       free (iter);
717     }
718 }
719
720 struct case_stream file_type_source =
721   {
722     NULL,
723     file_type_source_read,
724     NULL,
725     NULL,
726     file_type_source_destroy_source,
727     NULL,
728     "FILE TYPE",
729   };