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