Add support for reading and writing SPV files.
[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' % 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' % 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 bytes_to_hex(s):
355     return ''.join(['"'] + ["\\x%02x" % ord(x) for x in s] + ['"'])
356
357
358 class Parser_Context(object):
359     def __init__(self):
360         self.suffixes = {}
361         self.bail = 'error'
362         self.need_error_handler = False
363     def gen_name(self, prefix):
364         n = self.suffixes.get(prefix, 0) + 1
365         self.suffixes[prefix] = n
366         return '%s%d' % (prefix, n) if n > 1 else prefix
367     def save_pos(self, indent):
368         pos = self.gen_name('pos')
369         print "%sstruct spvbin_position %s = spvbin_position_save (input);" % (indent, pos)
370         return pos
371     def save_error(self, indent):
372         error = self.gen_name('save_n_errors')
373         print "%ssize_t %s = input->n_errors;" % (indent, error)
374         return error
375     def parse_limit(self, endian, indent):
376         limit = self.gen_name('saved_limit')
377         print """\
378 %sstruct spvbin_limit %s;
379 %sif (!spvbin_limit_parse%s (&%s, input))
380 %s    goto %s;""" % (
381     indent, limit,
382     indent, '_be' if endian == 'big' else '', limit,
383     indent, self.bail)
384         return limit
385         
386
387 def print_parser_items(name, production, indent, accessor, ctx):
388     for item_idx in range(len(production)):
389         if item_idx > 0:
390             print
391
392         item = production[item_idx]
393         if item.type_ == 'constant':
394             print """%sif (!spvbin_match_bytes (input, %s, %d))
395 %s    goto %s;""" % (
396                 indent, bytes_to_hex(item.content), len(item.content),
397                 indent, ctx.bail)
398             ctx.need_error_handler = True
399             if item.name:
400                 print "%sp->%s = true;" % (indent, item.name)
401         elif item.type_ == 'variable':
402             if item.content[0] == 'nonterminal':
403                 func = '%sparse_%s' % (prefix, name_to_id(item.content[1]))
404             else:
405                 func = 'spvbin_parse_%s' % item.content[0]
406
407             if item.name:
408                 dst = "&p->%s%s" % (accessor, name_to_id(item.name))
409             else:
410                 dst = "NULL"
411             if item.n == 1:
412                 print """%sif (!%s (input, %s))
413 %s    goto %s;""" % (indent, func, dst,
414                      indent, ctx.bail)
415
416                 if item.content[0] != 'nonterminal' and item.name == 'version':
417                     print "%sinput->version = p->%s%s;" % (
418                         indent, accessor, name_to_id(item.name))
419             else:
420                 if isinstance(item.n, int):
421                     count = item.n
422                 else:
423                     count = 'p->%s%s' % (accessor, name_to_id(item.n))
424
425                 i_name = ctx.gen_name('i')
426                 if item.name:
427                     if not isinstance(item.n, int):
428                         print "%sp->%s%s = xcalloc (%s, sizeof *p->%s%s);" % (
429                             indent,
430                             accessor, name_to_id(item.name), count,
431                             accessor, name_to_id(item.name))
432                     dst += '[%s]' % i_name
433                 print "%sfor (int %s = 0; %s < %s; %s++)" % (
434                     indent, i_name, i_name, count, i_name)
435                 print """%s    if (!%s (input, %s))
436 %s        goto %s;""" % (indent, func, dst,
437                      indent, ctx.bail)
438
439             ctx.need_error_handler = True
440         elif item.type_ == '()':
441             if item.n != 1:
442                 # Not yet implemented
443                 raise AssertionError
444
445             print_parser_items(name, item.content, indent, accessor, ctx)
446         elif item.type_ in  ('v1', 'v3', 'vAF', 'vB0'):
447             if item.n != 1:
448                 # Not yet implemented
449                 raise AssertionError
450
451             print "%sif (input->version == 0x%s) {" % (indent, item.type_[1:])
452             print_parser_items(name, item.content, indent + '    ', accessor, ctx)
453             print "%s}" % indent
454         elif item.type_ in ('count', 'becount'):
455             if item.n != 1:
456                 # Not yet implemented
457                 raise AssertionError
458
459             pos = ctx.save_pos(indent)
460             endian = 'big' if item.type_ == 'becount' else 'little'
461             limit = ctx.parse_limit(endian, indent)
462
463             save_bail = ctx.bail
464             ctx.bail = ctx.gen_name('backtrack')
465
466             print "%sdo {" % indent
467             indent += '    '
468             if (item.content
469                 and item.content[-1].type_ == 'variable'
470                 and item.content[-1].content[0] == '...'):
471                 content = item.content[:-1]
472                 ellipsis = True
473             else:
474                 content = item.content
475                 ellipsis = False
476             print_parser_items(name, content, indent, accessor, ctx)
477
478             if ellipsis:
479                 print "%sinput->ofs = input->size;" % indent
480             else:
481                 print """%sif (!spvbin_input_at_end (input))
482 %s    goto %s;""" % (indent,
483                      indent, ctx.bail)
484             print '%sspvbin_limit_pop (&%s, input);' % (indent, limit)
485             print '%sbreak;' % indent
486             print
487             print '%s%s:' % (indent[4:], ctx.bail)
488             # In theory, we should emit code to clear whatever we're
489             # backtracking from.  In practice, it's not important to
490             # do that.
491             print "%sspvbin_position_restore (&%s, input);" % (indent, pos)
492             print '%sspvbin_limit_pop (&%s, input);' % (indent, limit)
493             print '%sgoto %s;' % (indent, save_bail)
494             indent = indent[4:]
495             print "%s} while (0);" % indent
496
497             ctx.bail = save_bail
498         elif item.type_ == '|':
499             save_bail = ctx.bail
500
501             print "%sdo {" % indent
502             indent += '    '
503             pos = ctx.save_pos(indent)
504             error = ctx.save_error(indent)
505             i = 0
506             for choice in item.content:
507                 if i:
508                     print "%sspvbin_position_restore (&%s, input);" % (indent, pos)
509                     print "%sinput->n_errors = %s;" % (indent, error)
510                 i += 1
511
512                 if i != len(item.content):
513                     ctx.bail = ctx.gen_name('backtrack')
514                 else:
515                     ctx.bail = save_bail
516                 print_parser_items(name, choice, indent, accessor, ctx)
517                 print "%sbreak;" % indent
518                 if i != len(item.content):
519                     print
520                     print '%s%s:' % (indent[4:], ctx.bail)
521                     # In theory, we should emit code to clear whatever we're
522                     # backtracking from.  In practice, it's not important to
523                     # do that.
524             indent = indent[4:]
525             print "%s} while (0);" % indent
526         elif item.type_ == 'case':
527             i = 0
528             for choice_name, choice in sorted(item.content.items()):
529                 if choice_name.endswith('else'):
530                     print "%s} else {" % indent
531                     print "%s    p->%s%s = -1;" % (indent, accessor, item.name)
532                     print
533                 else:
534                     print "%s%sif (spvbin_match_byte (input, 0x%s)) {" % (
535                         indent, '} else ' if i else '', choice_name[-2:])
536                     print "%s    p->%s%s = 0x%s;" % (
537                         indent, accessor, item.name, choice_name[-2:])
538                     print
539                     choice = choice[1:]
540                 
541                 print_parser_items(name, choice, indent + '    ',
542                                    accessor + choice_name + '.', ctx)
543                 i += 1
544             print "%s}" % indent
545         else:
546             # Not implemented
547             raise AssertionError
548
549
550 def print_parser(name, production, indent):
551     print '''
552 bool
553 %(prefix)sparse_%(name)s (struct spvbin_input *input, struct %(prefix)s%(name)s **p_)
554 {
555     *p_ = NULL;
556     struct %(prefix)s%(name)s *p = xzalloc (sizeof *p);
557     p->start = input->ofs;
558 ''' % {'prefix': prefix,
559        'name': name_to_id(name)}
560
561     ctx = Parser_Context()
562     print_parser_items(name, production, indent, '', ctx)
563
564     print '''
565     p->len = input->ofs - p->start;
566     *p_ = p;
567     return true;'''
568
569     if ctx.need_error_handler:
570         print """
571 error:
572     spvbin_error (input, "%s", p->start);
573     %sfree_%s (p);
574     return false;""" % (name, prefix, name_to_id(name))
575
576     print "}"
577
578 def print_free_items(name, production, indent, accessor, ctx):
579     for item in production:
580         if item.type_ == 'constant':
581             pass
582         elif item.type_ == 'variable':
583             if not item.name:
584                 continue
585
586             if item.content[0] == 'nonterminal':
587                 free_func = '%sfree_%s' % (prefix, name_to_id(item.content[1]))
588             elif item.content[0] in ('string', 'bestring', '...'):
589                 free_func = 'free'
590             else:
591                 free_func = None
592
593             dst = "p->%s%s" % (accessor, name_to_id(item.name))
594
595             if item.n == 1:
596                 if free_func:
597                     print "%s%s (%s);" % (indent, free_func, dst)
598             else:
599                 if isinstance(item.n, int):
600                     count = item.n
601                 else:
602                     count = 'p->%s%s' % (accessor, name_to_id(item.n))
603
604                 i_name = ctx.gen_name('i')
605                 if free_func:
606                     print "%sfor (int %s = 0; %s < %s; %s++)" % (
607                         indent, i_name, i_name, count, i_name)
608                     print "%s    %s (%s[%s]);" % (
609                         indent, free_func, dst, i_name)
610                 if not isinstance(item.n, int):
611                     print "%sfree (p->%s%s);" % (
612                         indent, accessor, name_to_id(item.name))
613         elif item.type_ in ('()', 'v1', 'v3', 'vAF', 'vB0',
614                             'count', 'becount'):
615             if item.n != 1:
616                 # Not yet implemented
617                 raise AssertionError
618
619             print_free_items(name, item.content, indent, accessor, ctx)
620         elif item.type_ == '|':
621             for choice in item.content:
622                 print_free_items(name, choice, indent, accessor, ctx)
623         elif item.type_ == 'case':
624             i = 0
625             for choice_name, choice in sorted(item.content.items()):
626                 if choice_name.endswith('else'):
627                     value_name = '-1'
628                 else:
629                     value_name = '0x%s' % choice_name[-2:]
630
631                 print '%s%sif (p->%s%s == %s) {' % (
632                     indent, '} else ' if i else '', accessor, item.name,
633                     value_name)
634                 
635                 print_free_items(name, choice, indent + '    ',
636                                  accessor + choice_name + '.', ctx)
637                 i += 1
638             print "%s}" % indent
639         else:
640             # Not implemented
641             raise AssertionError
642
643 def print_free(name, production, indent):
644     print '''
645 void
646 %(prefix)sfree_%(name)s (struct %(prefix)s%(name)s *p)
647 {
648     if (p == NULL)
649         return;
650 ''' % {'prefix': prefix,
651        'name': name_to_id(name)}
652
653     print_free_items(name, production, indent, '', Parser_Context())
654
655     print "    free (p);"
656     print "}"
657
658 def print_print_items(name, production, indent, accessor, ctx):
659     for item_idx in range(len(production)):
660         if item_idx > 0:
661             print
662
663         item = production[item_idx]
664         if item.type_ == 'constant':
665             if item.name:
666                 print '%sspvbin_print_presence ("%s", indent + 1, p->%s);' % (
667                     indent, item.name, item.name)
668         elif item.type_ == 'variable':
669             if not item.name:
670                 continue
671
672             if item.content[0] == 'nonterminal':
673                 func = '%sprint_%s' % (prefix, name_to_id(item.content[1]))
674             else:
675                 c_types = {'bool': 'bool',
676                            'byte': 'byte',
677                            'int16': 'int16',
678                            'int32': 'int32',
679                            'int64': 'int64',
680                            'be16': 'int16',
681                            'be32': 'int32',
682                            'be64': 'int64',
683                            'string': 'string',
684                            'bestring': 'string',
685                            'float': 'double',
686                            'double': 'double',
687                            '...': ('uint8_t', 1)}
688                 func = 'spvbin_print_%s' % c_types[item.content[0]]
689
690             dst = "p->%s%s" % (accessor, name_to_id(item.name))
691             if item.n == 1:
692                 print '%s%s ("%s", indent + 1, %s);' % (indent, func,
693                                                       item.name, dst)
694             else:
695                 if isinstance(item.n, int):
696                     count = item.n
697                 else:
698                     count = 'p->%s%s' % (accessor, name_to_id(item.n))
699
700                 i_name = ctx.gen_name('i')
701                 elem_name = ctx.gen_name('elem_name')
702                 dst += '[%s]' % i_name
703                 print """\
704 %(indent)sfor (int %(index)s = 0; %(index)s < %(count)s; %(index)s++) {
705 %(indent)s    char *%(elem_name)s = xasprintf ("%(item.name)s[%%d]", %(index)s);
706 %(indent)s    %(func)s (%(elem_name)s, indent + 1, %(dst)s);
707 %(indent)s    free (%(elem_name)s);
708 %(indent)s}""" % {'indent': indent,
709                   'index': i_name,
710                   'count': count,
711                   'elem_name' : elem_name,
712                   'item.name': item.name,
713                   'func': func,
714                   'dst': dst}
715         elif item.type_ == '()':
716             if item.n != 1:
717                 # Not yet implemented
718                 raise AssertionError
719
720             print_print_items(name, item.content, indent, accessor, ctx)
721         elif item.type_ in  ('v1', 'v3', 'vAF', 'vB0'):
722             if item.n != 1:
723                 # Not yet implemented
724                 raise AssertionError
725
726             print_print_items(name, item.content, indent, accessor, ctx)
727         elif item.type_ in ('count', 'becount'):
728             if item.n != 1:
729                 # Not yet implemented
730                 raise AssertionError
731
732             indent += '    '
733             if (item.content
734                 and item.content[-1].type_ == 'variable'
735                 and item.content[-1].content[0] == '...'):
736                 content = item.content[:-1]
737             else:
738                 content = item.content
739             print_print_items(name, content, indent, accessor, ctx)
740         elif item.type_ == '|':
741             for choice in item.content:
742                 print_print_items(name, choice, indent, accessor, ctx)
743         elif item.type_ == 'case':
744             i = 0
745             print """\
746 %sspvbin_print_case ("%s", indent + 1, p->%s%s);""" % (
747     indent, item.name, accessor, name_to_id(item.name))
748             for choice_name, choice in sorted(item.content.items()):
749                 if choice_name.endswith('else'):
750                     value_name = '-1'
751                 else:
752                     value_name = '0x%s' % choice_name[-2:]
753
754                 print '%s%sif (p->%s%s == %s) {' % (
755                     indent, '} else ' if i else '', accessor, item.name,
756                     value_name)
757                 
758                 print_print_items(name, choice, indent + '    ',
759                                   accessor + choice_name + '.', ctx)
760                 i += 1
761             print "%s}" % indent
762         else:
763             # Not implemented
764             raise AssertionError
765
766
767 def print_print(name, production, indent):
768     print '''
769 void
770 %(prefix)sprint_%(name)s (const char *title, int indent, const struct %(prefix)s%(name)s *p)
771 {
772     spvbin_print_header (title, p ? p->start : -1, p ? p->len : -1, indent);
773     if (p == NULL) {
774         printf ("none\\n");
775         return;
776     }
777     putchar ('\\n');
778 ''' % {'prefix': prefix,
779        'rawname': name,
780        'name': name_to_id(name)}
781
782     ctx = Parser_Context()
783     print_print_items(name, production, indent, '', ctx)
784
785     print "}"
786
787 def name_to_id(s):
788     return s[0].lower() + ''.join(['_%c' % x.lower() if x.isupper() else x
789                                    for x in s[1:]]).replace('-', '_')
790     
791
792 if __name__ == "__main__":
793     argv0 = sys.argv[0]
794     try:
795         options, args = getopt.gnu_getopt(sys.argv[1:], 'h', ['help'])
796     except getopt.GetoptError as e:
797         sys.stderr.write("%s: %s\n" % (argv0, e.msg))
798         sys.exit(1)
799
800     for key, value in options:
801         if key in ['-h', '--help']:
802             usage()
803         else:
804             sys.exit(0)
805
806     if len(args) < 3:
807         sys.stderr.write("%s: bad usage (use --help for help)\n" % argv0)
808         sys.exit(1)
809
810     global file_name
811     file_name, output_type, prefix = args[:3]
812     input_file = open(file_name)
813
814     prefix = '%s_' % prefix
815
816     global line
817     global line_number
818     line = ""
819     line_number = 0
820
821     productions = {}
822
823     global token
824     token = ('start', )
825     get_token()
826     while True:
827         while match(';'):
828             pass
829         if token[0] == 'eof':
830             break
831
832         name, production = parse_production()
833         if name in productions:
834             fatal("%s: duplicate production" % name)
835         productions[name] = production
836
837     print '/* Generated automatically -- do not modify!    -*- buffer-read-only: t -*- */'
838     if output_type == 'code' and len(args) == 4:
839         header_name = args[3]
840
841         print """\
842 #include <config.h>
843 #include %s
844 #include <stdio.h>
845 #include <stdlib.h>
846 #include "libpspp/str.h"
847 #include "gl/xalloc.h"\
848 """ % header_name
849         for name, production in productions.items():
850             print_parser(name, production, ' ' * 4)
851             print_free(name, production, ' ' * 4)
852             print_print(name, production, ' ' * 4)
853     elif output_type == 'header' and len(args) == 3:
854         print """\
855 #ifndef %(PREFIX)sPARSER_H
856 #define %(PREFIX)sPARSER_H
857
858 #include <stddef.h>
859 #include <stdint.h>
860 #include <stdbool.h>
861 #include "output/spv/spvbin-helpers.h"\
862 """ % {'PREFIX': prefix.upper()}
863         for name, production in productions.items():
864             print '\nstruct %s%s {' % (prefix, name_to_id(name))
865             print "    size_t start, len;"
866             print_members(production, ' ' * 4)
867             print '''};
868 bool %(prefix)sparse_%(name)s (struct spvbin_input *, struct %(prefix)s%(name)s **);
869 void %(prefix)sfree_%(name)s (struct %(prefix)s%(name)s *);
870 void %(prefix)sprint_%(name)s (const char *title, int indent, const struct %(prefix)s%(name)s *);\
871 ''' % {'prefix': prefix,
872        'name': name_to_id(name)}
873         print """\
874
875 #endif /* %(PREFIX)sPARSER_H */""" % {'PREFIX': prefix.upper()}
876     else:
877         sys.stderr.write("%s: bad usage (use --help for help)" % argv0)