spv-file-format.texi: Import back from PSPP upstream.
[pspp] / dump2.c
1 #include <assert.h>
2 #include <float.h>
3 #include <stdbool.h>
4 #include <stdint.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <sys/stat.h>
9 #include <unistd.h>
10 #include "u8-mbtouc.h"
11
12 static uint8_t *data;
13 static size_t n, pos;
14
15 #define XSTR(x) #x
16 #define STR(x) XSTR(x)
17 #define WHERE __FILE__":" STR(__LINE__)
18
19 static unsigned int
20 get_u32(void)
21 {
22   uint32_t x;
23   memcpy(&x, &data[pos], 4);
24   pos += 4;
25   return x;
26 }
27
28 static double
29 get_double(void)
30 {
31   double x;
32   memcpy(&x, &data[pos], 8);
33   pos += 8;
34   return x;
35 }
36
37 static bool
38 match_u32(uint32_t x)
39 {
40   if (get_u32() == x)
41     return true;
42   pos -= 4;
43   return false;
44 }
45
46 static void
47 match_u32_assert(uint32_t x, const char *where)
48 {
49   unsigned int y = get_u32();
50   if (x != y)
51     {
52       fprintf(stderr, "%s: 0x%x: expected i%u, got i%u\n", where, pos - 4, x, y);
53       exit(1);
54     }
55 }
56 #define match_u32_assert(x) match_u32_assert(x, WHERE)
57
58 static bool
59 match_byte(uint8_t b)
60 {
61   if (pos < n && data[pos] == b)
62     {
63       pos++;
64       return true;
65     }
66   else
67     return false;
68 }
69
70 static void
71 match_byte_assert(uint8_t b, const char *where)
72 {
73   if (!match_byte(b))
74     {
75       fprintf(stderr, "%s: 0x%x: expected %02x, got %02x\n", where, pos, b, data[pos]);
76       exit(1);
77     }
78 }
79 #define match_byte_assert(b) match_byte_assert(b, WHERE)
80
81 static bool
82 all_ascii(const uint8_t *p)
83 {
84   for (; *p; p++)
85     if (*p < 32 || *p > 126)
86       return false;
87   return true;
88 }
89
90 static bool
91 all_utf8(const uint8_t *p)
92 {
93   size_t len = strlen ((char *) p);
94   for (size_t ofs = 0, mblen; ofs < len; ofs += mblen)
95     {
96       ucs4_t uc;
97
98       mblen = u8_mbtouc (&uc, p + ofs, len - ofs);
99       if (uc < 32 || uc == 127 || uc == 0xfffd)
100         return false;
101     }
102   return true;
103 }
104
105 static char *
106 get_fixed_string(int len, const char *where)
107 {
108   if (pos + len > n || !memchr(&data[pos], 0, len) || !all_utf8(&data[pos]))
109     {
110       fprintf(stderr, "%s: 0x%x: bad fixed-width string\n", where, pos);
111       exit(1);
112     }
113   char *s = (char *) &data[pos];
114   pos += len;
115   return s;
116 }
117 #define get_fixed_string(len) get_fixed_string(len, WHERE)
118
119 static bool
120 all_ascii2(const uint8_t *p, size_t n)
121 {
122   for (size_t i = 0; i < n; i++)
123     if (p[i] < 32 || p[i] > 126)
124       return false;
125   return true;
126 }
127
128 static char *
129 get_string(const char *where)
130 {
131   if (1
132       /*data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0*/
133       /*&& all_ascii(&data[pos + 4], data[pos])*/)
134     {
135       int len = data[pos] + data[pos + 1] * 256;
136       char *s = malloc(len + 1);
137
138       memcpy(s, &data[pos + 4], len);
139       s[len] = 0;
140       pos += 4 + len;
141       return s;
142     }
143   else
144     {
145       fprintf(stderr, "%s: 0x%x: expected string\n", where, pos);
146       exit(1);
147     }
148 }
149 #define get_string() get_string(WHERE)
150
151 static void
152 dump_raw(FILE *stream, int start, int end, const char *separator)
153 {
154   for (size_t i = start; i < end; )
155     {
156       if (i + 5 <= n
157           && data[i] > 0
158           //&& !data[i + 1]
159           && !data[i + 2]
160           && !data[i + 3]
161           && i + 4 + data[i] + data[i + 1] * 256 <= end
162           && all_ascii2(&data[i + 4], data[i] + data[i + 1] * 256))
163         {
164           fprintf(stream, "%s\"", separator);
165           fwrite(&data[i + 4], 1, data[i] + data[i + 1] * 256, stream);
166           fputs("\" ", stream);
167
168           i += 4 + data[i] + data[i + 1] * 256;
169         }
170       else if (i + 12 <= end
171                && data[i + 1] == 40
172                && data[i + 2] == 5
173                && data[i + 3] == 0)
174         {
175           double d;
176
177           memcpy (&d, &data[i + 4], 8);
178           fprintf (stream, "F40.%d(%.*f)%s", data[i], data[i], d, separator);
179           i += 12;
180         }
181       else if (i + 12 <= end
182                && data[i + 1] == 40
183                && data[i + 2] == 31
184                && data[i + 3] == 0)
185         {
186           double d;
187
188           memcpy (&d, &data[i + 4], 8);
189           fprintf (stream, "PCT40.%d(%.*f)%s", data[i], data[i], d, separator);
190           i += 12;
191         }
192       else if (i + 4 <= end
193                && !data[i + 1]
194                && !data[i + 2]
195                && !data[i + 3])
196         {
197           fprintf (stream, "i%d ", data[i]);
198           i += 4;
199         }
200       else
201         {
202           fprintf(stream, "%02x ", data[i]);
203           i++;
204         }
205     }
206 }
207
208 static void
209 dump_source(int end, int count, int n_series, const char *name)
210 {
211   const union
212     {
213       uint8_t b[8];
214       double d;
215     }
216   sysmis = {.b = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff}};
217   int n_sysmis = 0;
218   for (int i = 0; i < n_series; i++)
219     {
220       printf ("    series %d: \"%s\"\n        ", i, get_fixed_string(288));
221       for (int i = 0; i < count; i++)
222         {
223           double d = get_double();
224           if (d == sysmis.d)
225             {
226               printf (" .");
227               n_sysmis++;
228             }
229           else
230             printf (" %.*g", DBL_DIG, d);
231         }
232       printf ("\n");
233     }
234
235   if (pos >= end)
236     return;
237
238   match_u32_assert(1);
239   char *name2 = get_string();
240   assert(!strcmp(name, name2));
241
242   printf ("\n    %08x:", pos);
243   int n_more_series = get_u32();
244   if (n_series != n_more_series)
245     printf("different series counts: %d %d\n", n_series, n_more_series);
246   assert(n_more_series <= n_series);
247   printf (" %d series to come\n", n_more_series);
248
249   int max1 = -1;
250   int ofs = pos;
251   for (int i = 0; i < n_more_series; i++)
252     {
253       printf ("%08x:", pos);
254       printf (" \"%s\"", get_string());
255       int n_pairs = get_u32();
256       for (int j = 0; j < n_pairs; j++)
257         {
258           int x = get_u32();
259           int y = get_u32();
260           printf (" (%d, %d)", x, y);
261           if (y > max1)
262             max1 = y;
263         }
264       printf ("\n");
265     }
266
267   printf ("\n%08x:", pos);
268   int n_strings = get_u32();
269   assert(n_strings == max1 + 1);
270   printf (" %d strings\n", n_strings);
271
272   char **strings = malloc(n_strings * sizeof *strings);
273   for (int i = 0; i < n_strings; i++)
274     {
275       int frequency = get_u32();
276       char *s = get_string();
277       printf ("%d: \"%s\" (%d)\n", i, s, frequency);
278       strings[i] = s;
279     }
280   printf ("\n");
281
282   assert (pos == end);
283   pos = ofs;
284   printf("Strings:\n");
285   for (int i = 0; i < n_more_series; i++)
286     {
287       printf ("  \"%s\"\n", get_string());
288       int n_pairs = get_u32();
289       for (int j = 0; j < n_pairs; j++)
290         {
291           int x = get_u32();
292           //assert (x == j);
293           int y = get_u32();
294           printf ("    %d: \"%s\"\n", x, strings[y]);
295         }
296       printf ("\n");
297     }
298   pos = end;
299 }
300
301 int
302 main(int argc, char **argv)
303 {
304   size_t start;
305   struct stat s;
306
307   if (isatty(STDIN_FILENO))
308     {
309       fprintf(stderr, "redirect stdin from a .bin file\n");
310       exit(1);
311     }
312   if (fstat(STDIN_FILENO, &s))
313     {
314       perror("fstat");
315       exit(1);
316     }
317   n = s.st_size;
318   data = malloc(n);
319   if (!data)
320     {
321       perror("malloc");
322       exit(1);
323     }
324   if (read(STDIN_FILENO, data, n) != n)
325     {
326       perror("read");
327       exit(1);
328     }
329
330   pos = 0;
331   match_byte_assert(0);
332   int version = data[pos];
333   if (!match_byte(0xaf))
334     match_byte_assert(0xb0);
335   int n_sources = data[pos++];
336   match_byte_assert(0);
337
338   match_u32_assert(s.st_size);
339   printf ("%d sources\n", n_sources);
340
341   struct source
342     {
343       int offset, count, n_series;
344       char *name;
345     }
346   sources[n_sources];
347   for (int i = 0; i < n_sources; i++)
348     {
349       int count = get_u32();
350       int n_series = get_u32();
351       int offset = get_u32();
352       char *name = get_fixed_string(version == 0xb0 ? 64 : 28);
353       int dunno = version == 0xb0 ? get_u32() : 0;
354       printf ("source %d: %d series, %d observations per series, offset %08x, \"%s\", %x\n",
355               i, n_series, count, offset, name, dunno);
356       sources[i].offset = offset;
357       sources[i].count = count;
358       sources[i].n_series = n_series;
359       sources[i].name = name;
360     }
361
362   for (int i = 0; i < n_sources; i++)
363     {
364       if (pos != sources[i].offset)
365         {
366           fprintf (stderr, "pos=0x%x expected=0x%x reading source %d\n", pos, sources[i].offset, i);
367           exit(1);
368         }
369       dump_source(i + 1 >= n_sources ? n : sources[i + 1].offset, sources[i].count, sources[i].n_series, sources[i].name);
370     }
371   assert(pos == n);
372
373   return 0;
374 }