Allow non-ascii strings. Handle substitutions in dimension titles.
[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(void)
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, "0x%x: expected string\n", pos);
144       exit(1);
145     }
146 }
147
148 static void
149 dump_value(int level)
150 {
151   for (int i = 0; i <= level; i++)
152     printf ("    ");
153
154   if (match_byte (3))
155     {
156       get_string();
157       if (match_byte (0x31))
158         {
159           match_u32 (1);
160           printf("(footnote %d) ", get_u32());
161           match_byte_assert (0);
162           match_byte_assert (0);
163           int subn = get_u32 ();
164           printf ("nested %d bytes", subn);
165           pos += subn;
166         }
167       else
168         match_byte_assert (0x58);
169       get_string();
170       printf("string \"%s\"", get_string());
171       match_byte (0);
172       match_byte_assert (1);
173       match_byte (0);
174       match_byte (0);
175       match_byte (0);
176       match_byte (1);
177     }
178   else if (match_byte (5))
179     {
180       match_byte_assert (0x58);
181       printf ("variable \"%s\"", get_string());
182       get_string();
183       if (!match_byte (3))
184         match_byte_assert (2);
185       match_byte (0);
186       match_byte (0);
187       match_byte (0);
188       match_byte (0);
189     }
190   else if (match_byte (2))
191     {
192       unsigned int format;
193       char *var, *vallab;
194       double value;
195
196       match_byte_assert (0x58);
197       format = get_u32 ();
198       value = get_double ();
199       var = get_string ();
200       vallab = get_string ();
201       printf ("value %g format %d(%d.%d) var \"%s\" vallab \"%s\"",
202               value, format >> 16, (format >> 8) & 0xff, format & 0xff, var, vallab);
203       if (!match_u32 (3))
204         match_u32_assert (2);
205       match_byte (0);
206     }
207   else if (match_byte (1))
208     {
209       unsigned int format;
210       double value;
211
212       match_byte_assert (0x58);
213       format = get_u32 ();
214       value = get_double ();
215       printf ("value %g format %d(%d.%d)", value, format >> 16, (format >> 8) & 0xff, format & 0xff);
216       match_byte (1);
217       match_byte (0);
218       match_byte (0);
219       match_byte (0);
220       match_byte (1);
221     }
222   else
223     {
224       int subn;
225       int total_subs = 1;
226
227       match_byte (0);
228       match_byte_assert (0x31);
229       match_u32_assert (0);
230       match_u32_assert (0);
231       subn = get_u32 ();
232       printf ("nested %d bytes", subn);
233       pos += subn;
234       printf ("; \"%s\", substitutions:", get_string());
235       for (;;)
236         {
237           int n_subst = get_u32();
238           if (!n_subst)
239             break;
240           printf (" %d", n_subst);
241           total_subs *= n_subst;
242         }
243
244       for (int i = 0; i < total_subs; i++)
245         {
246           putc ('\n', stdout);
247           dump_value (level + 1);
248         }
249     }
250 }
251
252 static void
253 dump_dim_value(int level)
254 {
255   for (int i = 0; i <= level; i++)
256     printf ("    ");
257
258   if (match_byte (3))
259     {
260       get_string();
261       if (match_byte (0x31))
262         {
263           match_u32 (1);
264           printf("(footnote %d) ", get_u32());
265           match_byte_assert (0);
266           match_byte_assert (0);
267           int subn = get_u32 ();
268           printf ("nested %d bytes", subn);
269           pos += subn;
270         }
271       else
272         match_byte_assert (0x58);
273       get_string();
274       printf("string \"%s\"", get_string());
275       match_byte (0);
276       match_byte_assert (1);
277       match_byte (0);
278       match_byte (0);
279       match_byte (0);
280       match_byte (1);
281     }
282   else if (match_byte (5))
283     {
284       match_byte_assert (0x58);
285       printf ("variable \"%s\"", get_string());
286       get_string();
287       match_byte_assert (2);
288     }
289   else if (match_byte (2))
290     {
291       unsigned int format;
292       char *var, *vallab;
293       double value;
294
295       match_byte_assert (0x58);
296       format = get_u32 ();
297       value = get_double ();
298       var = get_string ();
299       vallab = get_string ();
300       printf ("value %g format %d(%d.%d) var \"%s\" vallab \"%s\"",
301               value, format >> 16, (format >> 8) & 0xff, format & 0xff, var, vallab);
302       if (!match_u32 (3))
303         match_u32_assert (2);
304       match_byte (0);
305     }
306   else if (match_byte (1))
307     {
308       unsigned int format;
309       double value;
310
311       match_byte_assert (0x58);
312       format = get_u32 ();
313       value = get_double ();
314       printf ("value %g format %d(%d.%d)", value, format >> 16, (format >> 8) & 0xff, format & 0xff);
315       match_byte (1);
316       match_byte (0);
317       match_byte (0);
318       match_byte (0);
319       match_byte (1);
320     }
321   else
322     {
323       int subn;
324       int total_subs = 1;
325
326       match_byte (0);
327       match_byte_assert (0x31);
328       match_u32_assert (0);
329       match_u32_assert (0);
330       subn = get_u32 ();
331       printf ("nested %d bytes", subn);
332       pos += subn;
333       printf ("; \"%s\", substitutions:", get_string());
334       for (;;)
335         {
336           int n_subst = get_u32();
337           if (!n_subst)
338             break;
339           printf (" %d", n_subst);
340           total_subs *= n_subst;
341         }
342
343       for (int i = 0; i < total_subs; i++)
344         {
345           putc ('\n', stdout);
346           dump_value (level + 1);
347         }
348     }
349 }
350
351 static void
352 dump_category(int level)
353 {
354   match_byte (0);
355   match_byte (0);
356   match_byte (0);
357   match_byte (0);
358   dump_value (level);
359
360   if (match_u32 (2))
361     get_u32 ();
362   else if (match_u32 (1))
363     {
364       match_byte (0);
365       match_byte (0);
366       match_byte (0);
367       get_u32 ();
368     }
369   else
370     {
371       match_u32_assert (0);
372       get_u32 ();
373     }
374
375   int n_categories = get_u32();
376   if (n_categories > 0)
377     printf (", %d subcategories:", n_categories);
378   printf("\n");
379   for (int i = 0; i < n_categories; i++)
380     dump_category (level + 1);
381 }
382
383 static void
384 dump_dim(void)
385 {
386   int n_categories;
387   printf("next dim\n");
388   match_byte(0);
389   if (match_byte(3))
390     {
391       get_string();
392       match_byte_assert(0x58);
393       get_string();
394       printf("string \"%s\": ", get_string());
395       match_byte_assert(1);
396     }
397   else if (match_byte(5)) 
398     {
399       match_byte_assert(0x58);
400       printf("variable \"%s\": ", get_string());
401       get_string();
402       if (!match_byte(2))
403         match_byte_assert(3);
404     }
405   else
406     {
407       int subn;
408       int total_subs = 1;
409
410       match_byte_assert(0x31);
411       match_u32_assert (0);
412       match_u32_assert (0);
413       subn = get_u32 ();
414       printf ("nested %d bytes", subn);
415       pos += subn;
416       printf ("; \"%s\", substitutions:", get_string());
417       for (;;)
418         {
419           int n_subst = get_u32();
420           if (!n_subst)
421             break;
422           printf (" %d", n_subst);
423           total_subs *= n_subst;
424         }
425
426       for (int i = 0; i < total_subs; i++)
427         {
428           putc ('\n', stdout);
429           dump_dim_value (0);
430         }
431     }
432
433   match_byte_assert(0);
434   if (!match_byte(0) && !match_byte(1))
435     match_byte_assert(2);
436   match_u32_assert(2);
437   if (!match_byte(0))
438     match_byte_assert(1);
439   match_byte(0);
440   match_byte(0);
441   match_byte(0);
442   match_byte(0);
443   get_u32();
444   match_byte(0);
445   match_byte(0);
446   match_byte(0);
447   match_byte(0);
448   n_categories = get_u32();
449   printf("%d nested categories\n", n_categories);
450   for (int i = 0; i < n_categories; i++)
451     dump_category (0);
452 }
453
454 static void
455 dump_dims(void)
456 {
457   int n_dims = get_u32();
458
459   printf ("%u dimensions\n", n_dims);
460   for (int i = 0; i < n_dims; i++)
461     {
462       printf("\n");
463       dump_dim ();
464     }
465 }
466
467 int
468 main(int argc, char *argv[])
469 {
470   size_t start;
471   struct stat s;
472
473   if (isatty(STDIN_FILENO))
474     {
475       fprintf(stderr, "redirect stdin from a .bin file\n");
476       exit(1);
477     }
478   if (fstat(STDIN_FILENO, &s))
479     {
480       perror("fstat");
481       exit(1);
482     }
483   n = s.st_size;
484   data = malloc(n);
485   if (!data)
486     {
487       perror("malloc");
488       exit(1);
489     }
490   if (read(STDIN_FILENO, data, n) != n)
491     {
492       perror("read");
493       exit(1);
494     }
495
496   if (argc > 1)
497     {
498       if (!strcmp(argv[1], "title0"))
499         {
500           pos = 0x27;
501           if (match_byte (0x03)
502               || (match_byte (0x05) && match_byte (0x58)))
503             printf ("%s\n", get_string());
504           else
505             printf ("<unknown>\n");
506           return 0;
507         }
508       if (!strcmp(argv[1], "title"))
509         {
510           const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
511           start = 0x27;
512           n = find(fonts, sizeof fonts - 1);
513         }
514       else if (!strcmp(argv[1], "fonts"))
515         {
516           const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
517           const char styles[] = "\xf0\0\0\0";
518           start = find(fonts, sizeof fonts - 1);
519           n = find(styles, sizeof styles - 1);
520         }
521       else if (!strcmp(argv[1], "styles"))
522         {
523           const char styles[] = "\xf0\0\0\0";
524           const char dimensions[] = "-,,,.\0";
525           start = find(styles, sizeof styles - 1);
526           n = find(dimensions, sizeof dimensions - 1) + sizeof dimensions - 1;
527         }
528       else if (!strcmp(argv[1], "dimensions"))
529         {
530           {
531             const char dimensions[] = "-,,,.\0";
532             start = try_find_tail(dimensions, sizeof dimensions - 1);
533           }
534
535           if (!start)
536             {
537               const char dimensions[] = "-,,, .\0";
538               start = find_tail(dimensions, sizeof dimensions - 1);
539             }
540
541           pos = start;
542           dump_dims ();
543           return 0;
544         }
545       else
546         {
547           fprintf (stderr, "unknown section %s\n", argv[1]);
548           exit(1);
549         }
550     }
551   else
552     start = 0x27;
553
554   for (size_t i = start; i < n; )
555     {
556       if (i + 5 <= n
557           && data[i]
558           && !data[i + 1]
559           && !data[i + 2]
560           && !data[i + 3]
561           && i + 4 + data[i] <= n
562           && all_ascii(&data[i + 4], data[i]))
563         {
564           fputs("\n\"", stdout);
565           fwrite(&data[i + 4], 1, data[i], stdout);
566           fputs("\" ", stdout);
567
568           i += 4 + data[i];
569         }
570       else if (i + 12 <= n
571                && data[i + 1] == 40
572                && data[i + 2] == 5
573                && data[i + 3] == 0)
574         {
575           double d;
576
577           memcpy (&d, &data[i + 4], 8);
578           printf ("F40.%d(%.*f)\n", data[i], data[i], d);
579           i += 12;
580         }
581       else if (i + 12 <= n
582                && data[i + 1] == 40
583                && data[i + 2] == 31
584                && data[i + 3] == 0)
585         {
586           double d;
587
588           memcpy (&d, &data[i + 4], 8);
589           printf ("PCT40.%d(%.*f)\n", data[i], data[i], d);
590           i += 12;
591         }
592       else if (i + 4 <= n
593                && (data[i] && data[i] != 88 && data[i] != 0x41)
594                && !data[i + 1]
595                && !data[i + 2]
596                && !data[i + 3])
597         {
598           printf ("i%d ", data[i]);
599           i += 4;
600         }
601       else
602         {
603           printf("%02x ", data[i]);
604           i++;
605         }
606     }
607
608   return 0;
609 }