Add support for .tlo TableLook files from SPSS 15 and earlier.
[pspp] / src / output / spv / binary-parser-generator
1 #! /usr/bin/python
2
3 # PSPP - a program for statistical analysis.
4 # Copyright (C) 2017, 2018, 2019 Free Software Foundation, Inc.
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
19 import getopt
20 import os
21 import struct
22 import sys
23
24 n_errors = 0
25
26 def error(msg):
27     global n_errors
28     sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg))
29     n_errors += 1
30
31
32 def fatal(msg):
33     error(msg)
34     sys.exit(1)
35
36
37 def get_line():
38     global line
39     global line_number
40     line = input_file.readline()
41     line_number += 1
42
43
44 def is_num(s):
45     return s.isdigit() or (s[0] == '-' and s[1].isdigit())
46
47
48 xdigits = "0123456789abcdefABCDEF"
49 def is_xdigits(s):
50     for c in s:
51         if c not in xdigits:
52             return False
53     return True
54
55
56 def expect(type):
57     if token[0] != type:
58         fatal("syntax error expecting %s" % type)
59
60
61 def match(type):
62     if token[0] == type:
63         get_token()
64         return True
65     else:
66         return False
67
68
69 def must_match(type):
70     expect(type)
71     get_token()
72
73
74 def get_token():
75     global token
76     global line
77     prev = token
78     if line == "":
79         if token == ('eof', ):
80             fatal("unexpected end of input")
81         get_line()
82         if not line:
83             token = ('eof', )
84             return
85         elif line == '\n':
86             token = (';', )
87             return
88         elif not line[0].isspace():
89             token = (';', )
90             return
91
92     line = line.lstrip()
93     if line == "":
94         get_token()
95     elif line[0] == '#':
96         line = ''
97         get_token()
98     elif line[0] in '[]()?|*':
99         token = (line[0], )
100         line = line[1:]
101     elif line.startswith('=>'):
102         token = (line[:2], )
103         line = line[2:]
104     elif line.startswith('...'):
105         token = (line[:3], )
106         line = line[3:]
107     elif line.startswith('"'):
108         n = 1
109         while n < len(line) and (line[n] != '"'):
110             n += 1
111         s = line[1:n].encode()
112         line = line[n+1:]
113         token = ('bytes', struct.pack('<h', len(s)) + s)
114     elif line[0].isalnum() or line[0] == '-':
115         n = 1
116         while n < len(line) and (line[n].isalnum() or line[n] == '-'):
117             n += 1
118         s = line[:n]
119         line = line[n:]
120
121         if prev[0] == '*' and is_num(s):
122             token = ('number', int(s, 10))
123         elif len(s) == 2 and is_xdigits(s):
124             token = ('bytes', struct.pack('B', int(s, 16)))
125         elif s[0] == 'i' and is_num(s[1:]):
126             token = ('bytes', struct.pack('<i', int(s[1:])))
127         elif s[:2] == 'ib' and is_num(s[2:]):
128             token = ('bytes', struct.pack('>i', int(s[2:])))
129         elif s[0].isupper():
130             token = ('nonterminal', s)
131         elif s in ('bool', 'int16', 'int32', 'int64', 'be16', 'be32', 'be64',
132                    'string', 'bestring', 'byte', 'float', 'double',
133                    'count', 'becount', 'v1', 'v3', 'vAF', 'vB0',
134                    'case', 'else'):
135             token = (s, )
136         else:
137             token = ('id', s)
138     else:
139         fatal("unknown character %c" % line[0])
140
141
142 def usage():
143     argv0 = os.path.basename(sys.argv[0])
144     print('''\
145 %(argv0)s, parser generator for SPV binary members
146 usage: %(argv0)s GRAMMAR header PREFIX
147        %(argv0)s GRAMMAR code PREFIX HEADER_NAME
148   where GRAMMAR contains grammar definitions,
149         PREFIX is the identifier prefix to use,
150     and HEADER_NAME is the name of the header to include.
151 ''' % {"argv0": argv0})
152     sys.exit(0)
153
154
155 class Item(object):
156     def __init__(self, type_, name, n, content):
157         self.type_ = type_
158         self.name = name
159         self.n = n
160         self.content = content
161     def __repr__(self):
162         if self.type_ == 'constant':
163             return ' '.join(['%02x' % maybe_ord(x) for x in self.content])
164         elif self.content:
165             return "%s(%s)" % (self.type_, self.content)
166         else:
167             return self.type_
168
169 def parse_item():
170     t = token
171     name = None
172     if t[0] == 'bytes':
173         type_ = 'constant'
174         content = t[1]
175         get_token()
176     elif t[0] in ('bool', 'byte',
177                   'int16', 'int32', 'int64',
178                   'be16', 'be32', 'be64',
179                   'string', 'bestring',
180                   'float', 'double',
181                   'nonterminal', '...'):
182         type_ = 'variable'
183         content = t
184         get_token()
185         if t[0] == 'nonterminal':
186             name = name_to_id(content[1])
187     elif t[0] in ('v1', 'v3', 'vAF', 'vB0', 'count', 'becount'):
188         type_ = t[0]
189         get_token()
190         must_match('(')
191         content = parse_choice()
192         must_match(')')
193     elif match('case'):
194         return parse_case()
195     elif match('('):
196         type_ = '()'
197         content = parse_choice()
198         must_match(')')
199     else:
200         print(token)
201         fatal('syntax error expecting item')
202
203     n = 1
204     optional = False
205     if match('*'):
206         if token[0] == 'number':
207             n = token[1]
208             get_token()
209         elif match('['):
210             expect('id')
211             n = token[1]
212             get_token()
213             must_match(']')
214             if n.startswith('n-'):
215                 name = n[2:]
216         else:
217             fatal('expecting quantity')
218     elif match('?'):
219         optional = True
220
221     if match('['):
222         expect('id')
223         if type_ == 'constant' and not optional:
224             fatal("%s: cannot name a constant" % token[1])
225
226         name = token[1]
227         get_token()
228         must_match(']')
229
230     if type_ == 'constant':
231         content *= n
232         n = 1
233
234     item = Item(type_, name, n, content)
235     if optional:
236         item = Item('|', None, 1, [[item], []])
237     return item
238
239
240 def parse_concatenation():
241     items = []
242     while token[0] not in (')', ';', '|', 'eof'):
243         item = parse_item()
244         if (item.type_ == 'constant'
245             and items
246             and items[-1].type_ == 'constant'):
247             items[-1].content += item.content
248         else:
249             items.append(item)
250     return items
251
252
253 def parse_choice():
254     sub = parse_concatenation()
255     if token[0] != '|':
256         return sub
257
258     choices = [sub]
259     while match('|'):
260         choices.append(parse_concatenation())
261
262     return [Item('|', None, 1, choices)]
263
264
265 def parse_case():
266     must_match('(')
267     choices = {}
268     while True:
269         choice = None
270         if match('else'):
271             choice = 'else'
272
273         items = parse_concatenation()
274         if choice is None:
275             if (not items
276                 or items[0].type_ != 'constant'
277                 or len(items[0].content) != 1):
278                 fatal("choice must begin with xx (or 'else')")
279             choice = '%02x' % maybe_ord(items[0].content)
280
281         if choice in choices:
282             fatal("duplicate choice %s" % choice)
283         choices[choice] = items
284
285         if match(')'):
286             break
287         must_match('|')
288
289     case_name = None
290     if match('['):
291         expect('id')
292         case_name = token[1]
293         get_token()
294         must_match(']')
295
296     return Item('case', case_name, 1,
297                 { '%s_%s' % (case_name, k) : v for k, v in choices.items() })
298
299
300 def parse_production():
301     expect('nonterminal')
302     name = token[1]
303     get_token()
304     must_match('=>')
305     return name, parse_choice()
306
307
308 def print_members(p, indent):
309     for item in p:
310         if item.type_ == 'variable' and item.name:
311             if item.content[0] == 'nonterminal':
312                 typename = 'struct %s%s' % (prefix,
313                                             name_to_id(item.content[1]))
314                 n_stars = 1
315             else:
316                 c_types = {'bool': ('bool', 0),
317                            'byte': ('uint8_t', 0),
318                            'int16': ('uint16_t', 0),
319                            'int32': ('uint32_t', 0),
320                            'int64': ('uint64_t', 0),
321                            'be16': ('uint16_t', 0),
322                            'be32': ('uint32_t', 0),
323                            'be64': ('uint64_t', 0),
324                            'string': ('char', 1),
325                            'bestring': ('char', 1),
326                            'float': ('double', 0),
327                            'double': ('double', 0),
328                            '...': ('uint8_t', 1)}
329                 typename, n_stars = c_types[item.content[0]]
330
331             array_suffix = ''
332             if item.n:
333                 if isinstance(item.n, int):
334                     if item.n > 1:
335                         array_suffix = '[%d]' % item.n
336                 else:
337                     n_stars += 1
338
339             print("%s%s %s%s%s;" % (indent, typename, '*' * n_stars,
340                                     name_to_id(item.name),
341                                     array_suffix))
342         elif item.type_ in ('v1', 'v3', 'vAF', 'vB0',
343                             'count', 'becount', '()'):
344             print_members(item.content, indent)
345         elif item.type_ == '|':
346             for choice in item.content:
347                 print_members(choice, indent)
348         elif item.type_ == 'case':
349             print("%sint %s;" % (indent, item.name))
350             print("%sunion {" % indent)
351             for name, choice in sorted(item.content.items()):
352                 print("%s    struct {" % indent)
353                 print_members(choice, indent + ' ' * 8)
354                 print("%s    } %s;" % (indent, name))
355             print("%s};" % indent)
356         elif item.type_ == 'constant':
357             if item.name:
358                 print("%sbool %s;" % (indent, item.name))
359         elif item.type_ not in ("constant", "variable"):
360             fatal("unhandled type %s" % item.type_)
361
362
363 def maybe_ord(x):
364     """
365     In Python 2, the elements of byte strings b'asdf' are char.
366     In Python 3, the elements are int.
367     This converts chars to ints.
368     """
369     return x if type(x) is int else ord(x)
370
371
372 def bytes_to_hex(s):
373     return ''.join(['"'] + ["\\x%02x" % maybe_ord(x) for x in s] + ['"'])
374
375
376 class Parser_Context(object):
377     def __init__(self):
378         self.suffixes = {}
379         self.bail = 'error'
380         self.need_error_handler = False
381     def gen_name(self, prefix):
382         n = self.suffixes.get(prefix, 0) + 1
383         self.suffixes[prefix] = n
384         return '%s%d' % (prefix, n) if n > 1 else prefix
385     def save_pos(self, indent):
386         pos = self.gen_name('pos')
387         print("%sstruct spvbin_position %s = spvbin_position_save (input);" % (indent, pos))
388         return pos
389     def save_error(self, indent):
390         error = self.gen_name('save_n_errors')
391         print("%ssize_t %s = input->n_errors;" % (indent, error))
392         return error
393     def parse_limit(self, endian, indent):
394         limit = self.gen_name('saved_limit')
395         print("""\
396 %sstruct spvbin_limit %s;
397 %sif (!spvbin_limit_parse%s (&%s, input))
398 %s    goto %s;""" % (
399     indent, limit,
400     indent, '_be' if endian == 'big' else '', limit,
401     indent, self.bail))
402         return limit
403
404
405 def print_parser_items(name, production, indent, accessor, ctx):
406     for item_idx in range(len(production)):
407         if item_idx > 0:
408             print
409
410         item = production[item_idx]
411         if item.type_ == 'constant':
412             print("""%sif (!spvbin_match_bytes (input, %s, %d))
413 %s    goto %s;""" % (
414                 indent, bytes_to_hex(item.content), len(item.content),
415                 indent, ctx.bail))
416             ctx.need_error_handler = True
417             if item.name:
418                 print("%sp->%s = true;" % (indent, item.name))
419         elif item.type_ == 'variable':
420             if item.content[0] == 'nonterminal':
421                 func = '%sparse_%s' % (prefix, name_to_id(item.content[1]))
422             else:
423                 func = 'spvbin_parse_%s' % item.content[0]
424
425             if item.name:
426                 dst = "&p->%s%s" % (accessor, name_to_id(item.name))
427             else:
428                 dst = "NULL"
429             if item.n == 1:
430                 print("""%sif (!%s (input, %s))
431 %s    goto %s;""" % (indent, func, dst,
432                      indent, ctx.bail))
433
434                 if item.content[0] != 'nonterminal' and item.name == 'version':
435                     print("%sinput->version = p->%s%s;" % (
436                         indent, accessor, name_to_id(item.name)))
437             else:
438                 if isinstance(item.n, int):
439                     count = item.n
440                 else:
441                     count = 'p->%s%s' % (accessor, name_to_id(item.n))
442
443                 i_name = ctx.gen_name('i')
444                 if item.name:
445                     if not isinstance(item.n, int):
446                         print("%sp->%s%s = xcalloc (%s, sizeof *p->%s%s);" % (
447                             indent,
448                             accessor, name_to_id(item.name), count,
449                             accessor, name_to_id(item.name)))
450                     dst += '[%s]' % i_name
451                 print("%sfor (int %s = 0; %s < %s; %s++)" % (
452                     indent, i_name, i_name, count, i_name))
453                 print("""%s    if (!%s (input, %s))
454 %s        goto %s;""" % (indent, func, dst,
455                      indent, ctx.bail))
456
457             ctx.need_error_handler = True
458         elif item.type_ == '()':
459             if item.n != 1:
460                 # Not yet implemented
461                 raise AssertionError
462
463             print_parser_items(name, item.content, indent, accessor, ctx)
464         elif item.type_ in  ('v1', 'v3', 'vAF', 'vB0'):
465             if item.n != 1:
466                 # Not yet implemented
467                 raise AssertionError
468
469             print("%sif (input->version == 0x%s) {" % (indent, item.type_[1:]))
470             print_parser_items(name, item.content, indent + '    ', accessor, ctx)
471             print("%s}" % indent)
472         elif item.type_ in ('count', 'becount'):
473             if item.n != 1:
474                 # Not yet implemented
475                 raise AssertionError
476
477             pos = ctx.save_pos(indent)
478             endian = 'big' if item.type_ == 'becount' else 'little'
479             limit = ctx.parse_limit(endian, indent)
480
481             save_bail = ctx.bail
482             ctx.bail = ctx.gen_name('backtrack')
483
484             print("%sdo {" % indent)
485             indent += '    '
486             if (item.content
487                 and item.content[-1].type_ == 'variable'
488                 and item.content[-1].content[0] == '...'):
489                 content = item.content[:-1]
490                 ellipsis = True
491             else:
492                 content = item.content
493                 ellipsis = False
494             print_parser_items(name, content, indent, accessor, ctx)
495
496             if ellipsis:
497                 print("%sinput->ofs = input->size;" % indent)
498             else:
499                 print("""%sif (!spvbin_input_at_end (input))
500 %s    goto %s;""" % (indent,
501                      indent, ctx.bail))
502             print('%sspvbin_limit_pop (&%s, input);' % (indent, limit))
503             print('%sbreak;' % indent)
504             print('')
505             print('%s%s:' % (indent[4:], ctx.bail))
506             # In theory, we should emit code to clear whatever we're
507             # backtracking from.  In practice, it's not important to
508             # do that.
509             print("%sspvbin_position_restore (&%s, input);" % (indent, pos))
510             print('%sspvbin_limit_pop (&%s, input);' % (indent, limit))
511             print('%sgoto %s;' % (indent, save_bail))
512             indent = indent[4:]
513             print("%s} while (0);" % indent)
514
515             ctx.bail = save_bail
516         elif item.type_ == '|':
517             save_bail = ctx.bail
518
519             print("%sdo {" % indent)
520             indent += '    '
521             pos = ctx.save_pos(indent)
522             error = ctx.save_error(indent)
523             i = 0
524             for choice in item.content:
525                 if i:
526                     print("%sspvbin_position_restore (&%s, input);" % (indent, pos))
527                     print("%sinput->n_errors = %s;" % (indent, error))
528                 i += 1
529
530                 if i != len(item.content):
531                     ctx.bail = ctx.gen_name('backtrack')
532                 else:
533                     ctx.bail = save_bail
534                 print_parser_items(name, choice, indent, accessor, ctx)
535                 print("%sbreak;" % indent)
536                 if i != len(item.content):
537                     print('')
538                     print('%s%s:' % (indent[4:], ctx.bail))
539                     # In theory, we should emit code to clear whatever we're
540                     # backtracking from.  In practice, it's not important to
541                     # do that.
542             indent = indent[4:]
543             print("%s} while (0);" % indent)
544         elif item.type_ == 'case':
545             i = 0
546             for choice_name, choice in sorted(item.content.items()):
547                 if choice_name.endswith('else'):
548                     print("%s} else {" % indent)
549                     print("%s    p->%s%s = -1;"
550                           % (indent, accessor, item.name))
551                     print('')
552                 else:
553                     print("%s%sif (spvbin_match_byte (input, 0x%s)) {" % (
554                         indent, '} else ' if i else '', choice_name[-2:]))
555                     print("%s    p->%s%s = 0x%s;" % (
556                         indent, accessor, item.name, choice_name[-2:]))
557                     print('')
558                     choice = choice[1:]
559
560                 print_parser_items(name, choice, indent + '    ',
561                                    accessor + choice_name + '.', ctx)
562                 i += 1
563             print("%s}" % indent)
564         else:
565             # Not implemented
566             raise AssertionError
567
568
569 def print_parser(name, production, indent):
570     print('''
571 bool
572 %(prefix)sparse_%(name)s (struct spvbin_input *input, struct %(prefix)s%(name)s **p_)
573 {
574     *p_ = NULL;
575     struct %(prefix)s%(name)s *p = xzalloc (sizeof *p);
576     p->start = input->ofs;
577 ''' % {'prefix': prefix,
578        'name': name_to_id(name)})
579
580     ctx = Parser_Context()
581     print_parser_items(name, production, indent, '', ctx)
582
583     print('''
584     p->len = input->ofs - p->start;
585     *p_ = p;
586     return true;''')
587
588     if ctx.need_error_handler:
589         print("""
590 error:
591     spvbin_error (input, "%s", p->start);
592     %sfree_%s (p);
593     return false;""" % (name, prefix, name_to_id(name)))
594
595     print("}")
596
597 def print_free_items(name, production, indent, accessor, ctx):
598     for item in production:
599         if item.type_ == 'constant':
600             pass
601         elif item.type_ == 'variable':
602             if not item.name:
603                 continue
604
605             if item.content[0] == 'nonterminal':
606                 free_func = '%sfree_%s' % (prefix, name_to_id(item.content[1]))
607             elif item.content[0] in ('string', 'bestring', '...'):
608                 free_func = 'free'
609             else:
610                 free_func = None
611
612             dst = "p->%s%s" % (accessor, name_to_id(item.name))
613
614             if item.n == 1:
615                 if free_func:
616                     print("%s%s (%s);" % (indent, free_func, dst))
617             else:
618                 if isinstance(item.n, int):
619                     count = item.n
620                 else:
621                     count = 'p->%s%s' % (accessor, name_to_id(item.n))
622
623                 i_name = ctx.gen_name('i')
624                 if free_func:
625                     print("%sfor (int %s = 0; %s < %s; %s++)" % (
626                         indent, i_name, i_name, count, i_name))
627                     print("%s    %s (%s[%s]);" % (
628                         indent, free_func, dst, i_name))
629                 if not isinstance(item.n, int):
630                     print("%sfree (p->%s%s);" % (
631                         indent, accessor, name_to_id(item.name)))
632         elif item.type_ in ('()', 'v1', 'v3', 'vAF', 'vB0',
633                             'count', 'becount'):
634             if item.n != 1:
635                 # Not yet implemented
636                 raise AssertionError
637
638             print_free_items(name, item.content, indent, accessor, ctx)
639         elif item.type_ == '|':
640             for choice in item.content:
641                 print_free_items(name, choice, indent, accessor, ctx)
642         elif item.type_ == 'case':
643             i = 0
644             for choice_name, choice in sorted(item.content.items()):
645                 if choice_name.endswith('else'):
646                     value_name = '-1'
647                 else:
648                     value_name = '0x%s' % choice_name[-2:]
649
650                 print('%s%sif (p->%s%s == %s) {' % (
651                     indent, '} else ' if i else '', accessor, item.name,
652                     value_name))
653
654                 print_free_items(name, choice, indent + '    ',
655                                  accessor + choice_name + '.', ctx)
656                 i += 1
657             print("%s}" % indent)
658         else:
659             # Not implemented
660             raise AssertionError
661
662 def print_free(name, production, indent):
663     print('''
664 void
665 %(prefix)sfree_%(name)s (struct %(prefix)s%(name)s *p)
666 {
667     if (p == NULL)
668         return;
669 ''' % {'prefix': prefix,
670        'name': name_to_id(name)})
671
672     print_free_items(name, production, indent, '', Parser_Context())
673
674     print("    free (p);")
675     print("}")
676
677 def print_print_items(name, production, indent, accessor, ctx):
678     for item_idx in range(len(production)):
679         if item_idx > 0:
680             print('')
681
682         item = production[item_idx]
683         if item.type_ == 'constant':
684             if item.name:
685                 print('%sspvbin_print_presence ("%s", indent + 1, p->%s);' % (
686                     indent, item.name, item.name))
687         elif item.type_ == 'variable':
688             if not item.name:
689                 continue
690
691             if item.content[0] == 'nonterminal':
692                 func = '%sprint_%s' % (prefix, name_to_id(item.content[1]))
693             else:
694                 c_types = {'bool': 'bool',
695                            'byte': 'byte',
696                            'int16': 'int16',
697                            'int32': 'int32',
698                            'int64': 'int64',
699                            'be16': 'int16',
700                            'be32': 'int32',
701                            'be64': 'int64',
702                            'string': 'string',
703                            'bestring': 'string',
704                            'float': 'double',
705                            'double': 'double',
706                            '...': ('uint8_t', 1)}
707                 func = 'spvbin_print_%s' % c_types[item.content[0]]
708
709             dst = "p->%s%s" % (accessor, name_to_id(item.name))
710             if item.n == 1:
711                 print('%s%s ("%s", indent + 1, %s);' % (indent, func,
712                                                         item.name, dst))
713             else:
714                 if isinstance(item.n, int):
715                     count = item.n
716                 else:
717                     count = 'p->%s%s' % (accessor, name_to_id(item.n))
718
719                 i_name = ctx.gen_name('i')
720                 elem_name = ctx.gen_name('elem_name')
721                 dst += '[%s]' % i_name
722                 print("""\
723 %(indent)sfor (int %(index)s = 0; %(index)s < %(count)s; %(index)s++) {
724 %(indent)s    char *%(elem_name)s = xasprintf ("%(item.name)s[%%d]", %(index)s);
725 %(indent)s    %(func)s (%(elem_name)s, indent + 1, %(dst)s);
726 %(indent)s    free (%(elem_name)s);
727 %(indent)s}""" % {'indent': indent,
728                   'index': i_name,
729                   'count': count,
730                   'elem_name' : elem_name,
731                   'item.name': item.name,
732                   'func': func,
733                   'dst': dst})
734         elif item.type_ == '()':
735             if item.n != 1:
736                 # Not yet implemented
737                 raise AssertionError
738
739             print_print_items(name, item.content, indent, accessor, ctx)
740         elif item.type_ in  ('v1', 'v3', 'vAF', 'vB0'):
741             if item.n != 1:
742                 # Not yet implemented
743                 raise AssertionError
744
745             print_print_items(name, item.content, indent, accessor, ctx)
746         elif item.type_ in ('count', 'becount'):
747             if item.n != 1:
748                 # Not yet implemented
749                 raise AssertionError
750
751             indent += '    '
752             if (item.content
753                 and item.content[-1].type_ == 'variable'
754                 and item.content[-1].content[0] == '...'):
755                 content = item.content[:-1]
756             else:
757                 content = item.content
758             print_print_items(name, content, indent, accessor, ctx)
759         elif item.type_ == '|':
760             for choice in item.content:
761                 print_print_items(name, choice, indent, accessor, ctx)
762         elif item.type_ == 'case':
763             i = 0
764             print("""\
765 %sspvbin_print_case ("%s", indent + 1, p->%s%s);""" % (
766     indent, item.name, accessor, name_to_id(item.name)))
767             for choice_name, choice in sorted(item.content.items()):
768                 if choice_name.endswith('else'):
769                     value_name = '-1'
770                 else:
771                     value_name = '0x%s' % choice_name[-2:]
772
773                 print('%s%sif (p->%s%s == %s) {' % (
774                     indent, '} else ' if i else '', accessor, item.name,
775                     value_name))
776
777                 print_print_items(name, choice, indent + '    ',
778                                   accessor + choice_name + '.', ctx)
779                 i += 1
780             print("%s}" % indent)
781         else:
782             # Not implemented
783             raise AssertionError
784
785
786 def print_print(name, production, indent):
787     print('''
788 void
789 %(prefix)sprint_%(name)s (const char *title, int indent, const struct %(prefix)s%(name)s *p)
790 {
791     spvbin_print_header (title, p ? p->start : -1, p ? p->len : -1, indent);
792     if (p == NULL) {
793         printf ("none\\n");
794         return;
795     }
796     putchar ('\\n');
797 ''' % {'prefix': prefix,
798        'rawname': name,
799        'name': name_to_id(name)})
800
801     ctx = Parser_Context()
802     print_print_items(name, production, indent, '', ctx)
803
804     print("}")
805
806 def name_to_id(s):
807     return s[0].lower() + ''.join(['_%c' % x.lower() if x.isupper() else x
808                                    for x in s[1:]]).replace('-', '_')
809
810
811 if __name__ == "__main__":
812     argv0 = sys.argv[0]
813     try:
814         options, args = getopt.gnu_getopt(sys.argv[1:], 'h', ['help'])
815     except getopt.GetoptError as e:
816         sys.stderr.write("%s: %s\n" % (argv0, e.msg))
817         sys.exit(1)
818
819     for key, value in options:
820         if key in ['-h', '--help']:
821             usage()
822         else:
823             sys.exit(0)
824
825     if len(args) < 3:
826         sys.stderr.write("%s: bad usage (use --help for help)\n" % argv0)
827         sys.exit(1)
828
829     global file_name
830     file_name, output_type, prefix = args[:3]
831     input_file = open(file_name)
832
833     prefix = '%s_' % prefix
834
835     global line
836     global line_number
837     line = ""
838     line_number = 0
839
840     productions = {}
841
842     global token
843     token = ('start', )
844     get_token()
845     while True:
846         while match(';'):
847             pass
848         if token[0] == 'eof':
849             break
850
851         name, production = parse_production()
852         if name in productions:
853             fatal("%s: duplicate production" % name)
854         productions[name] = production
855
856     print('/* Generated automatically -- do not modify!    -*- buffer-read-only: t -*- */')
857     if output_type == 'code' and len(args) == 4:
858         header_name = args[3]
859
860         print("""\
861 #include <config.h>
862 #include %s
863 #include <stdio.h>
864 #include <stdlib.h>
865 #include "libpspp/str.h"
866 #include "gl/xalloc.h"\
867 """ % header_name)
868         for name, production in productions.items():
869             print_parser(name, production, ' ' * 4)
870             print_free(name, production, ' ' * 4)
871             print_print(name, production, ' ' * 4)
872     elif output_type == 'header' and len(args) == 3:
873         print("""\
874 #ifndef %(PREFIX)sPARSER_H
875 #define %(PREFIX)sPARSER_H
876
877 #include <stddef.h>
878 #include <stdint.h>
879 #include <stdbool.h>
880 #include "output/spv/spvbin-helpers.h"\
881 """ % {'PREFIX': prefix.upper()})
882         for name, production in productions.items():
883             print('\nstruct %s%s {' % (prefix, name_to_id(name)))
884             print("    size_t start, len;")
885             print_members(production, ' ' * 4)
886             print('''};
887 bool %(prefix)sparse_%(name)s (struct spvbin_input *, struct %(prefix)s%(name)s **);
888 void %(prefix)sfree_%(name)s (struct %(prefix)s%(name)s *);
889 void %(prefix)sprint_%(name)s (const char *title, int indent, const struct %(prefix)s%(name)s *);\
890 ''' % {'prefix': prefix,
891        'name': name_to_id(name)})
892         print("""\
893
894 #endif /* %(PREFIX)sPARSER_H */""" % {'PREFIX': prefix.upper()})
895     else:
896         sys.stderr.write("%s: bad usage (use --help for help)" % argv0)