dump: Fix handling of another odd case.
[pspp] / dump.c
1 #include <stdbool.h>
2 #include <stdint.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <sys/stat.h>
7 #include <unistd.h>
8
9 static uint8_t *data;
10 static size_t n;
11
12 static bool
13 all_ascii(const uint8_t *p, size_t n)
14 {
15   for (size_t i = 0; i < n; i++)
16     if (p[i] < 32 || p[i] > 126)
17       return false;
18   return true;
19 }
20
21 static size_t
22 try_find(const char *target, size_t target_len)
23 {
24   const uint8_t *pos = (const uint8_t *) memmem (data, n, target, target_len);
25   return pos ? pos - data : 0;
26 }
27
28 static size_t
29 try_find_tail(const char *target, size_t target_len)
30 {
31   size_t pos = try_find(target, target_len);
32   return pos ? pos + target_len : 0;
33 }
34
35 static size_t
36 find(const char *target, size_t target_len)
37 {
38   size_t pos = try_find(target, target_len);
39   if (!pos)
40     {
41       fprintf (stderr, "not found\n");
42       exit(1);
43     }
44   return pos;
45 }
46
47 static size_t
48 find_tail(const char *target, size_t target_len)
49 {
50   size_t pos = try_find_tail(target, target_len);
51   if (!pos)
52     {
53       fprintf (stderr, "not found\n");
54       exit(1);
55     }
56   return pos;
57 }
58
59 size_t pos;
60
61 #define XSTR(x) #x
62 #define STR(x) XSTR(x)
63 #define WHERE __FILE__":" STR(__LINE__)
64
65 static unsigned int
66 get_u32(void)
67 {
68   uint32_t x;
69   memcpy(&x, &data[pos], 4);
70   pos += 4;
71   return x;
72 }
73
74 static double
75 get_double(void)
76 {
77   double x;
78   memcpy(&x, &data[pos], 8);
79   pos += 8;
80   return x;
81 }
82
83 static bool
84 match_u32(uint32_t x)
85 {
86   if (get_u32() == x)
87     return true;
88   pos -= 4;
89   return false;
90 }
91
92 static void
93 match_u32_assert(uint32_t x, const char *where)
94 {
95   unsigned int y = get_u32();
96   if (x != y)
97     {
98       fprintf(stderr, "%s: 0x%x: expected i%u, got i%u\n", where, pos - 4, x, y);
99       exit(1);
100     }
101 }
102 #define match_u32_assert(x) match_u32_assert(x, WHERE)
103
104 static bool
105 match_byte(uint8_t b)
106 {
107   if (data[pos] == b)
108     {
109       pos++;
110       return true;
111     }
112   else
113     return false;
114 }
115
116 static void
117 match_byte_assert(uint8_t b, const char *where)
118 {
119   if (!match_byte(b))
120     {
121       fprintf(stderr, "%s: 0x%x: expected %02x, got %02x\n", where, pos, b, data[pos]);
122       exit(1);
123     }
124 }
125 #define match_byte_assert(b) match_byte_assert(b, WHERE)
126
127 static char *
128 get_string(const char *where)
129 {
130   if (data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0
131       /*&& all_ascii(&data[pos + 4], data[pos])*/)
132     {
133       int len = data[pos];
134       char *s = malloc(len + 1);
135
136       memcpy(s, &data[pos + 4], len);
137       s[len] = 0;
138       pos += 4 + data[pos];
139       return s;
140     }
141   else
142     {
143       fprintf(stderr, "%s: 0x%x: expected string\n", where, pos);
144       exit(1);
145     }
146 }
147 #define get_string() get_string(WHERE)
148
149 static void
150 dump_value(int level)
151 {
152   for (int i = 0; i <= level; i++)
153     printf ("    ");
154
155   if (match_byte (3))
156     {
157       get_string();
158       if (match_byte (0x31))
159         {
160           if (match_u32 (1))
161             {
162               printf("(footnote %d) ", get_u32());
163               match_byte_assert (0);
164               match_byte_assert (0);
165             }
166           else
167             {
168               match_u32_assert (2);
169               printf("(special 2)");
170               match_byte_assert(0);
171               match_byte_assert(0);
172               match_u32_assert(1);
173               match_byte_assert(0);
174               match_byte_assert(0);
175             }
176           int subn = get_u32 ();
177           printf ("nested %d bytes", subn);
178           pos += subn;
179         }
180       else
181         match_byte_assert (0x58);
182       get_string();
183       printf("string \"%s\"", get_string());
184       match_byte (0);
185       match_byte (0);
186       match_byte (0);
187       match_byte_assert (1);
188       match_byte (0);
189       match_byte (0);
190       match_byte (0);
191       match_byte (1);
192     }
193   else if (match_byte (5))
194     {
195       match_byte_assert (0x58);
196       printf ("variable \"%s\"", get_string());
197       get_string();
198       if (!match_byte (3))
199         match_byte_assert (2);
200       match_byte (0);
201       match_byte (0);
202       match_byte (0);
203       match_byte (0);
204     }
205   else if (match_byte (2))
206     {
207       unsigned int format;
208       char *var, *vallab;
209       double value;
210
211       match_byte_assert (0x58);
212       format = get_u32 ();
213       value = get_double ();
214       var = get_string ();
215       vallab = get_string ();
216       printf ("value %g format %d(%d.%d) var \"%s\" vallab \"%s\"",
217               value, format >> 16, (format >> 8) & 0xff, format & 0xff, var, vallab);
218       if (!match_u32 (3))
219         match_u32_assert (2);
220       match_byte (0);
221       match_byte (0);
222       match_byte (0);
223       match_byte (0);
224     }
225   else if (match_byte (4))
226     {
227       unsigned int format;
228       char *var, *vallab, *value;
229
230       match_byte_assert (0x58);
231       format = get_u32 ();
232       vallab = get_string ();
233       var = get_string ();
234       match_byte_assert (2);
235       value = get_string ();
236       printf ("value \"%s\" format %d(%d.%d) var \"%s\" vallab \"%s\"",
237               value, format >> 16, (format >> 8) & 0xff, format & 0xff, var, vallab);
238       match_byte (0);
239       match_byte (0);
240       match_byte (0);
241       match_byte (0);
242     }
243   else if (match_byte (1))
244     {
245       unsigned int format;
246       double value;
247
248       match_byte_assert (0x58);
249       format = get_u32 ();
250       value = get_double ();
251       printf ("value %g format %d(%d.%d)", value, format >> 16, (format >> 8) & 0xff, format & 0xff);
252       match_byte (1);
253       match_byte (0);
254       match_byte (0);
255       match_byte (0);
256       match_byte (1);
257     }
258   else
259     {
260       int subn;
261       int total_subs = 1;
262
263       match_byte (0);
264       match_byte_assert (0x31);
265       match_u32_assert (0);
266       match_u32_assert (0);
267       subn = get_u32 ();
268       printf ("nested %d bytes", subn);
269       pos += subn;
270       printf ("; \"%s\", substitutions:", get_string());
271       for (;;)
272         {
273           int n_subst = get_u32();
274           if (!n_subst)
275             break;
276           printf (" %d", n_subst);
277           total_subs *= n_subst;
278         }
279
280       for (int i = 0; i < total_subs; i++)
281         {
282           putc ('\n', stdout);
283           dump_value (level + 1);
284         }
285     }
286 }
287
288 static void
289 dump_dim_value(int level)
290 {
291   for (int i = 0; i <= level; i++)
292     printf ("    ");
293
294   if (match_byte (3))
295     {
296       get_string();
297       if (match_byte (0x31))
298         {
299           match_u32 (1);
300           printf("(footnote %d) ", get_u32());
301           match_byte_assert (0);
302           match_byte_assert (0);
303           int subn = get_u32 ();
304           printf ("nested %d bytes", subn);
305           pos += subn;
306         }
307       else
308         match_byte_assert (0x58);
309       get_string();
310       printf("string \"%s\"", get_string());
311       match_byte (0);
312       match_byte_assert (1);
313       match_byte (0);
314       match_byte (0);
315       match_byte (0);
316       match_byte (1);
317     }
318   else if (match_byte (5))
319     {
320       match_byte_assert (0x58);
321       printf ("variable \"%s\"", get_string());
322       get_string();
323       match_byte_assert (2);
324     }
325   else if (match_byte (2))
326     {
327       unsigned int format;
328       char *var, *vallab;
329       double value;
330
331       match_byte_assert (0x58);
332       format = get_u32 ();
333       value = get_double ();
334       var = get_string ();
335       vallab = get_string ();
336       printf ("value %g format %d(%d.%d) var \"%s\" vallab \"%s\"",
337               value, format >> 16, (format >> 8) & 0xff, format & 0xff, var, vallab);
338       if (!match_u32 (3))
339         match_u32_assert (2);
340       match_byte (0);
341     }
342   else if (match_byte (1))
343     {
344       unsigned int format;
345       double value;
346
347       match_byte_assert (0x58);
348       format = get_u32 ();
349       value = get_double ();
350       printf ("value %g format %d(%d.%d)", value, format >> 16, (format >> 8) & 0xff, format & 0xff);
351       match_byte (1);
352       match_byte (0);
353       match_byte (0);
354       match_byte (0);
355       match_byte (1);
356     }
357   else
358     {
359       int subn;
360       int total_subs = 1;
361
362       match_byte (0);
363       match_byte_assert (0x31);
364       match_u32_assert (0);
365       match_u32_assert (0);
366       subn = get_u32 ();
367       printf ("nested %d bytes", subn);
368       pos += subn;
369       printf ("; \"%s\", substitutions:", get_string());
370       for (;;)
371         {
372           int n_subst = get_u32();
373           if (!n_subst)
374             break;
375           printf (" %d", n_subst);
376           total_subs *= n_subst;
377         }
378
379       for (int i = 0; i < total_subs; i++)
380         {
381           putc ('\n', stdout);
382           dump_value (level + 1);
383         }
384     }
385 }
386
387 static void
388 dump_category(int level)
389 {
390   match_byte (0);
391   match_byte (0);
392   match_byte (0);
393   match_byte (0);
394   dump_value (level);
395
396   if (match_u32 (2))
397     get_u32 ();
398   else if (match_u32 (1))
399     {
400       match_byte (0);
401       match_byte (0);
402       match_byte (0);
403       get_u32 ();
404     }
405   else if (match_byte (1))
406     {
407       match_byte (0);
408       match_u32_assert (1);
409       match_byte (0);
410       get_u32();
411     }
412   else
413     {
414       match_u32_assert (0);
415       get_u32 ();
416     }
417
418   int n_categories = get_u32();
419   if (n_categories > 0)
420     printf (", %d subcategories:", n_categories);
421   printf("\n");
422   for (int i = 0; i < n_categories; i++)
423     dump_category (level + 1);
424 }
425
426 static void
427 dump_dim(void)
428 {
429   int n_categories;
430   printf("next dim\n");
431   match_byte(0);
432   if (match_byte(3))
433     {
434       get_string();
435       match_byte_assert(0x58);
436       get_string();
437       printf("string \"%s\": ", get_string());
438       match_byte_assert(1);
439     }
440   else if (match_byte(5)) 
441     {
442       match_byte_assert(0x58);
443       printf("variable \"%s\": ", get_string());
444       get_string();
445       if (!match_byte(2))
446         match_byte_assert(3);
447     }
448   else
449     {
450       int subn;
451       int total_subs = 1;
452
453       match_byte_assert(0x31);
454       match_u32_assert (0);
455       match_u32_assert (0);
456       subn = get_u32 ();
457       printf ("nested %d bytes", subn);
458       pos += subn;
459       printf ("; \"%s\", substitutions:", get_string());
460       for (;;)
461         {
462           int n_subst = get_u32();
463           if (!n_subst)
464             break;
465           printf (" %d", n_subst);
466           total_subs *= n_subst;
467         }
468
469       for (int i = 0; i < total_subs; i++)
470         {
471           putc ('\n', stdout);
472           dump_dim_value (0);
473         }
474     }
475
476   match_byte_assert(0);
477   if (!match_byte(0) && !match_byte(1))
478     match_byte_assert(2);
479   match_u32_assert(2);
480   if (!match_byte(0))
481     match_byte_assert(1);
482   match_byte(0);
483   match_byte(0);
484   match_byte(0);
485   match_byte(0);
486   get_u32();
487   match_byte(0);
488   match_byte(0);
489   match_byte(0);
490   match_byte(0);
491   n_categories = get_u32();
492   printf("%d nested categories\n", n_categories);
493   for (int i = 0; i < n_categories; i++)
494     dump_category (0);
495 }
496
497 static void
498 dump_dims(void)
499 {
500   int n_dims = get_u32();
501
502   printf ("%u dimensions\n", n_dims);
503   for (int i = 0; i < n_dims; i++)
504     {
505       printf("\n");
506       dump_dim ();
507     }
508 }
509
510 int
511 main(int argc, char *argv[])
512 {
513   size_t start;
514   struct stat s;
515
516   if (isatty(STDIN_FILENO))
517     {
518       fprintf(stderr, "redirect stdin from a .bin file\n");
519       exit(1);
520     }
521   if (fstat(STDIN_FILENO, &s))
522     {
523       perror("fstat");
524       exit(1);
525     }
526   n = s.st_size;
527   data = malloc(n);
528   if (!data)
529     {
530       perror("malloc");
531       exit(1);
532     }
533   if (read(STDIN_FILENO, data, n) != n)
534     {
535       perror("read");
536       exit(1);
537     }
538
539   if (argc > 1)
540     {
541       if (!strcmp(argv[1], "title0"))
542         {
543           pos = 0x27;
544           if (match_byte (0x03)
545               || (match_byte (0x05) && match_byte (0x58)))
546             printf ("%s\n", get_string());
547           else
548             printf ("<unknown>\n");
549           return 0;
550         }
551       if (!strcmp(argv[1], "title"))
552         {
553           const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
554           start = 0x27;
555           n = find(fonts, sizeof fonts - 1);
556         }
557       else if (!strcmp(argv[1], "fonts"))
558         {
559           const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
560           const char styles[] = "\xf0\0\0\0";
561           start = find(fonts, sizeof fonts - 1);
562           n = find(styles, sizeof styles - 1);
563         }
564       else if (!strcmp(argv[1], "styles"))
565         {
566           const char styles[] = "\xf0\0\0\0";
567           const char dimensions[] = "-,,,.\0";
568           start = find(styles, sizeof styles - 1);
569           n = find(dimensions, sizeof dimensions - 1) + sizeof dimensions - 1;
570         }
571       else if (!strcmp(argv[1], "dimensions"))
572         {
573           {
574             const char dimensions[] = "-,,,.\0";
575             start = try_find_tail(dimensions, sizeof dimensions - 1);
576           }
577
578           if (!start)
579             {
580               const char dimensions[] = "-,,, .\0";
581               start = find_tail(dimensions, sizeof dimensions - 1);
582             }
583
584           pos = start;
585           dump_dims ();
586           return 0;
587         }
588       else
589         {
590           fprintf (stderr, "unknown section %s\n", argv[1]);
591           exit(1);
592         }
593     }
594   else
595     start = 0x27;
596
597   for (size_t i = start; i < n; )
598     {
599       if (i + 5 <= n
600           && data[i]
601           && !data[i + 1]
602           && !data[i + 2]
603           && !data[i + 3]
604           && i + 4 + data[i] <= n
605           && all_ascii(&data[i + 4], data[i]))
606         {
607           fputs("\n\"", stdout);
608           fwrite(&data[i + 4], 1, data[i], stdout);
609           fputs("\" ", stdout);
610
611           i += 4 + data[i];
612         }
613       else if (i + 12 <= n
614                && data[i + 1] == 40
615                && data[i + 2] == 5
616                && data[i + 3] == 0)
617         {
618           double d;
619
620           memcpy (&d, &data[i + 4], 8);
621           printf ("F40.%d(%.*f)\n", data[i], data[i], d);
622           i += 12;
623         }
624       else if (i + 12 <= n
625                && data[i + 1] == 40
626                && data[i + 2] == 31
627                && data[i + 3] == 0)
628         {
629           double d;
630
631           memcpy (&d, &data[i + 4], 8);
632           printf ("PCT40.%d(%.*f)\n", data[i], data[i], d);
633           i += 12;
634         }
635       else if (i + 4 <= n
636                && (data[i] && data[i] != 88 && data[i] != 0x41)
637                && !data[i + 1]
638                && !data[i + 2]
639                && !data[i + 3])
640         {
641           printf ("i%d ", data[i]);
642           i += 4;
643         }
644       else
645         {
646           printf("%02x ", data[i]);
647           i++;
648         }
649     }
650
651   return 0;
652 }