Add support for reading and writing SPV files.
[pspp] / src / output / spv / xml-parser-generator
1 #! /usr/bin/python
2
3 import getopt
4 import os
5 import re
6 import struct
7 import sys
8
9 n_errors = 0
10
11 def error(msg):
12     global n_errors
13     sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg))
14     n_errors += 1
15
16
17 def fatal(msg):
18     error(msg)
19     sys.exit(1)
20
21
22 def get_line():
23     global line
24     global line_number
25     line = input_file.readline()
26     line = re.sub('#.*', '\n', line)
27     line_number += 1
28
29
30 def expect(type):
31     if token[0] != type:
32         fatal("syntax error expecting %s" % type)
33
34
35 def match(type):
36     if token[0] == type:
37         get_token()
38         return True
39     else:
40         return False
41
42
43 def must_match(type):
44     expect(type)
45     get_token()
46
47
48 def match_id(id_):
49     if token == ('id', id_):
50         get_token()
51         return True
52     else:
53         return False
54
55
56 def is_idchar(c):
57     return c.isalnum() or c in '-_'
58
59
60 def get_token():
61     global token
62     global line
63     prev = token
64     while True:
65         if line == "":
66             if token == ('eof', ):
67                 fatal("unexpected end of input")
68             get_line()
69             if not line:
70                 token = ('eof', )
71                 break
72             elif line == '\n':
73                 token = (';', )
74                 break
75
76         line = line.lstrip()
77         if line == "":
78             continue
79
80         if line.startswith('=>'):
81             token = (line[:2],)
82             line = line[2:]
83         elif line[0] in '[]()?|*+=:':
84             token = (line[0],)
85             line = line[1:]
86         elif is_idchar(line[0]):
87             n = 1
88             while n < len(line) and is_idchar(line[n]):
89                 n += 1
90             s = line[:n]
91             token = ('id', s)
92             line = line[n:]
93         else:
94             fatal("unknown character %c" % line[0])
95         break
96
97
98 def usage():
99     argv0 = os.path.basename(sys.argv[0])
100     print('''\
101 %(argv0)s, parser generator for SPV XML members
102 usage: %(argv0)s GRAMMAR header PREFIX
103        %(argv0)s GRAMMAR code PREFIX HEADER_NAME
104   where GRAMMAR contains grammar definitions\
105 ''' % {"argv0": argv0})
106     sys.exit(0)
107
108
109 def parse_term():
110     if match('('):
111         sub = parse_alternation()
112         must_match(')')
113         return sub
114     else:
115         member_name, nonterminal_name = parse_name()
116         if member_name.isupper():
117             fatal('%s; unknown terminal' % member_name)
118         else:
119             return {'type': 'nonterminal',
120                     'nonterminal_name': nonterminal_name,
121                     'member_name': member_name}
122
123
124 def parse_quantified():
125     item = parse_term()
126     if token[0] in ['*', '+', '?']:
127         item = {'type': token[0], 'item': item}
128         get_token()
129     return item
130
131
132 def parse_sequence():
133     if match_id('EMPTY'):
134         return {'type': 'empty'}
135     items = []
136     while True:
137         sub = parse_quantified()
138         if sub['type'] == 'sequence':
139             items.extend(sub[1:])
140         else:
141             items.append(sub)
142         if token[0] in ('|', ';', ')', 'eof'):
143             break
144     return {'type': 'sequence', 'items': items} if len(items) > 1 else items[0]
145
146
147 def parse_alternation():
148     items = [parse_sequence()]
149     while match('|'):
150         items.append(parse_sequence())
151     if len(items) > 1:
152         return {'type': '|', 'items': items}
153     else:
154         return items[0]
155
156
157 def parse_name():
158     # The name used in XML for the attribute or element always comes
159     # first.
160     expect('id')
161     xml_name = token[1]
162     get_token()
163
164     # If a different name is needed to disambiguate when the same name
165     # is used in different contexts in XML, it comes later, in
166     # brackets.
167     if match('['):
168         expect('id')
169         unique_name = token[1]
170         get_token()
171         must_match(']')
172     else:
173         unique_name = xml_name
174
175     return unique_name, xml_name
176
177
178 enums = {}
179 def parse_production():
180     unique_name, xml_name = parse_name()
181
182     attr_xml_names = set()
183     attributes = {}
184     while match(':'):
185         attr_unique_name, attr_xml_name = parse_name()
186         if match('='):
187             if match('('):
188                 attr_value = set()
189                 while not match(')'):
190                     expect('id')
191                     attr_value.add(token[1])
192                     get_token()
193                     match('|')
194
195                 global enums
196                 if attr_unique_name not in enums:
197                     enums[attr_unique_name] = attr_value
198                 elif enums[attr_unique_name] != attr_value:
199                     sys.stderr.write('%s: different enums with same name\n'
200                                      % attr_unique_name)
201                     sys.exit(1)
202             elif match_id('bool'):
203                 attr_value = set(('true', 'false'))
204             elif match_id('dimension'):
205                 attr_value = 'dimension'
206             elif match_id('real'):
207                 attr_value = 'real'
208             elif match_id('int'):
209                 attr_value = 'int'
210             elif match_id('color'):
211                 attr_value = 'color'
212             elif match_id('ref'):
213                 if token[0] == 'id':
214                     ref_type = token[1]
215                     attr_value = ('ref', ref_type)
216                     get_token()
217                 elif match('('):
218                     ref_types = set()
219                     while not match(')'):
220                         expect('id')
221                         ref_types.add(token[1])
222                         get_token()
223                         match('|')
224                     attr_value = ('ref', ref_types)
225                 else:
226                     attr_value = ('ref', None)
227             else:
228                 fatal("unknown attribute value type")
229         else:
230             attr_value = 'string'
231         attr_required = not match('?')
232
233         if attr_xml_name == 'id':
234             if attr_value != 'string':
235                 fatal("id attribute must have string type")
236             attr_value = 'id'
237
238         if attr_unique_name in attributes:
239             fatal("production %s has two attributes %s" % (unique_name,
240                                                            attr_unique_name))
241         if attr_xml_name in attr_xml_names:
242             fatal("production %s has two attributes %s" % (unique_name,
243                                                            attr_xml_name))
244         attr_xml_names.add(attr_xml_name)
245         attributes[attr_unique_name] = (attr_xml_name,
246                                         attr_value, attr_required)
247     if 'id' not in attributes:
248         attributes["id"] = ('id', 'id', False)
249
250     must_match('=>')
251
252     if match_id('TEXT'):
253         rhs = {'type': 'text'}
254     elif match_id('ETC'):
255         rhs = {'type': 'etc'}
256     else:
257         rhs = parse_alternation()
258
259     n = 0
260     for a in rhs['items'] if rhs['type'] == '|' else (rhs,):
261         for term in a['items'] if a['type'] == 'sequence' else (a,):
262             if term['type'] == 'empty':
263                 pass
264             elif term['type'] == 'nonterminal':
265                 pass
266             elif term['type'] == '?' and term['item']['type'] == 'nonterminal':
267                 pass
268             elif (term['type'] in ('*', '+')
269                   and term['item']['type'] == 'nonterminal'):
270                 pass
271             else:
272                 n += 1
273                 term['seq_name'] = 'seq' if n == 1 else 'seq%d' % n
274
275     return unique_name, xml_name, attributes, rhs
276
277
278 used_enums = set()
279 def print_members(attributes, rhs, indent):
280     attrs = []
281     new_enums = set()
282     for unique_name, (xml_name, value, required) in attributes.items():
283         c_name = name_to_id(unique_name)
284         if type(value) is set:
285             if len(value) <= 1:
286                 if not required:
287                     attrs += [('bool %s_present;' % c_name,
288                                'True if attribute present')]
289             elif value == set(('true', 'false')):
290                 if required:
291                     attrs += [('bool %s;' % c_name, None)]
292                 else:
293                     attrs += [('int %s;' % c_name,
294                                '-1 if not present, otherwise 0 or 1')]
295             else:
296                 attrs += [('enum %s%s %s;' % (prefix, c_name, c_name),
297                            'Always nonzero' if required else
298                            'Zero if not present')]
299
300                 global used_enums
301                 if unique_name not in used_enums:
302                     new_enums.add(unique_name)
303         elif value == 'dimension' or value == 'real':
304             attrs += [('double %s;' % c_name,
305                        'In inches.  ' + ('Always present' if required else
306                                          'DBL_MAX if not present'))]
307         elif value == 'int':
308             attrs += [('int %s;' % c_name,
309                        'Always present' if required
310                        else 'INT_MIN if not present')]
311         elif value == 'color':
312             attrs += [('int %s;' % c_name,
313                        'Always present' if required
314                        else '-1 if not present')]
315         elif value == 'string':
316             attrs += [('char *%s;' % c_name,
317                        'Always nonnull' if required else 'Possibly null')]
318         elif value[0] == 'ref':
319             struct = ('spvxml_node'
320                       if value[1] is None or type(value[1]) is set
321                       else '%s%s' % (prefix, name_to_id(value[1])))
322             attrs += [('struct %s *%s;' % (struct, c_name),
323                        'Always nonnull' if required else 'Possibly null')]
324         elif value == 'id':
325             pass
326         else:
327             assert False
328
329     for enum_name in new_enums:
330         used_enums.add(enum_name)
331         c_name = name_to_id(enum_name)
332         print '\nenum %s%s {' % (prefix, c_name)
333         i = 0
334         for value in sorted(enums[enum_name]):
335             print '    %s%s_%s%s,' % (prefix.upper(),
336                                       c_name.upper(),
337                                       name_to_id(value).upper(),
338                                       ' = 1' if i == 0 else '')
339             i += 1
340         print '};'
341         print 'const char *%s%s_to_string (enum %s%s);' % (
342             prefix, c_name, prefix, c_name)
343
344     print '\nstruct %s%s {' % (prefix, name_to_id(name))
345     print '%sstruct spvxml_node node_;' % indent
346
347     if attrs:
348         print '\n%s/* Attributes. */' % indent
349         for decl, comment in attrs:
350             line = '%s%s' % (indent, decl)
351             if comment:
352                 n_spaces = max(35 - len(line), 1)
353                 line += '%s/* %s. */' % (' ' * n_spaces, comment)
354             print line
355
356     if rhs['type'] == 'etc' or rhs['type'] == 'empty':
357         return
358
359     print '\n%s/* Content. */' % indent
360     if rhs['type'] == 'text':
361         print '%schar *text; /* Always nonnull. */' % indent
362         return
363
364     for a in rhs['items'] if rhs['type'] == '|' else (rhs,):
365         for term in a['items'] if a['type'] == 'sequence' else (a,):
366             if term['type'] == 'empty':
367                 pass
368             elif term['type'] == 'nonterminal':
369                 nt_name = name_to_id(term['nonterminal_name'])
370                 member_name = name_to_id(term['member_name'])
371                 print '%sstruct %s%s *%s; /* Always nonnull. */' % (
372                     indent, prefix, nt_name, member_name)
373             elif term['type'] == '?' and term['item']['type'] == 'nonterminal':
374                 nt_name = name_to_id(term['item']['nonterminal_name'])
375                 member_name = name_to_id(term['item']['member_name'])
376                 print '%sstruct %s%s *%s; /* Possibly null. */' % (
377                     indent, prefix, nt_name, member_name)
378             elif (term['type'] in ('*', '+')
379                   and term['item']['type'] == 'nonterminal'):
380                 nt_name = name_to_id(term['item']['nonterminal_name'])
381                 member_name = name_to_id(term['item']['member_name'])
382                 print '%sstruct %s%s **%s;' % (indent, prefix,
383                                                nt_name, member_name)
384                 print '%ssize_t n_%s;' % (indent, member_name)
385             else:
386                 seq_name = term['seq_name']
387                 print '%sstruct spvxml_node **%s;' % (indent, seq_name)
388                 print '%ssize_t n_%s;' % (indent, seq_name)
389
390
391 def bytes_to_hex(s):
392     return ''.join(['"'] + ["\\x%02x" % ord(x) for x in s] + ['"'])
393
394
395 class Parser_Context(object):
396     def __init__(self, function_name, productions):
397         self.suffixes = {}
398         self.bail = 'error'
399         self.need_error_handler = False
400         self.parsers = {}
401         self.parser_index = 0
402         self.productions = productions
403
404         self.function_name = function_name
405         self.functions = []
406     def gen_name(self, prefix):
407         n = self.suffixes.get(prefix, 0) + 1
408         self.suffixes[prefix] = n
409         return '%s%d' % (prefix, n) if n > 1 else prefix
410     def new_function(self, type_name):
411         f = Function('%s_%d' % (self.function_name, len(self.functions) + 1),
412                      type_name)
413         self.functions += [f]
414         return f
415
416
417 def print_attribute_decls(name, attributes):
418     if attributes:
419         print('    enum {')
420         for unique_name, (xml_name, value, required) in sorted(attributes.items()):
421             c_name = name_to_id(unique_name)
422             print('        ATTR_%s,' % c_name.upper())
423         print('    };')
424     print('    struct spvxml_attribute attrs[] = {')
425     for unique_name, (xml_name, value, required) in sorted(attributes.items()):
426         c_name = name_to_id(unique_name)
427         print('        [ATTR_%s] = { "%s", %s, NULL },'
428               % (c_name.upper(), xml_name, 'true' if required else 'false'))
429     print('    };')
430     print('    enum { N_ATTRS = sizeof attrs / sizeof *attrs };')
431
432
433 def print_parser_for_attributes(name, attributes):
434     print('    /* Parse attributes. */')
435     print('    spvxml_parse_attributes (&nctx);')
436
437     if not attributes:
438         return
439
440     for unique_name, (xml_name, value, required) in sorted(attributes.items()):
441         c_name = name_to_id(unique_name)
442         params = '&nctx, &attrs[ATTR_%s]' % c_name.upper()
443         if type(value) is set:
444             if len(value) <= 1:
445                 if required:
446                     print('    spvxml_attr_parse_fixed (%s, "%s");'
447                           % (params, tuple(value)[0]))
448                 else:
449                     print('    p->%s_present = spvxml_attr_parse_fixed (\n'
450                           '        %s, "%s");'
451                           % (c_name, params, tuple(value)[0]))
452             elif value == set(('true', 'false')):
453                 print('    p->%s = spvxml_attr_parse_bool (%s);'
454                       % (c_name, params))
455             else:
456                 map_name = '%s%s_map' % (prefix, c_name)
457                 print('    p->%s = spvxml_attr_parse_enum (\n'
458                       '        %s, %s);'
459                       % (c_name, params, map_name))
460         elif value in ('real', 'dimension', 'int', 'color'):
461             print('    p->%s = spvxml_attr_parse_%s (%s);'
462                   % (c_name, value, params))
463         elif value == 'string':
464             print('    p->%s = attrs[ATTR_%s].value;\n'
465                   '    attrs[ATTR_%s].value = NULL;'
466                   % (c_name, c_name.upper(),
467                      c_name.upper()))
468         elif value == 'id':
469             print('    p->node_.id = attrs[ATTR_%s].value;\n'
470                   '    attrs[ATTR_%s].value = NULL;'
471                   % (c_name.upper(), c_name.upper()))
472         elif value[0] == 'ref':
473             pass
474         else:
475             assert False
476     print('''\
477     if (ctx->error) {
478         spvxml_node_context_uninit (&nctx);
479         ctx->hard_error = true;
480         %sfree_%s (p);
481         return false;
482     }'''
483           % (prefix, name_to_id(name)))
484
485 class Function(object):
486     def __init__(self, function_name, type_name):
487         self.function_name = function_name
488         self.type_name = type_name
489         self.suffixes = {}
490         self.code = []
491     def gen_name(self, prefix):
492         n = self.suffixes.get(prefix, 0) + 1
493         self.suffixes[prefix] = n
494         return '%s%d' % (prefix, n) if n > 1 else prefix
495     def print_(self):
496         print('''
497 static bool
498 %s (struct spvxml_node_context *nctx, xmlNode **input, struct %s *p)
499 {'''
500               % (self.function_name, self.type_name))
501         while self.code and self.code[0] == '':
502             self.code = self.code[1:]
503         for line in self.code:
504             print('    %s' % line if line else '')
505         print('    return true;')
506         print('}')
507
508 STATE_START = 0
509 STATE_ALTERNATION = 1
510 STATE_SEQUENCE = 2
511 STATE_REPETITION = 3
512 STATE_OPTIONAL = 4
513 STATE_GENERAL = 5
514
515 def generate_content_parser(nonterminal, rhs, function, ctx, state, seq_name):
516     seq_name = seq_name if seq_name else rhs.get('seq_name')
517     ctx.parser_index += 1
518
519     if rhs['type'] == 'etc':
520         function.code += ['spvxml_content_parse_etc (input);']
521     elif rhs['type'] == 'text':
522         function.code += ['if (!spvxml_content_parse_text (nctx, input, &p->text))',
523                           '    return false;']
524     elif rhs['type'] == '|':
525         for i in range(len(rhs['items'])):
526             choice = rhs['items'][i]
527             subfunc = ctx.new_function(function.type_name)
528             generate_content_parser(nonterminal, choice, subfunc, ctx,
529                                     STATE_ALTERNATION
530                                     if state == STATE_START
531                                     else STATE_GENERAL, seq_name)
532             function.code += ['%(start)s!%(tryfunc)s (nctx, input, p, %(subfunc)s)%(end)s'
533                                % {'start': 'if (' if i == 0 else '    && ',
534                                   'subfunc': subfunc.function_name,
535                                   'tryfunc': '%stry_parse_%s'
536                                   % (prefix, name_to_id(nonterminal)),
537                                   'end': ')' if i == len(rhs['items']) - 1 else ''}]
538         function.code += ['  {',
539                           '    spvxml_content_error (nctx, *input, "Syntax error.");',
540                           '    return false;',
541                           '  }']
542     elif rhs['type'] == 'sequence':
543         for element in rhs['items']:
544             generate_content_parser(nonterminal, element, function, ctx,
545                                     STATE_SEQUENCE
546                                     if state in (STATE_START,
547                                                  STATE_ALTERNATION)
548                                     else STATE_GENERAL, seq_name)
549     elif rhs['type'] == 'empty':
550         function.code += ['(void) nctx;']
551         function.code += ['(void) input;']
552         function.code += ['(void) p;']
553     elif rhs['type'] in ('*', '+', '?'):
554         subfunc = ctx.new_function(function.type_name)
555         generate_content_parser(nonterminal, rhs['item'], subfunc, ctx,
556                                 (STATE_OPTIONAL
557                                  if rhs['type'] == '?'
558                                  else STATE_REPETITION)
559                                 if state in (STATE_START,
560                                              STATE_ALTERNATION,
561                                              STATE_SEQUENCE)
562                                 else STATE_GENERAL, seq_name)
563         next_name = function.gen_name('next')
564         args = {'subfunc': subfunc.function_name,
565                 'tryfunc': '%stry_parse_%s' % (prefix,
566                                                name_to_id (nonterminal))}
567         if rhs['type'] == '?':
568             function.code += [
569                 '%(tryfunc)s (nctx, input, p, %(subfunc)s);' % args]
570         else:
571             if rhs['type'] == '+':
572                 function.code += ['if (!%(subfunc)s (nctx, input, p))' % args,
573                                   '    return false;']
574             function.code += [
575                 'while (%(tryfunc)s (nctx, input, p, %(subfunc)s))' % args,
576                 '    continue;']
577     elif rhs['type'] == 'nonterminal':
578         node_name = function.gen_name('node')
579         function.code += [
580             '',
581             'xmlNode *%s;' % node_name,
582             'if (!spvxml_content_parse_element (nctx, input, "%s", &%s))'
583             % (ctx.productions[rhs['nonterminal_name']][0], node_name),
584             '    return false;']
585         if state in (STATE_START,
586                      STATE_ALTERNATION,
587                      STATE_SEQUENCE,
588                      STATE_OPTIONAL):
589             target = '&p->%s' % name_to_id(rhs['member_name'])
590         else:
591             assert state in (STATE_REPETITION, STATE_GENERAL)
592             member = name_to_id(rhs['member_name']) if state == STATE_REPETITION else seq_name
593             function.code += ['struct %s%s *%s;' % (
594                 prefix, name_to_id(rhs['nonterminal_name']), member)]
595             target = '&%s' % member
596         function.code += [
597             'if (!%sparse_%s (nctx->up, %s, %s))'
598             % (prefix, name_to_id(rhs['nonterminal_name']), node_name, target),
599             '    return false;']
600         if state in (STATE_REPETITION, STATE_GENERAL):
601             function.code += [
602                 'p->%s = xrealloc (p->%s, sizeof *p->%s * (p->n_%s + 1));'
603                 % (member, member, member, member),
604                 'p->%s[p->n_%s++] = %s;' % (member, member, 
605                                             '&%s->node_' % member
606                                             if state == STATE_GENERAL
607                                             else member)]
608     else:
609         assert False
610
611 def print_parser(name, production, productions, indent):
612     xml_name, attributes, rhs = production
613
614     print('''
615 static bool UNUSED
616 %(prefix)stry_parse_%(name)s (
617     struct spvxml_node_context *nctx, xmlNode **input,
618     struct %(prefix)s%(name)s *p,
619     bool (*sub) (struct spvxml_node_context *,
620                  xmlNode **,
621                  struct %(prefix)s%(name)s *))
622 {
623     xmlNode *next = *input;
624     bool ok = sub (nctx, &next, p);
625     if (ok)
626         *input = next;
627     else if (!nctx->up->hard_error) {
628         free (nctx->up->error);
629         nctx->up->error = NULL;
630     }
631     return ok;
632 }'''
633           % {'prefix': prefix,
634              'name': name_to_id(name)})
635
636     ctx = Parser_Context('%sparse_%s' % (prefix, name_to_id(name)),
637                          productions)
638     if rhs['type'] not in ('empty', 'etc'):
639         function = ctx.new_function('%s%s' % (prefix, name_to_id(name)))
640         generate_content_parser(name, rhs, function, ctx, 0, None)
641         for f in reversed(ctx.functions):
642             f.print_()
643
644     print('''
645 bool
646 %(prefix)sparse_%(name)s (
647     struct spvxml_context *ctx, xmlNode *input,
648     struct %(prefix)s%(name)s **p_)
649 {'''
650           % {'prefix': prefix,
651              'name': name_to_id(name)})
652
653     print_attribute_decls(name, attributes)
654
655     print('    struct spvxml_node_context nctx = {')
656     print('        .up = ctx,')
657     print('        .parent = input,')
658     print('        .attrs = attrs,')
659     print('        .n_attrs = N_ATTRS,')
660     print('    };')
661     print('')
662     print('    *p_ = NULL;')
663     print('    struct %(prefix)s%(name)s *p = xzalloc (sizeof *p);'
664           % {'prefix': prefix,
665              'name': name_to_id(name)})
666     print('    p->node_.raw = input;')
667     print('    p->node_.class_ = &%(prefix)s%(name)s_class;'
668           % {'prefix': prefix,
669              'name': name_to_id(name)})
670     print('')
671
672     print_parser_for_attributes(name, attributes)
673
674     if rhs['type'] == 'empty':
675         print('''
676     /* Parse content. */
677     if (!spvxml_content_parse_end (&nctx, input->children)) {
678         ctx->hard_error = true;
679         spvxml_node_context_uninit (&nctx);
680         %sfree_%s (p);
681         return false;
682     }'''
683               % (prefix, name_to_id(name)))
684     elif rhs['type'] == 'etc':
685         print('''
686     /* Ignore content. */
687 ''')
688     else:
689         print('''
690     /* Parse content. */
691     input = input->children;
692     if (!%s (&nctx, &input, p)
693         || !spvxml_content_parse_end (&nctx, input)) {
694         ctx->hard_error = true;
695         spvxml_node_context_uninit (&nctx);
696         %sfree_%s (p);
697         return false;
698     }'''
699               % (function.function_name,
700                  prefix, name_to_id(name)))
701
702     print('''
703     spvxml_node_context_uninit (&nctx);
704     *p_ = p;
705     return true;''')
706
707     print "}"
708
709
710 def print_free_members(attributes, rhs, indent):
711     for unique_name, (xml_name, value, required) in attributes.items():
712         c_name = name_to_id(unique_name)
713         if (type(value) is set
714             or value in ('dimension', 'real', 'int', 'color', 'id')
715             or value[0] == 'ref'):
716             pass
717         elif value == 'string':
718             print('    free (p->%s);' % c_name);
719         else:
720             assert False
721
722     if rhs['type'] in ('etc', 'empty'):
723         pass
724     elif rhs['type'] == 'text':
725         print('    free (p->text);')
726     else:
727         n = 0
728         for a in rhs['items'] if rhs['type'] == '|' else (rhs,):
729             for term in a['items'] if a['type'] == 'sequence' else (a,):
730                 if term['type'] == 'empty':
731                     pass
732                 elif (term['type'] == 'nonterminal'
733                       or (term['type'] == '?'
734                           and term['item']['type'] == 'nonterminal')):
735                     if term['type'] == '?':
736                         term = term['item']
737                     nt_name = name_to_id(term['nonterminal_name'])
738                     member_name = name_to_id(term['member_name'])
739                     print('    %sfree_%s (p->%s);' % (prefix, nt_name,
740                                                       member_name))
741                 elif (term['type'] in ('*', '+')
742                       and term['item']['type'] == 'nonterminal'):
743                     nt_name = name_to_id(term['item']['nonterminal_name'])
744                     member_name = name_to_id(term['item']['member_name'])
745                     print('''\
746     for (size_t i = 0; i < p->n_%s; i++)
747         %sfree_%s (p->%s[i]);
748     free (p->%s);'''
749                           % (member_name,
750                              prefix, nt_name, member_name,
751                              member_name))
752                 else:
753                     n += 1
754                     seq_name = 'seq' if n == 1 else 'seq%d' % n
755                     print('''\
756     for (size_t i = 0; i < p->n_%s; i++)
757         p->%s[i]->class_->spvxml_node_free (p->%s[i]);
758     free (p->%s);'''
759                           % (seq_name,
760                              seq_name, seq_name,
761                              seq_name))
762     print('    free (p->node_.id);')
763     print('    free (p);')
764
765
766 def print_free(name, production, indent):
767     xml_name, attributes, rhs = production
768
769     print '''
770 void
771 %(prefix)sfree_%(name)s (struct %(prefix)s%(name)s *p)
772 {
773     if (!p)
774         return;
775 ''' % {'prefix': prefix,
776        'name': name_to_id(name)}
777
778     print_free_members(attributes, rhs, ' ' * 4)
779
780     print('}')
781
782 def name_to_id(s):
783     return s[0].lower() + ''.join(['_%c' % x.lower() if x.isupper() else x
784                                    for x in s[1:]]).replace('-', '_')
785
786
787 def print_recurse_members(attributes, rhs, function):
788     if rhs['type'] == 'etc' or rhs['type'] == 'empty':
789         pass
790     elif rhs['type'] == 'text':
791         pass
792     else:
793         n = 0
794         for a in rhs['items'] if rhs['type'] == '|' else (rhs,):
795             for term in a['items'] if a['type'] == 'sequence' else (a,):
796                 if term['type'] == 'empty':
797                     pass
798                 elif (term['type'] == 'nonterminal'
799                       or (term['type'] == '?'
800                           and term['item']['type'] == 'nonterminal')):
801                     if term['type'] == '?':
802                         term = term['item']
803                     nt_name = name_to_id(term['nonterminal_name'])
804                     member_name = name_to_id(term['member_name'])
805                     print('    %s%s_%s (ctx, p->%s);'
806                           % (prefix, function, nt_name, member_name))
807                 elif (term['type'] in ('*', '+')
808                       and term['item']['type'] == 'nonterminal'):
809                     nt_name = name_to_id(term['item']['nonterminal_name'])
810                     member_name = name_to_id(term['item']['member_name'])
811                     print('''\
812     for (size_t i = 0; i < p->n_%s; i++)
813         %s%s_%s (ctx, p->%s[i]);'''
814                           % (member_name,
815                              prefix, function, nt_name, member_name))
816                 else:
817                     n += 1
818                     seq_name = 'seq' if n == 1 else 'seq%d' % n
819                     print('''\
820     for (size_t i = 0; i < p->n_%s; i++)
821         p->%s[i]->class_->spvxml_node_%s (ctx, p->%s[i]);'''
822                           % (seq_name,
823                              seq_name, function, seq_name))
824
825
826 def print_collect_ids(name, production):
827     xml_name, attributes, rhs = production
828
829     print '''
830 void
831 %(prefix)scollect_ids_%(name)s (struct spvxml_context *ctx, struct %(prefix)s%(name)s *p)
832 {
833     if (!p)
834         return;
835
836     spvxml_node_collect_id (ctx, &p->node_);
837 ''' % {'prefix': prefix,
838        'name': name_to_id(name)}
839
840     print_recurse_members(attributes, rhs, 'collect_ids')
841
842     print('}')
843
844
845 def print_resolve_refs(name, production):
846     xml_name, attributes, rhs = production
847
848     print '''
849 bool
850 %(prefix)sis_%(name)s (const struct spvxml_node *node)
851 {
852     return node->class_ == &%(prefix)s%(name)s_class;
853 }
854
855 struct %(prefix)s%(name)s *
856 %(prefix)scast_%(name)s (const struct spvxml_node *node)
857 {
858     return (node && %(prefix)sis_%(name)s (node)
859             ? UP_CAST (node, struct %(prefix)s%(name)s, node_)
860             : NULL);
861 }
862
863 void
864 %(prefix)sresolve_refs_%(name)s (struct spvxml_context *ctx UNUSED, struct %(prefix)s%(name)s *p UNUSED)
865 {
866     if (!p)
867         return;
868 ''' % {'prefix': prefix,
869        'name': name_to_id(name)}
870
871     i = 0
872     for unique_name, (xml_name, value, required) in sorted(attributes.items()):
873         c_name = name_to_id(unique_name)
874         if type(value) is set or value[0] != 'ref':
875             continue
876
877         if value[1] is None:
878             print('    p->%s = spvxml_node_resolve_ref (ctx, p->node_.raw, \"%s\", NULL, 0);'
879                   % (c_name, xml_name))
880         else:
881             i += 1
882             name = 'classes'
883             if i > 1:
884                 name += '%d' % i
885             if type(value[1]) is set:
886                 print('    static const struct spvxml_node_class *const %s[] = {' % name)
887                 for ref_type in value[1]:
888                     print('        &%(prefix)s%(ref_type)s_class,'
889                          % {'prefix': prefix,
890                             'ref_type': name_to_id(ref_type)})
891                 print('    };');
892                 print('    const size_t n_%s = sizeof %s / sizeof *%s;'
893                       % (name, name, name))
894                 print('    p->%(member)s = spvxml_node_resolve_ref (ctx, p->node_.raw, \"%(attr)s\", %(name)s, n_%(name)s);'
895                       % {"member": c_name,
896                          "attr": xml_name,
897                          'prefix': prefix,
898                          'name': name
899                          })
900             else:
901                 print('    static const struct spvxml_node_class *const %s' % name)
902                 print('        = &%(prefix)s%(ref_type)s_class;'
903                       % {'prefix': prefix,
904                          'ref_type': name_to_id(value[1])})
905                 print('    p->%(member)s = %(prefix)scast_%(ref_type)s (spvxml_node_resolve_ref (ctx, p->node_.raw, \"%(attr)s\", &%(name)s, 1));'
906                       % {"member": c_name,
907                          "attr": xml_name,
908                          'prefix': prefix,
909                          'name': name,
910                          'ref_type': name_to_id(value[1])})
911                 
912
913     print_recurse_members(attributes, rhs, 'resolve_refs')
914
915     print('}')
916
917
918 def name_to_id(s):
919     return s[0].lower() + ''.join(['_%c' % x.lower() if x.isupper() else x
920                                    for x in s[1:]]).replace('-', '_')
921
922
923 if __name__ == "__main__":
924     argv0 = sys.argv[0]
925     try:
926         options, args = getopt.gnu_getopt(sys.argv[1:], 'h', ['help'])
927     except getopt.GetoptError as e:
928         sys.stderr.write("%s: %s\n" % (argv0, e.msg))
929         sys.exit(1)
930
931     for key, value in options:
932         if key in ['-h', '--help']:
933             usage()
934         else:
935             sys.exit(0)
936
937     if len(args) < 3:
938         sys.stderr.write("%s: bad usage (use --help for help)\n" % argv0)
939         sys.exit(1)
940
941     global file_name
942     global prefix
943     file_name, output_type, prefix = args[:3]
944     input_file = open(file_name)
945
946     prefix = '%s_' % prefix
947
948     global line
949     global line_number
950     line = ""
951     line_number = 0
952
953     productions = {}
954
955     global token
956     token = ('start', )
957     get_token()
958     while True:
959         while match(';'):
960             pass
961         if token[0] == 'eof':
962             break
963
964         name, xml_name, attributes, rhs = parse_production()
965         if name in productions:
966             fatal("%s: duplicate production" % name)
967         productions[name] = (xml_name, attributes, rhs)
968
969     print '/* Generated automatically -- do not modify!    -*- buffer-read-only: t -*- */'
970     if output_type == 'code' and len(args) == 4:
971         header_name = args[3]
972
973         print """\
974 #include <config.h>
975 #include %s
976 #include <limits.h>
977 #include <stdio.h>
978 #include <stdlib.h>
979 #include "libpspp/cast.h"
980 #include "libpspp/str.h"
981 #include "gl/xalloc.h"
982
983 """ % header_name
984         for enum_name, values in sorted(enums.items()):
985             if len(values) <= 1:
986                 continue
987
988             c_name = name_to_id(enum_name)
989             print('\nstatic const struct spvxml_enum %s%s_map[] = {'
990                   % (prefix, c_name))
991             for value in sorted(values):
992                 print('    { "%s", %s%s_%s },' % (value, prefix.upper(),
993                                                  c_name.upper(),
994                                                  name_to_id(value).upper()))
995             print('    { NULL, 0 },')
996             print('};')
997             print('\nconst char *')
998             print('%s%s_to_string (enum %s%s %s)'
999                   % (prefix, c_name, prefix, c_name, c_name))
1000             print('{')
1001             print('    switch (%s) {' % c_name)
1002             for value in sorted(values):
1003                 print('    case %s%s_%s: return "%s";'
1004                        % (prefix.upper(), c_name.upper(),
1005                           name_to_id(value).upper(), value))
1006             print('    default: return NULL;')
1007             print('    }')
1008             print('}')
1009
1010         for name, (xml_name, attributes, rhs) in sorted(productions.items()):
1011             print('static void %(prefix)scollect_ids_%(name)s (struct spvxml_context *, struct %(prefix)s%(name)s *);\n'
1012                   'static void %(prefix)sresolve_refs_%(name)s (struct spvxml_context *ctx UNUSED, struct %(prefix)s%(name)s *p UNUSED);\n'
1013                   % {'prefix': prefix,
1014                      'name': name_to_id(name)})
1015         for name, production in sorted(productions.items()):
1016             print_parser(name, production, productions, ' ' * 4)
1017             print_free(name, production, ' ' * 4)
1018             print_collect_ids(name, production)
1019             print_resolve_refs(name, production)
1020             print('''
1021 static void
1022 %(prefix)sdo_free_%(name)s (struct spvxml_node *node)
1023 {
1024     %(prefix)sfree_%(name)s (UP_CAST (node, struct %(prefix)s%(name)s, node_));
1025 }
1026
1027 static void
1028 %(prefix)sdo_collect_ids_%(name)s (struct spvxml_context *ctx, struct spvxml_node *node)
1029 {
1030     %(prefix)scollect_ids_%(name)s (ctx, UP_CAST (node, struct %(prefix)s%(name)s, node_));
1031 }
1032
1033 static void
1034 %(prefix)sdo_resolve_refs_%(name)s (struct spvxml_context *ctx, struct spvxml_node *node)
1035 {
1036     %(prefix)sresolve_refs_%(name)s (ctx, UP_CAST (node, struct %(prefix)s%(name)s, node_));
1037 }
1038
1039 struct spvxml_node_class %(prefix)s%(name)s_class = {
1040     "%(class)s",
1041     %(prefix)sdo_free_%(name)s,
1042     %(prefix)sdo_collect_ids_%(name)s,
1043     %(prefix)sdo_resolve_refs_%(name)s,
1044 };
1045 '''
1046             % {'prefix': prefix,
1047                'name': name_to_id(name),
1048                'class': (name if name == production[0]
1049                          else '%s (%s)' % (name, production[0]))})
1050     elif output_type == 'header' and len(args) == 3:
1051         print """\
1052 #ifndef %(PREFIX)sPARSER_H
1053 #define %(PREFIX)sPARSER_H
1054
1055 #include <stddef.h>
1056 #include <stdint.h>
1057 #include <stdbool.h>
1058 #include "output/spv/spvxml-helpers.h"\
1059 """ % {'PREFIX': prefix.upper()}
1060         for name, (xml_name, attributes, rhs) in sorted(productions.items()):
1061             print_members(attributes, rhs, ' ' * 4)
1062             print('''};
1063
1064 extern struct spvxml_node_class %(prefix)s%(name)s_class;
1065
1066 bool %(prefix)sparse_%(name)s (struct spvxml_context *, xmlNode *input, struct %(prefix)s%(name)s **);
1067 void %(prefix)sfree_%(name)s (struct %(prefix)s%(name)s *);
1068 bool %(prefix)sis_%(name)s (const struct spvxml_node *);
1069 struct %(prefix)s%(name)s *%(prefix)scast_%(name)s (const struct spvxml_node *);'''
1070                   % {'prefix': prefix,
1071                      'name': name_to_id(name)})
1072         print """\
1073
1074 #endif /* %(PREFIX)sPARSER_H */""" % {'PREFIX': prefix.upper()}
1075     else:
1076         sys.stderr.write("%s: bad usage (use --help for help)" % argv0)