Refine nested parsing.
[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 (pos < n && 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 void
128 dump_raw(FILE *stream, int start, int end, const char *separator)
129 {
130   for (size_t i = start; i < end; )
131     {
132       if (i + 5 <= n
133           && data[i]
134           //&& !data[i + 1]
135           && !data[i + 2]
136           && !data[i + 3]
137           && i + 4 + data[i] + data[i + 1] * 256 <= end
138           && all_ascii(&data[i + 4], data[i] + data[i + 1] * 256))
139         {
140           fprintf(stream, "%s\"", separator);
141           fwrite(&data[i + 4], 1, data[i] + data[i + 1] * 256, stream);
142           fputs("\" ", stream);
143
144           i += 4 + data[i] + data[i + 1] * 256;
145         }
146       else if (i + 12 <= end
147                && data[i + 1] == 40
148                && data[i + 2] == 5
149                && data[i + 3] == 0)
150         {
151           double d;
152
153           memcpy (&d, &data[i + 4], 8);
154           fprintf (stream, "F40.%d(%.*f)%s", data[i], data[i], d, separator);
155           i += 12;
156         }
157       else if (i + 12 <= end
158                && data[i + 1] == 40
159                && data[i + 2] == 31
160                && data[i + 3] == 0)
161         {
162           double d;
163
164           memcpy (&d, &data[i + 4], 8);
165           fprintf (stream, "PCT40.%d(%.*f)%s", data[i], data[i], d, separator);
166           i += 12;
167         }
168       else if (i + 4 <= end
169                && (data[i] && data[i] != 88 && data[i] != 0x41)
170                && !data[i + 1]
171                && !data[i + 2]
172                && !data[i + 3])
173         {
174           fprintf (stream, "i%d ", data[i]);
175           i += 4;
176         }
177       else
178         {
179           fprintf(stream, "%02x ", data[i]);
180           i++;
181         }
182     }
183
184
185 }
186
187 static char *
188 get_string(const char *where)
189 {
190   if (1
191       /*data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0*/
192       /*&& all_ascii(&data[pos + 4], data[pos])*/)
193     {
194       int len = data[pos] + data[pos + 1] * 256;
195       char *s = malloc(len + 1);
196
197       memcpy(s, &data[pos + 4], len);
198       s[len] = 0;
199       pos += 4 + len;
200       return s;
201     }
202   else
203     {
204       fprintf(stderr, "%s: 0x%x: expected string\n", where, pos);
205       exit(1);
206     }
207 }
208 #define get_string() get_string(WHERE)
209
210 static void
211 dump_empty_nested(void)
212 {
213   int outer_end = pos + get_u32();
214   int inner_end = pos + get_u32();
215   if (pos != inner_end)
216     {
217       match_u32_assert(0);
218       match_byte_assert(0x58);
219       if (pos != inner_end)
220         {
221           fprintf(stderr, "inner end discrepancy\n");
222           exit(1);
223         }
224     }
225   match_byte_assert(0x58);
226   match_byte_assert(0x58);
227   if (pos != outer_end)
228     {
229       fprintf(stderr, "outer end discrepancy\n");
230       exit(1);
231     }
232 }
233
234 static void
235 dump_value_31(void)
236 {
237   if (match_byte (0x31))
238     {
239       if (match_u32 (0))
240         {
241           if (match_u32 (1))
242             get_string();
243           else
244             match_u32_assert (0);
245
246           int outer_end = pos + get_u32();
247           int inner_end = pos + get_u32();
248           match_u32_assert(0);
249           if (match_byte(0x31))
250             get_string();
251           else
252             match_byte_assert(0x58);
253           if (pos != inner_end)
254             {
255               fprintf(stderr, "inner end discrepancy\n");
256               exit(1);
257             }
258
259           if (match_byte(0x31))
260             {
261               match_byte(0);
262               match_byte(0);
263               match_byte(0);
264               match_byte_assert(1);
265               get_string();     /* foreground */
266               get_string();     /* background */
267               get_string();     /* font */
268               match_byte_assert(12); /* size? */
269             }
270           else
271             match_byte_assert(0x58);
272           match_byte_assert(0x58);
273           if (pos != outer_end)
274             {
275               fprintf(stderr, "outer end discrepancy\n");
276               exit(1);
277             }
278         }
279       else if (match_u32 (1))
280         {
281           printf("(footnote %d) ", get_u32());
282           match_byte_assert (0);
283           match_byte_assert (0);
284
285           int outer_end = pos + get_u32();
286           int inner_end = pos + get_u32();
287           if (pos != inner_end)
288             {
289               match_u32_assert(0);
290               if (match_byte(0x31))
291                 get_string();
292               else
293                 match_byte_assert(0x58);
294               if (pos != inner_end)
295                 {
296                   fprintf(stderr, "inner end discrepancy\n");
297                   exit(1);
298                 }
299             }
300           match_byte_assert(0x58);
301           match_byte_assert(0x58);
302           if (pos != outer_end)
303             {
304               fprintf(stderr, "outer end discrepancy\n");
305               exit(1);
306             }
307         }
308       else if (match_u32 (2))
309         {
310           printf("(special 2)");
311           match_byte_assert(0);
312           match_byte_assert(0);
313           if (!match_u32 (2))
314             match_u32_assert(1);
315           match_byte_assert(0);
316           match_byte_assert(0);
317           dump_empty_nested();
318         }
319       else
320         {
321           match_u32_assert(3);
322           printf("(special 3)");
323           match_byte_assert(0);
324           match_byte_assert(0);
325           match_byte_assert(1);
326           match_byte_assert(0);
327           match_u32_assert(2);
328           match_byte_assert(0);
329           match_byte_assert(0);
330           dump_empty_nested();
331         }
332     }
333   else
334     match_byte_assert (0x58);
335 }
336
337 static void
338 dump_value__(int level, bool match1)
339 {
340   for (int i = 0; i <= level; i++)
341     printf ("    ");
342
343   match_byte(0);
344   match_byte(0);
345   match_byte(0);
346   match_byte(0);
347
348   if (match_byte (3))
349     {
350       char *s1 = get_string();
351       dump_value_31();
352       char *s2 = get_string();
353       char *s3 = get_string();
354       if (strcmp(s1, s3))
355         printf("strings \"%s\", \"%s\" and \"%s\"", s1, s2, s3);
356       else
357         printf("string \"%s\" and \"%s\"", s1, s2);
358       if (!match_byte (0))
359         match_byte_assert(1);
360       if (match1)
361         match_byte (1);
362     }
363   else if (match_byte (5))
364     {
365       dump_value_31();
366       printf ("variable \"%s\"", get_string());
367       get_string();
368       if (!match_byte(1) && !match_byte(2))
369         match_byte_assert(3);
370     }
371   else if (match_byte (2))
372     {
373       unsigned int format;
374       char *var, *vallab;
375       double value;
376
377       match_byte_assert (0x58);
378       format = get_u32 ();
379       value = get_double ();
380       var = get_string ();
381       vallab = get_string ();
382       printf ("value %g format %d(%d.%d) var \"%s\" vallab \"%s\"",
383               value, format >> 16, (format >> 8) & 0xff, format & 0xff, var, vallab);
384       if (!match_byte (1) && !match_byte(2))
385         match_byte_assert (3);
386     }
387   else if (match_byte (4))
388     {
389       unsigned int format;
390       char *var, *vallab, *value;
391
392       match_byte_assert (0x58);
393       format = get_u32 ();
394       vallab = get_string ();
395       var = get_string ();
396       if (!match_byte(1) && !match_byte(2))
397         match_byte_assert (3);
398       value = get_string ();
399       printf ("value \"%s\" format %d(%d.%d) var \"%s\" vallab \"%s\"",
400               value, format >> 16, (format >> 8) & 0xff, format & 0xff, var, vallab);
401     }
402   else if (match_byte (1))
403     {
404       unsigned int format;
405       double value;
406
407       dump_value_31();
408       format = get_u32 ();
409       value = get_double ();
410       printf ("value %g format %d(%d.%d)", value, format >> 16, (format >> 8) & 0xff, format & 0xff);
411       if (match1)
412         match_byte (1);
413     }
414   else
415     {
416       dump_value_31();
417
418       char *base = get_string();
419       int x = get_u32();
420       printf ("\"%s\" with %d variables:\n", base, x);
421       for (int i = 0; i < x; i++)
422         {
423           int y = get_u32();
424           if (!y)
425             y = 1;
426           else
427             match_u32_assert(0);
428           for (int j = 0; j <= level; j++)
429             printf ("    ");
430           printf("variable %d has %d values:\n", i, y);
431           for (int j = 0; j < y; j++)
432             {
433               dump_value__ (level + 1, false);
434               putchar('\n');
435             }
436         }
437     }
438 }
439
440 static int
441 compare_int(const void *a_, const void *b_)
442 {
443   const int *a = a_;
444   const int *b = b_;
445   return *a < *b ? -1 : *a > *b;
446 }
447
448 static void
449 check_permutation(int *a, int n, const char *name)
450 {
451   int b[n];
452   memcpy(b, a, n * sizeof *a);
453   qsort(b, n, sizeof *b, compare_int);
454   for (int i = 0; i < n; i++)
455     if (b[i] != i)
456       {
457         fprintf(stderr, "bad %s permutation:", name);
458         for (int i = 0; i < n; i++)
459           fprintf(stderr, " %d", a[i]);
460         putc('\n', stderr);
461         exit(1);
462       }
463 }
464
465 static void
466 dump_category(int level, int *indexes, int *n_indexes)
467 {
468   dump_value__ (level, true);
469   match_byte(0);
470   match_byte(0);
471   match_byte(0);
472
473   if (match_u32 (1))
474     match_byte (0);
475   else if (match_byte (1))
476     {
477       match_byte (0);
478       if (!match_u32 (2))
479         match_u32_assert (1);
480       match_byte (0);
481     }
482   else if (!match_u32(2))
483     match_u32_assert (0);
484
485   int indx = get_u32();
486   int n_categories = get_u32();
487   if (indx != -1)
488     {
489       if (n_categories != 0)
490         {
491           fprintf(stderr, "index not -1 but subcategories\n");
492           exit(1);
493         }
494       indexes[(*n_indexes)++] = indx;
495     }
496   if (n_categories > 0)
497     printf (", %d subcategories:", n_categories);
498   else
499     printf (", index %d", indx);
500   printf("\n");
501   for (int i = 0; i < n_categories; i++)
502     dump_category (level + 1, indexes, n_indexes);
503 }
504
505 static void
506 dump_dim(void)
507 {
508   int n_categories;
509   printf("next dim\n");
510   dump_value__ (0, false);
511
512   /* This byte is usually 0x02 but 0x00 and 0x75 (!) have also been spotted. */
513   pos++;
514
515   if (!match_byte(0) && !match_byte(1))
516     match_byte_assert(2);
517   if (!match_u32(0))
518     match_u32_assert(2);
519   if (!match_byte(0))
520     match_byte_assert(1);
521   if (!match_byte(0))
522     match_byte_assert(1);
523   match_byte_assert(1);
524   static int dim_indx = 0;
525   match_u32_assert(dim_indx++);
526   n_categories = get_u32();
527   printf("%d nested categories\n", n_categories);
528
529   int indexes[1024];
530   int n_indexes = 0;
531   for (int i = 0; i < n_categories; i++)
532     dump_category (0, indexes, &n_indexes);
533   check_permutation(indexes, n_indexes, "categories");
534 }
535
536 int n_dims;
537 static void
538 dump_dims(void)
539 {
540   n_dims = get_u32();
541   printf ("%u dimensions\n", n_dims);
542   for (int i = 0; i < n_dims; i++)
543     {
544       printf("\n");
545       dump_dim ();
546     }
547 }
548
549 static void
550 dump_data(void)
551 {
552   /* The first three numbers add to the number of dimensions. */
553   int t = get_u32();
554   t += get_u32();
555   match_u32_assert(n_dims - t);
556
557   /* The next n_dims numbers are a permutation of the dimension numbers. */
558   int a[n_dims];
559   for (int i = 0; i < n_dims; i++)
560     a[i] = get_u32();
561   check_permutation(a, n_dims, "dimensions");
562
563   int x = get_u32();
564   printf ("%d data values, starting at %08x\n", x, pos);
565   for (int i = 0; i < x; i++)
566     {
567       printf("%08x, index %d:\n", pos, get_u32());
568       match_u32_assert(0);
569       dump_value__(0, false);
570       putchar('\n');
571     }
572 }
573
574 static void
575 dump_title(void)
576 {
577   pos = 0x27;
578   dump_value__(0, true); putchar('\n');
579   dump_value__(0, true); putchar('\n');
580   match_byte_assert(0x31);
581   dump_value__(0, true); putchar('\n');
582   match_byte(0);
583   match_byte_assert(0x58);
584   if (match_byte(0x31))
585     {
586       dump_value__(0, false); putchar('\n');
587     }
588   else
589     match_byte_assert(0x58);
590
591
592   int n_footnotes = get_u32();
593   if (n_footnotes >= 20)
594     {
595       fprintf(stderr, "%08x: %d footnotes\n", pos - 4, n_footnotes);
596       exit(1);
597     }
598
599   printf("------\n%d footnotes\n", n_footnotes);
600   if (n_footnotes < 20)
601     {
602       for (int i = 0; i < n_footnotes; i++)
603         {
604           printf("footnote %d:\n", i);
605           dump_value__(0, false);
606           if (match_byte (0x31))
607             {
608               /* Custom footnote marker string. */
609               match_byte_assert(3);
610               get_string();
611               match_byte_assert(0x58);
612               match_u32_assert(0);
613               get_string();
614             }
615           else
616             match_byte_assert (0x58);
617           printf("(%d)\n", get_u32());
618         }
619     }
620 }
621
622 static int
623 find_dimensions(void)
624 {
625   {
626     const char dimensions[] = "-,,,.\0";
627     int x = try_find_tail(dimensions, sizeof dimensions - 1);
628     if (x)
629       return x;
630   }
631
632   const char dimensions[] = "-,,, .\0";
633   return find_tail(dimensions, sizeof dimensions - 1);
634 }
635
636 static void
637 dump_fonts(void)
638 {
639   printf("fonts: offset=%08x\n", pos);
640   match_byte(0);
641   for (int i = 1; i <= 8; i++)
642     {
643       printf("%08x: font %d, ", pos, i);
644       match_byte_assert(i);
645       match_byte_assert(0x31);
646       printf("%s, ", get_string());
647       match_byte_assert(0);
648       match_byte_assert(0);
649       if (!match_byte(0x40) && !match_byte(0x20) && !match_byte(0x80) && !match_byte(0x10))
650         match_byte_assert(0x50);
651       if (!match_byte(0x41))
652         match_byte_assert(0x51);
653       if (!match_u32(0))
654         match_u32_assert(1);
655       match_byte_assert(0);
656
657       /* OK, this seems really unlikely to be totally correct, but it matches my corpus... */
658       if (!match_u32(0) && !match_u32(2))
659         match_u32_assert(0xfaad);
660
661       if (!match_u32(0) && !match_u32(1) && !match_u32(2))
662         match_u32_assert(3);
663       printf ("%s, ", get_string());
664       printf ("%s, ", get_string());
665       match_u32_assert(0);
666       match_u32_assert(0);
667       match_byte_assert(0);
668
669       /* These seem unlikely to be correct too. */
670       if (i != 3)
671         {
672           match_u32_assert(8);
673           if (!match_u32(10))
674             match_u32_assert(11);
675           match_u32_assert(1);
676         }
677       else
678         {
679           get_u32();
680           if (!match_u32(-1) && !match_u32(8))
681             match_u32_assert(24);
682           if (!match_u32(-1) && !match_u32(2))
683             match_u32_assert(3);
684         }
685
686       /* Who knows? Ranges from -1 to 8 with no obvious pattern. */
687       get_u32();
688     }
689
690   match_u32_assert(240);
691   pos += 240;
692
693   match_u32_assert(18);
694   pos += 18;
695
696   if (match_u32(117))
697     pos += 117;
698   else
699     {
700       match_u32_assert(142);
701       pos += 142;
702     }
703
704   int count = get_u32();
705   pos += 4 * count;
706
707   char *encoding = get_string();
708   printf("encoding=%s\n", encoding);
709
710   if (!match_u32(0))
711     match_u32_assert(UINT32_MAX);
712   if (!match_byte(0))
713     match_byte_assert(1);
714   match_byte_assert(0);
715   if (!match_byte(0))
716     match_byte_assert(1);
717   if (!match_byte(0x99) && !match_byte(0x98))
718     match_byte_assert(0x97);
719   match_byte_assert(7);
720   match_byte_assert(0);
721   match_byte_assert(0);
722   if (match_byte('.'))
723     match_byte_assert(',');
724   else
725     {
726       match_byte_assert(',');
727       if (!match_byte('.'))
728         match_byte_assert(' ');
729     }
730   match_u32_assert(5);
731   for (int i = 0; i < 5; i++)
732     get_string();
733   pos += get_u32();
734   if (pos != find_dimensions())
735     fprintf (stderr, "%08x / %08x\n", pos, find_dimensions());
736 }
737
738 int
739 main(int argc, char *argv[])
740 {
741   size_t start;
742   struct stat s;
743
744   if (isatty(STDIN_FILENO))
745     {
746       fprintf(stderr, "redirect stdin from a .bin file\n");
747       exit(1);
748     }
749   if (fstat(STDIN_FILENO, &s))
750     {
751       perror("fstat");
752       exit(1);
753     }
754   n = s.st_size;
755   data = malloc(n);
756   if (!data)
757     {
758       perror("malloc");
759       exit(1);
760     }
761   if (read(STDIN_FILENO, data, n) != n)
762     {
763       perror("read");
764       exit(1);
765     }
766
767   if (argc > 1)
768     {
769       if (!strcmp(argv[1], "title0"))
770         {
771           pos = 0x27;
772           if (match_byte (0x03)
773               || (match_byte (0x05) && match_byte (0x58)))
774             printf ("%s\n", get_string());
775           else
776             printf ("<unknown>\n");
777           return 0;
778         }
779       else if (!strcmp(argv[1], "title"))
780         {
781           dump_title();
782           exit(0);
783         }
784       else if (!strcmp(argv[1], "titleraw"))
785         {
786           const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
787           start = 0x27;
788           n = find(fonts, sizeof fonts - 1);
789         }
790       else if (!strcmp(argv[1], "fonts"))
791         {
792           const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
793           const char styles[] = "\xf0\0\0\0";
794           start = find(fonts, sizeof fonts - 1);
795           n = find(styles, sizeof styles - 1);
796         }
797       else if (!strcmp(argv[1], "styles"))
798         {
799           const char styles[] = "\xf0\0\0\0";
800           const char dimensions[] = "-,,,.\0";
801           start = find(styles, sizeof styles - 1);
802           n = find(dimensions, sizeof dimensions - 1) + sizeof dimensions - 1;
803         }
804       else if (!strcmp(argv[1], "dimensions") || !strcmp(argv[1], "all"))
805         {
806           pos = 0;
807           match_byte_assert(1);
808           match_byte_assert(0);
809           match_u32_assert(3);
810           match_byte_assert(1);
811           if (!match_byte(0))
812             match_byte_assert(1);
813           match_byte_assert(0);
814           match_byte_assert(0);
815           if (!match_byte(0))
816             match_byte_assert(1);
817           pos++;
818           match_byte_assert(0);
819           match_byte_assert(0);
820           match_byte_assert(0);
821           dump_title ();
822           dump_fonts();
823           dump_dims ();
824           printf("\n\ndata:\n");
825           dump_data ();
826           match_byte (1);
827           if (pos != n)
828             {
829               fprintf (stderr, "%x / %x\n", pos, n);
830               exit(1);
831             }
832           exit(0);
833         }
834       else
835         {
836           fprintf (stderr, "unknown section %s\n", argv[1]);
837           exit(1);
838         }
839     }
840   else
841     start = 0x27;
842
843   dump_raw(stdout, start, n, "\n");
844
845   return 0;
846 }