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