Fix compiler warnings on 64 bit machines
[pspp] / tests / data / sack.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2011, 2013, 2014 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <ctype.h>
20 #include <errno.h>
21 #include <float.h>
22 #include <getopt.h>
23 #include <limits.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28
29 #include "libpspp/assertion.h"
30 #include "libpspp/compiler.h"
31 #include "libpspp/float-format.h"
32 #include "libpspp/hash-functions.h"
33 #include "libpspp/hmap.h"
34 #include "libpspp/integer-format.h"
35
36 #include "gl/c-ctype.h"
37 #include "gl/error.h"
38 #include "gl/intprops.h"
39 #include "gl/progname.h"
40 #include "gl/xalloc.h"
41
42 struct buffer
43   {
44     uint8_t *data;
45     size_t size;
46     size_t allocated;
47   };
48
49 static void buffer_put (struct buffer *, const void *, size_t);
50 static void *buffer_put_uninit (struct buffer *, size_t);
51
52 enum token_type
53   {
54     T_EOF,
55     T_INTEGER,
56     T_FLOAT,
57     T_PCSYSMIS,
58     T_STRING,
59     T_SEMICOLON,
60     T_ASTERISK,
61     T_LPAREN,
62     T_RPAREN,
63     T_I8,
64     T_I16,
65     T_I64,
66     T_S,
67     T_COUNT,
68     T_COUNT8,
69     T_HEX,
70     T_LABEL,
71     T_AT,
72     T_MINUS,
73     T_PLUS,
74   };
75
76 static enum token_type token;
77 static unsigned long long int tok_integer;
78 static double tok_float;
79 static char *tok_string;
80 static size_t tok_strlen, tok_allocated;
81
82 /* Symbol table. */
83 struct symbol
84   {
85     struct hmap_node hmap_node;
86     const char *name;
87     unsigned int offset;
88   };
89
90 static struct hmap symbol_table = HMAP_INITIALIZER (symbol_table);
91
92 /* --be, --le: Integer and floating-point formats. */
93 static enum float_format float_format = FLOAT_IEEE_DOUBLE_BE;
94 static enum integer_format integer_format = INTEGER_MSB_FIRST;
95
96 /* Input file and current position. */
97 static FILE *input;
98 static const char *input_file_name;
99 static int line_number;
100
101 static void PRINTF_FORMAT (1, 2)
102 fatal (const char *message, ...)
103 {
104   va_list args;
105
106   fprintf (stderr, "%s:%d: ", input_file_name, line_number);
107   va_start (args, message);
108   vfprintf (stderr, message, args);
109   va_end (args);
110   putc ('\n', stderr);
111
112   exit (EXIT_FAILURE);
113 }
114
115 static void
116 add_char__ (int c)
117 {
118   if (tok_strlen >= tok_allocated)
119     tok_string = x2realloc (tok_string, &tok_allocated);
120
121   tok_string[tok_strlen] = c;
122 }
123
124 static void
125 add_char (int c)
126 {
127   add_char__ (c);
128   tok_strlen++;
129 }
130
131 static void
132 get_token (void)
133 {
134   int c;
135
136   do
137     {
138       c = getc (input);
139       if (c == '#')
140         {
141           while ((c = getc (input)) != '\n' && c != EOF)
142             continue;
143         }
144       if (c == '\n')
145         line_number++;
146     }
147   while (isspace (c) || c == '<' || c == '>');
148
149   tok_strlen = 0;
150   if (c == EOF)
151     {
152       if (token == T_EOF)
153         fatal ("unexpected end of input");
154       token = T_EOF;
155     }
156   else if (isdigit (c) || c == '-')
157     {
158       do
159         {
160           add_char (c);
161           c = getc (input);
162         }
163       while (isdigit (c) || isalpha (c) || c == '.');
164       add_char__ ('\0');
165       ungetc (c, input);
166
167       if (!strcmp (tok_string, "-"))
168         token = T_MINUS;
169       else
170         {
171           char *tail;
172
173           errno = 0;
174           if (strchr (tok_string, '.') == NULL)
175             {
176               token = T_INTEGER;
177               tok_integer = strtoull (tok_string, &tail, 0);
178             }
179           else
180             {
181               token = T_FLOAT;
182               tok_float = strtod (tok_string, &tail);
183             }
184           if (errno || *tail)
185             fatal ("invalid numeric syntax \"%s\"", tok_string);
186         }
187     }
188   else if (c == '"')
189     {
190       token = T_STRING;
191       while ((c = getc (input)) != '"')
192         {
193           if (c == '\n')
194             fatal ("new-line inside string");
195           add_char (c);
196         }
197       add_char__ ('\0');
198     }
199   else if (c == ';')
200     token = T_SEMICOLON;
201   else if (c == '*')
202     token = T_ASTERISK;
203   else if (c == '+')
204     token = T_PLUS;
205   else if (c == '(')
206     token = T_LPAREN;
207   else if (c == ')')
208     token = T_RPAREN;
209   else if (isalpha (c) || c == '@' || c == '_')
210     {
211       do
212         {
213           add_char (c);
214           c = getc (input);
215         }
216       while (isdigit (c) || isalpha (c) || c == '.' || c == '_');
217       add_char ('\0');
218
219       if (c == ':')
220         {
221           token = T_LABEL;
222           return;
223         }
224       ungetc (c, input);
225       if (tok_string[0] == '@')
226         {
227           token = T_AT;
228           return;
229         }
230
231       if (!strcmp (tok_string, "i8"))
232         token = T_I8;
233       else if (!strcmp (tok_string, "i16"))
234         token = T_I16;
235       else if (!strcmp (tok_string, "i64"))
236         token = T_I64;
237       else if (tok_string[0] == 's')
238         {
239           token = T_S;
240           tok_integer = atoi (tok_string + 1);
241         }
242       else if (!strcmp (tok_string, "SYSMIS"))
243         {
244           token = T_FLOAT;
245           tok_float = -DBL_MAX;
246         }
247       else if (!strcmp (tok_string, "PCSYSMIS"))
248         token = T_PCSYSMIS;
249       else if (!strcmp (tok_string, "LOWEST"))
250         {
251           token = T_FLOAT;
252           tok_float = float_get_lowest ();
253         }
254       else if (!strcmp (tok_string, "HIGHEST"))
255         {
256           token = T_FLOAT;
257           tok_float = DBL_MAX;
258         }
259       else if (!strcmp (tok_string, "ENDIAN"))
260         {
261           token = T_INTEGER;
262           tok_integer = integer_format == INTEGER_MSB_FIRST ? 1 : 2;
263         }
264       else if (!strcmp (tok_string, "COUNT"))
265         token = T_COUNT;
266       else if (!strcmp (tok_string, "COUNT8"))
267         token = T_COUNT8;
268       else if (!strcmp (tok_string, "hex"))
269         token = T_HEX;
270       else
271         fatal ("invalid token `%s'", tok_string);
272     }
273   else
274     fatal ("invalid input byte `%c'", c);
275 }
276
277 static void
278 buffer_put (struct buffer *buffer, const void *data, size_t n)
279 {
280   memcpy (buffer_put_uninit (buffer, n), data, n);
281 }
282
283 static void *
284 buffer_put_uninit (struct buffer *buffer, size_t n)
285 {
286   buffer->size += n;
287   if (buffer->size > buffer->allocated)
288     {
289       buffer->allocated = buffer->size * 2;
290       buffer->data = xrealloc (buffer->data, buffer->allocated);
291     }
292   return &buffer->data[buffer->size - n];
293 }
294
295 /* Returns the integer value of hex digit C. */
296 static int
297 hexit_value (int c)
298 {
299   const char s[] = "0123456789abcdef";
300   const char *cp = strchr (s, c_tolower ((unsigned char) c));
301
302   assert (cp != NULL);
303   return cp - s;
304 }
305
306 static void
307 usage (void)
308 {
309   printf ("\
310 %s, SAv Construction Kit\n\
311 usage: %s [OPTIONS] INPUT\n\
312 \nOptions:\n\
313   --be     big-endian output format (default)\n\
314   --le     little-endian output format\n\
315   --help   print this help message and exit\n\
316 \n\
317 The input is a sequence of data items, each followed by a semicolon.\n\
318 Each data item is converted to the output format and written on\n\
319 stdout.  A data item is one of the following\n\
320 \n\
321   - An integer in decimal, in hexadecimal prefixed by 0x, or in octal\n\
322     prefixed by 0.  Output as a 32-bit binary integer.\n\
323 \n\
324   - A floating-point number.  Output in 64-bit IEEE 754 format.\n\
325 \n\
326   - A string enclosed in double quotes.  Output literally.  There is\n\
327     no syntax for \"escapes\".  Strings may not contain new-lines.\n\
328 \n\
329   - A literal of the form s<number> followed by a quoted string as\n\
330     above.  Output as the string's contents followed by enough spaces\n\
331     to fill up <number> bytes.  For example, s8 \"foo\" is output as\n\
332     the \"foo\" followed by 5 spaces.\n\
333 \n\
334   - The literal \"i8\", \"i16\", or \"i64\" followed by an integer.  Output\n\
335     as a binary integer with the specified number of bits.\n\
336 \n\
337   - One of the literals SYSMIS, LOWEST, or HIGHEST.  Output as a\n\
338     64-bit IEEE 754 float of the appropriate PSPP value.\n\
339 \n\
340   - PCSYSMIS.  Output as SPSS/PC+ system-missing value.\n\
341 \n\
342   - The literal ENDIAN.  Output as a 32-bit binary integer, either\n\
343     with value 1 if --be is in effect or 2 if --le is in effect.\n\
344 \n\
345   - A pair of parentheses enclosing a sequence of data items, each\n\
346     followed by a semicolon (the last semicolon is optional).\n\
347     Output as the enclosed data items in sequence.\n\
348 \n\
349   - The literal COUNT or COUNT8 followed by a sequence of parenthesized\n\
350     data items, as above.  Output as a 32-bit or 8-bit binary integer whose\n\
351     value is the number of bytes enclosed within the parentheses, followed\n\
352     by the enclosed data items themselves.\n\
353 \n\
354 optionally followed by an asterisk and a positive integer, which\n\
355 specifies a repeat count for the data item.\n",
356           program_name, program_name);
357   exit (EXIT_SUCCESS);
358 }
359
360 static const char *
361 parse_options (int argc, char **argv)
362 {
363   for (;;)
364     {
365       enum {
366         OPT_BE = UCHAR_MAX + 1,
367         OPT_LE,
368         OPT_HELP
369       };
370       static const struct option options[] =
371         {
372           {"be", no_argument, NULL, OPT_BE},
373           {"le", no_argument, NULL, OPT_LE},
374           {"help", no_argument, NULL, OPT_HELP},
375           {NULL, 0, NULL, 0},
376         };
377
378       int c = getopt_long (argc, argv, "", options, NULL);
379       if (c == -1)
380         break;
381
382       switch (c)
383         {
384         case OPT_BE:
385           float_format = FLOAT_IEEE_DOUBLE_BE;
386           integer_format = INTEGER_MSB_FIRST;
387           break;
388
389         case OPT_LE:
390           float_format = FLOAT_IEEE_DOUBLE_LE;
391           integer_format = INTEGER_LSB_FIRST;
392           break;
393
394         case OPT_HELP:
395           usage ();
396
397         case 0:
398           break;
399
400         case '?':
401           exit (EXIT_FAILURE);
402           break;
403
404         default:
405           NOT_REACHED ();
406         }
407
408     }
409
410   if (optind + 1 != argc)
411     error (1, 0, "exactly one non-option argument required; "
412            "use --help for help");
413   return argv[optind];
414 }
415
416 static struct symbol *
417 symbol_find (const char *name)
418 {
419   struct symbol *symbol;
420   unsigned int hash;
421
422   if (name[0] == '@')
423     name++;
424   hash = hash_string (name, 0);
425   HMAP_FOR_EACH_WITH_HASH (symbol, struct symbol, hmap_node,
426                            hash, &symbol_table)
427     if (!strcmp (name, symbol->name))
428       return symbol;
429
430   symbol = xmalloc (sizeof *symbol);
431   hmap_insert (&symbol_table, &symbol->hmap_node, hash);
432   symbol->name = xstrdup (name);
433   symbol->offset = UINT_MAX;
434   return symbol;
435 }
436
437 static void
438 parse_data_item (struct buffer *output)
439 {
440   size_t old_size = output->size;
441
442   if (token == T_INTEGER)
443     {
444       integer_put (tok_integer, integer_format,
445                    buffer_put_uninit (output, 4), 4);
446       get_token ();
447     }
448   else if (token == T_FLOAT)
449     {
450       float_convert (FLOAT_NATIVE_DOUBLE, &tok_float,
451                      float_format, buffer_put_uninit (output, 8));
452       get_token ();
453     }
454   else if (token == T_PCSYSMIS)
455     {
456       static const uint8_t pcsysmis[] =
457         { 0xf5, 0x1e, 0x26, 0x02, 0x8a, 0x8c, 0xed, 0xff, };
458       buffer_put (output, pcsysmis, sizeof pcsysmis);
459       get_token ();
460     }
461   else if (token == T_I8)
462     {
463       uint8_t byte;
464
465       get_token ();
466       do
467         {
468           if (token != T_INTEGER)
469             fatal ("integer expected after `i8'");
470           byte = tok_integer;
471           buffer_put (output, &byte, 1);
472           get_token ();
473         }
474       while (token == T_INTEGER);
475     }
476   else if (token == T_I16)
477     {
478       get_token ();
479       do
480         {
481           if (token != T_INTEGER)
482             fatal ("integer expected after `i16'");
483           integer_put (tok_integer, integer_format,
484                        buffer_put_uninit (output, 2), 2);
485           get_token ();
486         }
487       while (token == T_INTEGER);
488     }
489   else if (token == T_I64)
490     {
491       get_token ();
492       do
493         {
494           if (token != T_INTEGER)
495             fatal ("integer expected after `i64'");
496           integer_put (tok_integer, integer_format,
497                        buffer_put_uninit (output, 8), 8);
498           get_token ();
499         }
500       while (token == T_INTEGER);
501     }
502   else if (token == T_STRING)
503     {
504       buffer_put (output, tok_string, tok_strlen);
505       get_token ();
506     }
507   else if (token == T_S)
508     {
509       int n;
510
511       n = tok_integer;
512       get_token ();
513
514       if (token != T_STRING)
515         fatal ("string expected");
516       if (tok_strlen > n)
517         fatal ("%zu-byte string is longer than pad length %d",
518                tok_strlen, n);
519
520       buffer_put (output, tok_string, tok_strlen);
521       memset (buffer_put_uninit (output, n - tok_strlen), ' ',
522               n - tok_strlen);
523       get_token ();
524     }
525   else if (token == T_LPAREN)
526     {
527       get_token ();
528
529       while (token != T_RPAREN)
530         parse_data_item (output);
531
532       get_token ();
533     }
534   else if (token == T_COUNT)
535     {
536       buffer_put_uninit (output, 4);
537
538       get_token ();
539       if (token != T_LPAREN)
540         fatal ("`(' expected after COUNT");
541       get_token ();
542
543       while (token != T_RPAREN)
544         parse_data_item (output);
545       get_token ();
546
547       integer_put (output->size - old_size - 4, integer_format,
548                    output->data + old_size, 4);
549     }
550   else if (token == T_COUNT8)
551     {
552       buffer_put_uninit (output, 1);
553
554       get_token ();
555       if (token != T_LPAREN)
556         fatal ("`(' expected after COUNT8");
557       get_token ();
558
559       while (token != T_RPAREN)
560         parse_data_item (output);
561       get_token ();
562
563       integer_put (output->size - old_size - 1, integer_format,
564                    output->data + old_size, 1);
565     }
566   else if (token == T_HEX)
567     {
568       const char *p;
569
570       get_token ();
571
572       if (token != T_STRING)
573         fatal ("string expected");
574
575       for (p = tok_string; *p; p++)
576         {
577           if (isspace ((unsigned char) *p))
578             continue;
579           else if (isxdigit ((unsigned char) p[0])
580                    && isxdigit ((unsigned char) p[1]))
581             {
582               int high = hexit_value (p[0]);
583               int low = hexit_value (p[1]);
584               uint8_t byte = high * 16 + low;
585               buffer_put (output, &byte, 1);
586               p++;
587             }
588           else
589             fatal ("invalid format in hex string");
590         }
591       get_token ();
592     }
593   else if (token == T_LABEL)
594     {
595       struct symbol *sym = symbol_find (tok_string);
596       if (sym->offset == UINT_MAX)
597         sym->offset = output->size;
598       else if (sym->offset != output->size)
599         fatal ("%s: can't redefine label for offset %u with offset %zu",
600                tok_string, sym->offset, output->size);
601       get_token ();
602       return;
603     }
604   else if (token == T_AT)
605     {
606       unsigned int value = symbol_find (tok_string)->offset;
607       get_token ();
608
609       while (token == T_MINUS || token == T_PLUS)
610         {
611           enum token_type op = token;
612           unsigned int operand;
613           get_token ();
614           if (token == T_AT)
615             operand = symbol_find (tok_string)->offset;
616           else if (token == T_INTEGER)
617             operand = tok_integer;
618           else
619             fatal ("expecting @label");
620           get_token ();
621
622           if (op == T_PLUS)
623             value += operand;
624           else
625             value -= operand;
626         }
627       integer_put (value, integer_format, buffer_put_uninit (output, 4), 4);
628     }
629   else
630     fatal ("syntax error");
631
632   if (token == T_ASTERISK)
633     {
634       size_t n = output->size - old_size;
635       char *p;
636
637       get_token ();
638
639       if (token != T_INTEGER || tok_integer < 1)
640         fatal ("positive integer expected after `*'");
641       p = buffer_put_uninit (output, (tok_integer - 1) * n);
642       while (--tok_integer > 0)
643         {
644           memcpy (p, output->data + old_size, n);
645           p += n;
646         }
647
648       get_token ();
649     }
650
651   if (token == T_SEMICOLON)
652     get_token ();
653   else if (token != T_RPAREN)
654     fatal ("`;' expected");
655 }
656
657 int
658 main (int argc, char **argv)
659 {
660   struct buffer output;
661
662   set_program_name (argv[0]);
663   input_file_name = parse_options (argc, argv);
664
665   if (!strcmp (input_file_name, "-"))
666     input = stdin;
667   else
668     {
669       input = fopen (input_file_name, "r");
670       if (input == NULL)
671         error (1, errno, "%s: open failed", input_file_name);
672     }
673
674   if (isatty (STDOUT_FILENO))
675     error (1, 0, "not writing binary data to a terminal; redirect to a file");
676
677   output.data = NULL;
678   output.size = 0;
679   output.allocated = 0;
680
681   line_number = 1;
682   get_token ();
683   while (token != T_EOF)
684     parse_data_item (&output);
685
686   if (!hmap_is_empty (&symbol_table))
687     {
688       struct symbol *symbol;
689
690       HMAP_FOR_EACH (symbol, struct symbol, hmap_node, &symbol_table)
691         if (symbol->offset == UINT_MAX)
692           error (1, 0, "label %s used but never defined", symbol->name);
693
694       output.size = 0;
695       if (fseek (input, 0, SEEK_SET) != 0)
696         error (1, 0, "failed to rewind stdin for second pass");
697
698       line_number = 1;
699       get_token ();
700       while (token != T_EOF)
701         parse_data_item (&output);
702     }
703
704   if (input != stdin)
705     fclose (input);
706
707   fwrite (output.data, output.size, 1, stdout);
708
709   return 0;
710 }