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