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