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