0326b1370d255b693d0a4321feeafbb4eec65776
[pspp] / tests / data / sack.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2011, 2013 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/integer-format.h"
33
34 #include "gl/c-ctype.h"
35 #include "gl/error.h"
36 #include "gl/md5.h"
37 #include "gl/intprops.h"
38 #include "gl/progname.h"
39 #include "gl/xalloc.h"
40
41 struct buffer
42   {
43     uint8_t *data;
44     size_t size;
45     size_t allocated;
46   };
47
48 static void buffer_put (struct buffer *, const void *, size_t);
49 static void *buffer_put_uninit (struct buffer *, size_t);
50
51 enum token_type
52   {
53     T_EOF,
54     T_INTEGER,
55     T_FLOAT,
56     T_STRING,
57     T_SEMICOLON,
58     T_ASTERISK,
59     T_LPAREN,
60     T_RPAREN,
61     T_I8,
62     T_I64,
63     T_S,
64     T_COUNT,
65     T_HEX
66   };
67
68 static enum token_type token;
69 static unsigned long long int tok_integer;
70 static double tok_float;
71 static char *tok_string;
72 static size_t tok_strlen, tok_allocated;
73
74 /* --be, --le: Integer and floating-point formats. */
75 static enum float_format float_format = FLOAT_IEEE_DOUBLE_BE;
76 static enum integer_format integer_format = INTEGER_MSB_FIRST;
77
78 /* Input file and current position. */
79 static FILE *input;
80 static const char *input_file_name;
81 static int line_number;
82
83 static void PRINTF_FORMAT (1, 2)
84 fatal (const char *message, ...)
85 {
86   va_list args;
87
88   fprintf (stderr, "%s:%d: ", input_file_name, line_number);
89   va_start (args, message);
90   vfprintf (stderr, message, args);
91   va_end (args);
92   putc ('\n', stderr);
93
94   exit (EXIT_FAILURE);
95 }
96
97 static void
98 add_char__ (int c)
99 {
100   if (tok_strlen >= tok_allocated)
101     tok_string = x2realloc (tok_string, &tok_allocated);
102
103   tok_string[tok_strlen] = c;
104 }
105
106 static void
107 add_char (int c)
108 {
109   add_char__ (c);
110   tok_strlen++;
111 }
112
113 static void
114 get_token (void)
115 {
116   int c;
117
118   do
119     {
120       c = getc (input);
121       if (c == '#')
122         {
123           while ((c = getc (input)) != '\n' && c != EOF)
124             continue;
125         }
126       if (c == '\n')
127         line_number++;
128     }
129   while (isspace (c) || c == '<' || c == '>');
130
131   tok_strlen = 0;
132   if (c == EOF)
133     {
134       if (token == T_EOF)
135         fatal ("unexpected end of input");
136       token = T_EOF;
137     }
138   else if (isdigit (c) || c == '-')
139     {
140       char *tail;
141
142       do
143         {
144           add_char (c);
145           c = getc (input);
146         }
147       while (isdigit (c) || isalpha (c) || c == '.');
148       add_char__ ('\0');
149       ungetc (c, input);
150
151       errno = 0;
152       if (strchr (tok_string, '.') == NULL)
153         {
154           token = T_INTEGER;
155           tok_integer = strtoull (tok_string, &tail, 0);
156         }
157       else
158         {
159           token = T_FLOAT;
160           tok_float = strtod (tok_string, &tail);
161         }
162       if (errno || *tail)
163         fatal ("invalid numeric syntax");
164     }
165   else if (c == '"')
166     {
167       token = T_STRING;
168       while ((c = getc (input)) != '"')
169         {
170           if (c == '\n')
171             fatal ("new-line inside string");
172           add_char (c);
173         }
174       add_char__ ('\0');
175     }
176   else if (c == ';')
177     token = T_SEMICOLON;
178   else if (c == '*')
179     token = T_ASTERISK;
180   else if (c == '(')
181     token = T_LPAREN;
182   else if (c == ')')
183     token = T_RPAREN;
184   else if (isalpha (c))
185     {
186       do
187         {
188           add_char (c);
189           c = getc (input);
190         }
191       while (isdigit (c) || isalpha (c) || c == '.');
192       add_char ('\0');
193       ungetc (c, input);
194
195       if (!strcmp (tok_string, "i8"))
196         token = T_I8;
197       else if (!strcmp (tok_string, "i64"))
198         token = T_I64;
199       else if (tok_string[0] == 's')
200         {
201           token = T_S;
202           tok_integer = atoi (tok_string + 1);
203         }
204       else if (!strcmp (tok_string, "SYSMIS"))
205         {
206           token = T_FLOAT;
207           tok_float = -DBL_MAX;
208         }
209       else if (!strcmp (tok_string, "LOWEST"))
210         {
211           token = T_FLOAT;
212           tok_float = float_get_lowest ();
213         }
214       else if (!strcmp (tok_string, "HIGHEST"))
215         {
216           token = T_FLOAT;
217           tok_float = DBL_MAX;
218         }
219       else if (!strcmp (tok_string, "ENDIAN"))
220         {
221           token = T_INTEGER;
222           tok_integer = integer_format == INTEGER_MSB_FIRST ? 1 : 2;
223         }
224       else if (!strcmp (tok_string, "COUNT"))
225         token = T_COUNT;
226       else if (!strcmp (tok_string, "hex"))
227         token = T_HEX;
228       else
229         fatal ("invalid token `%s'", tok_string);
230     }
231   else
232     fatal ("invalid input byte `%c'", c);
233 }
234
235 static void
236 buffer_put (struct buffer *buffer, const void *data, size_t n)
237 {
238   memcpy (buffer_put_uninit (buffer, n), data, n);
239 }
240
241 static void *
242 buffer_put_uninit (struct buffer *buffer, size_t n)
243 {
244   buffer->size += n;
245   if (buffer->size > buffer->allocated)
246     {
247       buffer->allocated = buffer->size * 2;
248       buffer->data = xrealloc (buffer->data, buffer->allocated);
249     }
250   return &buffer->data[buffer->size - n];
251 }
252
253 /* Returns the integer value of hex digit C. */
254 static int
255 hexit_value (int c)
256 {
257   const char s[] = "0123456789abcdef";
258   const char *cp = strchr (s, c_tolower ((unsigned char) c));
259
260   assert (cp != NULL);
261   return cp - s;
262 }
263
264 static void
265 usage (void)
266 {
267   printf ("\
268 %s, SAv Construction Kit\n\
269 usage: %s [OPTIONS] INPUT\n\
270 \nOptions:\n\
271   --be     big-endian output format (default)\n\
272   --le     little-endian output format\n\
273   --help   print this help message and exit\n\
274 \n\
275 The input is a sequence of data items, each followed by a semicolon.\n\
276 Each data item is converted to the output format and written on\n\
277 stdout.  A data item is one of the following\n\
278 \n\
279   - An integer in decimal, in hexadecimal prefixed by 0x, or in octal\n\
280     prefixed by 0.  Output as a 32-bit binary integer.\n\
281 \n\
282   - A floating-point number.  Output in 64-bit IEEE 754 format.\n\
283 \n\
284   - A string enclosed in double quotes.  Output literally.  There is\n\
285     no syntax for \"escapes\".  Strings may not contain new-lines.\n\
286 \n\
287   - A literal of the form s<number> followed by a quoted string as\n\
288     above.  Output as the string's contents followed by enough spaces\n\
289     to fill up <number> bytes.  For example, s8 \"foo\" is output as\n\
290     the \"foo\" followed by 5 spaces.\n\
291 \n\
292   - The literal \"i8\" followed by an integer.  Output as a single\n\
293     byte with the specified value.\n\
294 \n\
295   - The literal \"i64\" followed by an integer.  Output as a 64-bit\n\
296     binary integer.\n\
297 \n\
298   - One of the literals SYSMIS, LOWEST, or HIGHEST.  Output as a\n\
299     64-bit IEEE 754 float of the appropriate PSPP value.\n\
300 \n\
301   - The literal ENDIAN.  Output as a 32-bit binary integer, either\n\
302     with value 1 if --be is in effect or 2 if --le is in effect.\n\
303 \n\
304   - A pair of parentheses enclosing a sequence of data items, each\n\
305     followed by a semicolon (the last semicolon is optional).\n\
306     Output as the enclosed data items in sequence.\n\
307 \n\
308   - The literal COUNT followed by a sequence of parenthesized data\n\
309     items, as above.  Output as a 32-bit binary integer whose value\n\
310     is the number of bytes enclosed within the parentheses, followed\n\
311     by the enclosed data items themselves.\n\
312 \n\
313 optionally followed by an asterisk and a positive integer, which\n\
314 specifies a repeat count for the data item.\n\
315 \n\
316 The md5sum of the data written to stdout is written to stderr as\n\
317 16 hexadecimal digits followed by a new-line.\n",
318           program_name, program_name);
319   exit (EXIT_SUCCESS);
320 }
321
322 static const char *
323 parse_options (int argc, char **argv)
324 {
325   for (;;)
326     {
327       enum {
328         OPT_BE = UCHAR_MAX + 1,
329         OPT_LE,
330         OPT_HELP
331       };
332       static const struct option options[] =
333         {
334           {"be", no_argument, NULL, OPT_BE},
335           {"le", no_argument, NULL, OPT_LE},
336           {"help", no_argument, NULL, OPT_HELP},
337           {NULL, 0, NULL, 0},
338         };
339
340       int c = getopt_long (argc, argv, "", options, NULL);
341       if (c == -1)
342         break;
343
344       switch (c)
345         {
346         case OPT_BE:
347           float_format = FLOAT_IEEE_DOUBLE_BE;
348           integer_format = INTEGER_MSB_FIRST;
349           break;
350
351         case OPT_LE:
352           float_format = FLOAT_IEEE_DOUBLE_LE;
353           integer_format = INTEGER_LSB_FIRST;
354           break;
355
356         case OPT_HELP:
357           usage ();
358
359         case 0:
360           break;
361
362         case '?':
363           exit (EXIT_FAILURE);
364           break;
365
366         default:
367           NOT_REACHED ();
368         }
369
370     }
371
372   if (optind + 1 != argc)
373     error (1, 0, "exactly one non-option argument required; "
374            "use --help for help");
375   return argv[optind];
376 }
377
378 static void
379 parse_data_item (struct buffer *output)
380 {
381   size_t old_size = output->size;
382
383   if (token == T_INTEGER)
384     {
385       integer_put (tok_integer, integer_format,
386                    buffer_put_uninit (output, 4), 4);
387       get_token ();
388     }
389   else if (token == T_FLOAT)
390     {
391       float_convert (FLOAT_NATIVE_DOUBLE, &tok_float,
392                      float_format, buffer_put_uninit (output, 8));
393       get_token ();
394     }
395   else if (token == T_I8)
396     {
397       uint8_t byte;
398
399       get_token ();
400       do
401         {
402           if (token != T_INTEGER)
403             fatal ("integer expected after `i8'");
404           byte = tok_integer;
405           buffer_put (output, &byte, 1);
406           get_token ();
407         }
408       while (token == T_INTEGER);
409     }
410   else if (token == T_I64)
411     {
412       get_token ();
413       do
414         {
415           if (token != T_INTEGER)
416             fatal ("integer expected after `i64'");
417           integer_put (tok_integer, integer_format,
418                        buffer_put_uninit (output, 8), 8);
419           get_token ();
420         }
421       while (token == T_INTEGER);
422     }
423   else if (token == T_STRING)
424     {
425       buffer_put (output, tok_string, tok_strlen);
426       get_token ();
427     }
428   else if (token == T_S)
429     {
430       int n;
431
432       n = tok_integer;
433       get_token ();
434
435       if (token != T_STRING)
436         fatal ("string expected");
437       if (tok_strlen > n)
438         fatal ("%zu-byte string is longer than pad length %d",
439                tok_strlen, n);
440
441       buffer_put (output, tok_string, tok_strlen);
442       memset (buffer_put_uninit (output, n - tok_strlen), ' ',
443               n - tok_strlen);
444       get_token ();
445     }
446   else if (token == T_LPAREN)
447     {
448       get_token ();
449
450       while (token != T_RPAREN)
451         parse_data_item (output);
452
453       get_token ();
454     }
455   else if (token == T_COUNT)
456     {
457       buffer_put_uninit (output, 4);
458
459       get_token ();
460       if (token != T_LPAREN)
461         fatal ("`(' expected after COUNT");
462       get_token ();
463
464       while (token != T_RPAREN)
465         parse_data_item (output);
466       get_token ();
467
468       integer_put (output->size - old_size - 4, integer_format,
469                    output->data + old_size, 4);
470     }
471   else if (token == T_HEX)
472     {
473       const char *p;
474
475       get_token ();
476
477       if (token != T_STRING)
478         fatal ("string expected");
479
480       for (p = tok_string; *p; p++)
481         {
482           if (isspace ((unsigned char) *p))
483             continue;
484           else if (isxdigit ((unsigned char) p[0])
485                    && isxdigit ((unsigned char) p[1]))
486             {
487               int high = hexit_value (p[0]);
488               int low = hexit_value (p[1]);
489               uint8_t byte = high * 16 + low;
490               buffer_put (output, &byte, 1);
491               p++;
492             }
493           else
494             fatal ("invalid format in hex string");
495         }
496       get_token ();
497     }
498   else
499     fatal ("syntax error");
500
501   if (token == T_ASTERISK)
502     {
503       size_t n = output->size - old_size;
504       char *p;
505
506       get_token ();
507
508       if (token != T_INTEGER || tok_integer < 1)
509         fatal ("positive integer expected after `*'");
510       p = buffer_put_uninit (output, (tok_integer - 1) * n);
511       while (--tok_integer > 0)
512         {
513           memcpy (p, output->data + old_size, n);
514           p += n;
515         }
516
517       get_token ();
518     }
519
520   if (token == T_SEMICOLON)
521     get_token ();
522   else if (token != T_RPAREN)
523     fatal ("`;' expected");
524 }
525
526 int
527 main (int argc, char **argv)
528 {
529   struct buffer output;
530   uint8_t digest[16];
531   int i;
532
533   set_program_name (argv[0]);
534   input_file_name = parse_options (argc, argv);
535
536   if (!strcmp (input_file_name, "-"))
537     input = stdin;
538   else
539     {
540       input = fopen (input_file_name, "r");
541       if (input == NULL)
542         error (1, errno, "%s: open failed", input_file_name);
543     }
544
545   if (isatty (STDOUT_FILENO))
546     error (1, 0, "not writing binary data to a terminal; redirect to a file");
547
548   output.data = NULL;
549   output.size = 0;
550   output.allocated = 0;
551
552   line_number = 1;
553   get_token ();
554   while (token != T_EOF)
555     parse_data_item (&output);
556
557   if (input != stdin)
558     fclose (input);
559
560   fwrite (output.data, output.size, 1, stdout);
561
562   md5_buffer ((const char *) output.data, output.size, digest);
563   for (i = 0; i < sizeof digest; i++)
564     fprintf (stderr, "%02x", digest[i]);
565   putc ('\n', stderr);
566
567   return 0;
568 }