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