xref: /openbmc/qemu/scripts/qapi/common.py (revision 0b8f7448)
1#
2# QAPI helper library
3#
4# Copyright IBM, Corp. 2011
5# Copyright (c) 2013-2018 Red Hat Inc.
6#
7# Authors:
8#  Anthony Liguori <aliguori@us.ibm.com>
9#  Markus Armbruster <armbru@redhat.com>
10#
11# This work is licensed under the terms of the GNU GPL, version 2.
12# See the COPYING file in the top-level directory.
13
14from __future__ import print_function
15from contextlib import contextmanager
16import errno
17import os
18import re
19import string
20import sys
21from collections import OrderedDict
22
23builtin_types = {
24    'null':     'QTYPE_QNULL',
25    'str':      'QTYPE_QSTRING',
26    'int':      'QTYPE_QNUM',
27    'number':   'QTYPE_QNUM',
28    'bool':     'QTYPE_QBOOL',
29    'int8':     'QTYPE_QNUM',
30    'int16':    'QTYPE_QNUM',
31    'int32':    'QTYPE_QNUM',
32    'int64':    'QTYPE_QNUM',
33    'uint8':    'QTYPE_QNUM',
34    'uint16':   'QTYPE_QNUM',
35    'uint32':   'QTYPE_QNUM',
36    'uint64':   'QTYPE_QNUM',
37    'size':     'QTYPE_QNUM',
38    'any':      None,           # any QType possible, actually
39    'QType':    'QTYPE_QSTRING',
40}
41
42# Are documentation comments required?
43doc_required = False
44
45# Whitelist of commands allowed to return a non-dictionary
46returns_whitelist = []
47
48# Whitelist of entities allowed to violate case conventions
49name_case_whitelist = []
50
51enum_types = {}
52struct_types = {}
53union_types = {}
54all_names = {}
55
56#
57# Parsing the schema into expressions
58#
59
60
61def error_path(parent):
62    res = ''
63    while parent:
64        res = ('In file included from %s:%d:\n' % (parent['file'],
65                                                   parent['line'])) + res
66        parent = parent['parent']
67    return res
68
69
70class QAPIError(Exception):
71    def __init__(self, fname, line, col, incl_info, msg):
72        Exception.__init__(self)
73        self.fname = fname
74        self.line = line
75        self.col = col
76        self.info = incl_info
77        self.msg = msg
78
79    def __str__(self):
80        loc = '%s:%d' % (self.fname, self.line)
81        if self.col is not None:
82            loc += ':%s' % self.col
83        return error_path(self.info) + '%s: %s' % (loc, self.msg)
84
85
86class QAPIParseError(QAPIError):
87    def __init__(self, parser, msg):
88        col = 1
89        for ch in parser.src[parser.line_pos:parser.pos]:
90            if ch == '\t':
91                col = (col + 7) % 8 + 1
92            else:
93                col += 1
94        QAPIError.__init__(self, parser.fname, parser.line, col,
95                           parser.incl_info, msg)
96
97
98class QAPISemError(QAPIError):
99    def __init__(self, info, msg):
100        QAPIError.__init__(self, info['file'], info['line'], None,
101                           info['parent'], msg)
102
103
104class QAPIDoc(object):
105    class Section(object):
106        def __init__(self, name=None):
107            # optional section name (argument/member or section name)
108            self.name = name
109            # the list of lines for this section
110            self.text = ''
111
112        def append(self, line):
113            self.text += line.rstrip() + '\n'
114
115    class ArgSection(Section):
116        def __init__(self, name):
117            QAPIDoc.Section.__init__(self, name)
118            self.member = None
119
120        def connect(self, member):
121            self.member = member
122
123    def __init__(self, parser, info):
124        # self._parser is used to report errors with QAPIParseError.  The
125        # resulting error position depends on the state of the parser.
126        # It happens to be the beginning of the comment.  More or less
127        # servicable, but action at a distance.
128        self._parser = parser
129        self.info = info
130        self.symbol = None
131        self.body = QAPIDoc.Section()
132        # dict mapping parameter name to ArgSection
133        self.args = OrderedDict()
134        # a list of Section
135        self.sections = []
136        # the current section
137        self._section = self.body
138
139    def has_section(self, name):
140        """Return True if we have a section with this name."""
141        for i in self.sections:
142            if i.name == name:
143                return True
144        return False
145
146    def append(self, line):
147        """Parse a comment line and add it to the documentation."""
148        line = line[1:]
149        if not line:
150            self._append_freeform(line)
151            return
152
153        if line[0] != ' ':
154            raise QAPIParseError(self._parser, "Missing space after #")
155        line = line[1:]
156
157        # FIXME not nice: things like '#  @foo:' and '# @foo: ' aren't
158        # recognized, and get silently treated as ordinary text
159        if self.symbol:
160            self._append_symbol_line(line)
161        elif not self.body.text and line.startswith('@'):
162            if not line.endswith(':'):
163                raise QAPIParseError(self._parser, "Line should end with :")
164            self.symbol = line[1:-1]
165            # FIXME invalid names other than the empty string aren't flagged
166            if not self.symbol:
167                raise QAPIParseError(self._parser, "Invalid name")
168        else:
169            self._append_freeform(line)
170
171    def end_comment(self):
172        self._end_section()
173
174    def _append_symbol_line(self, line):
175        name = line.split(' ', 1)[0]
176
177        if name.startswith('@') and name.endswith(':'):
178            line = line[len(name)+1:]
179            self._start_args_section(name[1:-1])
180        elif name in ('Returns:', 'Since:',
181                      # those are often singular or plural
182                      'Note:', 'Notes:',
183                      'Example:', 'Examples:',
184                      'TODO:'):
185            line = line[len(name)+1:]
186            self._start_section(name[:-1])
187
188        self._append_freeform(line)
189
190    def _start_args_section(self, name):
191        # FIXME invalid names other than the empty string aren't flagged
192        if not name:
193            raise QAPIParseError(self._parser, "Invalid parameter name")
194        if name in self.args:
195            raise QAPIParseError(self._parser,
196                                 "'%s' parameter name duplicated" % name)
197        if self.sections:
198            raise QAPIParseError(self._parser,
199                                 "'@%s:' can't follow '%s' section"
200                                 % (name, self.sections[0].name))
201        self._end_section()
202        self._section = QAPIDoc.ArgSection(name)
203        self.args[name] = self._section
204
205    def _start_section(self, name=None):
206        if name in ('Returns', 'Since') and self.has_section(name):
207            raise QAPIParseError(self._parser,
208                                 "Duplicated '%s' section" % name)
209        self._end_section()
210        self._section = QAPIDoc.Section(name)
211        self.sections.append(self._section)
212
213    def _end_section(self):
214        if self._section:
215            text = self._section.text = self._section.text.strip()
216            if self._section.name and (not text or text.isspace()):
217                raise QAPIParseError(self._parser, "Empty doc section '%s'"
218                                     % self._section.name)
219            self._section = None
220
221    def _append_freeform(self, line):
222        in_arg = isinstance(self._section, QAPIDoc.ArgSection)
223        if (in_arg and self._section.text.endswith('\n\n')
224                and line and not line[0].isspace()):
225            self._start_section()
226        if (in_arg or not self._section.name
227                or not self._section.name.startswith('Example')):
228            line = line.strip()
229        match = re.match(r'(@\S+:)', line)
230        if match:
231            raise QAPIParseError(self._parser,
232                                 "'%s' not allowed in free-form documentation"
233                                 % match.group(1))
234        self._section.append(line)
235
236    def connect_member(self, member):
237        if member.name not in self.args:
238            # Undocumented TODO outlaw
239            self.args[member.name] = QAPIDoc.ArgSection(member.name)
240        self.args[member.name].connect(member)
241
242    def check_expr(self, expr):
243        if self.has_section('Returns') and 'command' not in expr:
244            raise QAPISemError(self.info,
245                               "'Returns:' is only valid for commands")
246
247    def check(self):
248        bogus = [name for name, section in self.args.items()
249                 if not section.member]
250        if bogus:
251            raise QAPISemError(
252                self.info,
253                "The following documented members are not in "
254                "the declaration: %s" % ", ".join(bogus))
255
256
257class QAPISchemaParser(object):
258
259    def __init__(self, fp, previously_included=[], incl_info=None):
260        self.fname = fp.name
261        previously_included.append(os.path.abspath(fp.name))
262        self.incl_info = incl_info
263        self.src = fp.read()
264        if self.src == '' or self.src[-1] != '\n':
265            self.src += '\n'
266        self.cursor = 0
267        self.line = 1
268        self.line_pos = 0
269        self.exprs = []
270        self.docs = []
271        self.accept()
272        cur_doc = None
273
274        while self.tok is not None:
275            info = {'file': self.fname, 'line': self.line,
276                    'parent': self.incl_info}
277            if self.tok == '#':
278                self.reject_expr_doc(cur_doc)
279                cur_doc = self.get_doc(info)
280                self.docs.append(cur_doc)
281                continue
282
283            expr = self.get_expr(False)
284            if 'include' in expr:
285                self.reject_expr_doc(cur_doc)
286                if len(expr) != 1:
287                    raise QAPISemError(info, "Invalid 'include' directive")
288                include = expr['include']
289                if not isinstance(include, str):
290                    raise QAPISemError(info,
291                                       "Value of 'include' must be a string")
292                incl_fname = os.path.join(os.path.dirname(self.fname),
293                                          include)
294                self.exprs.append({'expr': {'include': incl_fname},
295                                   'info': info})
296                exprs_include = self._include(include, info, incl_fname,
297                                              previously_included)
298                if exprs_include:
299                    self.exprs.extend(exprs_include.exprs)
300                    self.docs.extend(exprs_include.docs)
301            elif "pragma" in expr:
302                self.reject_expr_doc(cur_doc)
303                if len(expr) != 1:
304                    raise QAPISemError(info, "Invalid 'pragma' directive")
305                pragma = expr['pragma']
306                if not isinstance(pragma, dict):
307                    raise QAPISemError(
308                        info, "Value of 'pragma' must be a dictionary")
309                for name, value in pragma.items():
310                    self._pragma(name, value, info)
311            else:
312                expr_elem = {'expr': expr,
313                             'info': info}
314                if cur_doc:
315                    if not cur_doc.symbol:
316                        raise QAPISemError(
317                            cur_doc.info, "Expression documentation required")
318                    expr_elem['doc'] = cur_doc
319                self.exprs.append(expr_elem)
320            cur_doc = None
321        self.reject_expr_doc(cur_doc)
322
323    @staticmethod
324    def reject_expr_doc(doc):
325        if doc and doc.symbol:
326            raise QAPISemError(
327                doc.info,
328                "Documentation for '%s' is not followed by the definition"
329                % doc.symbol)
330
331    def _include(self, include, info, incl_fname, previously_included):
332        incl_abs_fname = os.path.abspath(incl_fname)
333        # catch inclusion cycle
334        inf = info
335        while inf:
336            if incl_abs_fname == os.path.abspath(inf['file']):
337                raise QAPISemError(info, "Inclusion loop for %s" % include)
338            inf = inf['parent']
339
340        # skip multiple include of the same file
341        if incl_abs_fname in previously_included:
342            return None
343
344        try:
345            if sys.version_info[0] >= 3:
346                fobj = open(incl_fname, 'r', encoding='utf-8')
347            else:
348                fobj = open(incl_fname, 'r')
349        except IOError as e:
350            raise QAPISemError(info, '%s: %s' % (e.strerror, incl_fname))
351        return QAPISchemaParser(fobj, previously_included, info)
352
353    def _pragma(self, name, value, info):
354        global doc_required, returns_whitelist, name_case_whitelist
355        if name == 'doc-required':
356            if not isinstance(value, bool):
357                raise QAPISemError(info,
358                                   "Pragma 'doc-required' must be boolean")
359            doc_required = value
360        elif name == 'returns-whitelist':
361            if (not isinstance(value, list)
362                    or any([not isinstance(elt, str) for elt in value])):
363                raise QAPISemError(info,
364                                   "Pragma returns-whitelist must be"
365                                   " a list of strings")
366            returns_whitelist = value
367        elif name == 'name-case-whitelist':
368            if (not isinstance(value, list)
369                    or any([not isinstance(elt, str) for elt in value])):
370                raise QAPISemError(info,
371                                   "Pragma name-case-whitelist must be"
372                                   " a list of strings")
373            name_case_whitelist = value
374        else:
375            raise QAPISemError(info, "Unknown pragma '%s'" % name)
376
377    def accept(self, skip_comment=True):
378        while True:
379            self.tok = self.src[self.cursor]
380            self.pos = self.cursor
381            self.cursor += 1
382            self.val = None
383
384            if self.tok == '#':
385                if self.src[self.cursor] == '#':
386                    # Start of doc comment
387                    skip_comment = False
388                self.cursor = self.src.find('\n', self.cursor)
389                if not skip_comment:
390                    self.val = self.src[self.pos:self.cursor]
391                    return
392            elif self.tok in '{}:,[]':
393                return
394            elif self.tok == "'":
395                string = ''
396                esc = False
397                while True:
398                    ch = self.src[self.cursor]
399                    self.cursor += 1
400                    if ch == '\n':
401                        raise QAPIParseError(self, 'Missing terminating "\'"')
402                    if esc:
403                        if ch == 'b':
404                            string += '\b'
405                        elif ch == 'f':
406                            string += '\f'
407                        elif ch == 'n':
408                            string += '\n'
409                        elif ch == 'r':
410                            string += '\r'
411                        elif ch == 't':
412                            string += '\t'
413                        elif ch == 'u':
414                            value = 0
415                            for _ in range(0, 4):
416                                ch = self.src[self.cursor]
417                                self.cursor += 1
418                                if ch not in '0123456789abcdefABCDEF':
419                                    raise QAPIParseError(self,
420                                                         '\\u escape needs 4 '
421                                                         'hex digits')
422                                value = (value << 4) + int(ch, 16)
423                            # If Python 2 and 3 didn't disagree so much on
424                            # how to handle Unicode, then we could allow
425                            # Unicode string defaults.  But most of QAPI is
426                            # ASCII-only, so we aren't losing much for now.
427                            if not value or value > 0x7f:
428                                raise QAPIParseError(self,
429                                                     'For now, \\u escape '
430                                                     'only supports non-zero '
431                                                     'values up to \\u007f')
432                            string += chr(value)
433                        elif ch in '\\/\'"':
434                            string += ch
435                        else:
436                            raise QAPIParseError(self,
437                                                 "Unknown escape \\%s" % ch)
438                        esc = False
439                    elif ch == '\\':
440                        esc = True
441                    elif ch == "'":
442                        self.val = string
443                        return
444                    else:
445                        string += ch
446            elif self.src.startswith('true', self.pos):
447                self.val = True
448                self.cursor += 3
449                return
450            elif self.src.startswith('false', self.pos):
451                self.val = False
452                self.cursor += 4
453                return
454            elif self.src.startswith('null', self.pos):
455                self.val = None
456                self.cursor += 3
457                return
458            elif self.tok == '\n':
459                if self.cursor == len(self.src):
460                    self.tok = None
461                    return
462                self.line += 1
463                self.line_pos = self.cursor
464            elif not self.tok.isspace():
465                raise QAPIParseError(self, 'Stray "%s"' % self.tok)
466
467    def get_members(self):
468        expr = OrderedDict()
469        if self.tok == '}':
470            self.accept()
471            return expr
472        if self.tok != "'":
473            raise QAPIParseError(self, 'Expected string or "}"')
474        while True:
475            key = self.val
476            self.accept()
477            if self.tok != ':':
478                raise QAPIParseError(self, 'Expected ":"')
479            self.accept()
480            if key in expr:
481                raise QAPIParseError(self, 'Duplicate key "%s"' % key)
482            expr[key] = self.get_expr(True)
483            if self.tok == '}':
484                self.accept()
485                return expr
486            if self.tok != ',':
487                raise QAPIParseError(self, 'Expected "," or "}"')
488            self.accept()
489            if self.tok != "'":
490                raise QAPIParseError(self, 'Expected string')
491
492    def get_values(self):
493        expr = []
494        if self.tok == ']':
495            self.accept()
496            return expr
497        if self.tok not in "{['tfn":
498            raise QAPIParseError(self, 'Expected "{", "[", "]", string, '
499                                 'boolean or "null"')
500        while True:
501            expr.append(self.get_expr(True))
502            if self.tok == ']':
503                self.accept()
504                return expr
505            if self.tok != ',':
506                raise QAPIParseError(self, 'Expected "," or "]"')
507            self.accept()
508
509    def get_expr(self, nested):
510        if self.tok != '{' and not nested:
511            raise QAPIParseError(self, 'Expected "{"')
512        if self.tok == '{':
513            self.accept()
514            expr = self.get_members()
515        elif self.tok == '[':
516            self.accept()
517            expr = self.get_values()
518        elif self.tok in "'tfn":
519            expr = self.val
520            self.accept()
521        else:
522            raise QAPIParseError(self, 'Expected "{", "[", string, '
523                                 'boolean or "null"')
524        return expr
525
526    def get_doc(self, info):
527        if self.val != '##':
528            raise QAPIParseError(self, "Junk after '##' at start of "
529                                 "documentation comment")
530
531        doc = QAPIDoc(self, info)
532        self.accept(False)
533        while self.tok == '#':
534            if self.val.startswith('##'):
535                # End of doc comment
536                if self.val != '##':
537                    raise QAPIParseError(self, "Junk after '##' at end of "
538                                         "documentation comment")
539                doc.end_comment()
540                self.accept()
541                return doc
542            else:
543                doc.append(self.val)
544            self.accept(False)
545
546        raise QAPIParseError(self, "Documentation comment must end with '##'")
547
548
549#
550# Semantic analysis of schema expressions
551# TODO fold into QAPISchema
552# TODO catching name collisions in generated code would be nice
553#
554
555
556def find_base_members(base):
557    if isinstance(base, dict):
558        return base
559    base_struct_define = struct_types.get(base)
560    if not base_struct_define:
561        return None
562    return base_struct_define['data']
563
564
565# Return the qtype of an alternate branch, or None on error.
566def find_alternate_member_qtype(qapi_type):
567    if qapi_type in builtin_types:
568        return builtin_types[qapi_type]
569    elif qapi_type in struct_types:
570        return 'QTYPE_QDICT'
571    elif qapi_type in enum_types:
572        return 'QTYPE_QSTRING'
573    elif qapi_type in union_types:
574        return 'QTYPE_QDICT'
575    return None
576
577
578# Return the discriminator enum define if discriminator is specified as an
579# enum type, otherwise return None.
580def discriminator_find_enum_define(expr):
581    base = expr.get('base')
582    discriminator = expr.get('discriminator')
583
584    if not (discriminator and base):
585        return None
586
587    base_members = find_base_members(base)
588    if not base_members:
589        return None
590
591    discriminator_type = base_members.get(discriminator)
592    if not discriminator_type:
593        return None
594
595    return enum_types.get(discriminator_type)
596
597
598# Names must be letters, numbers, -, and _.  They must start with letter,
599# except for downstream extensions which must start with __RFQDN_.
600# Dots are only valid in the downstream extension prefix.
601valid_name = re.compile(r'^(__[a-zA-Z0-9.-]+_)?'
602                        '[a-zA-Z][a-zA-Z0-9_-]*$')
603
604
605def check_name(info, source, name, allow_optional=False,
606               enum_member=False):
607    global valid_name
608    membername = name
609
610    if not isinstance(name, str):
611        raise QAPISemError(info, "%s requires a string name" % source)
612    if name.startswith('*'):
613        membername = name[1:]
614        if not allow_optional:
615            raise QAPISemError(info, "%s does not allow optional name '%s'"
616                               % (source, name))
617    # Enum members can start with a digit, because the generated C
618    # code always prefixes it with the enum name
619    if enum_member and membername[0].isdigit():
620        membername = 'D' + membername
621    # Reserve the entire 'q_' namespace for c_name(), and for 'q_empty'
622    # and 'q_obj_*' implicit type names.
623    if not valid_name.match(membername) or \
624       c_name(membername, False).startswith('q_'):
625        raise QAPISemError(info, "%s uses invalid name '%s'" % (source, name))
626
627
628def add_name(name, info, meta, implicit=False):
629    global all_names
630    check_name(info, "'%s'" % meta, name)
631    # FIXME should reject names that differ only in '_' vs. '.'
632    # vs. '-', because they're liable to clash in generated C.
633    if name in all_names:
634        raise QAPISemError(info, "%s '%s' is already defined"
635                           % (all_names[name], name))
636    if not implicit and (name.endswith('Kind') or name.endswith('List')):
637        raise QAPISemError(info, "%s '%s' should not end in '%s'"
638                           % (meta, name, name[-4:]))
639    all_names[name] = meta
640
641
642def check_if(expr, info):
643
644    def check_if_str(ifcond, info):
645        if not isinstance(ifcond, str):
646            raise QAPISemError(
647                info, "'if' condition must be a string or a list of strings")
648        if ifcond == '':
649            raise QAPISemError(info, "'if' condition '' makes no sense")
650
651    ifcond = expr.get('if')
652    if ifcond is None:
653        return
654    if isinstance(ifcond, list):
655        if ifcond == []:
656            raise QAPISemError(info, "'if' condition [] is useless")
657        for elt in ifcond:
658            check_if_str(elt, info)
659    else:
660        check_if_str(ifcond, info)
661
662
663def check_type(info, source, value, allow_array=False,
664               allow_dict=False, allow_optional=False,
665               allow_metas=[]):
666    global all_names
667
668    if value is None:
669        return
670
671    # Check if array type for value is okay
672    if isinstance(value, list):
673        if not allow_array:
674            raise QAPISemError(info, "%s cannot be an array" % source)
675        if len(value) != 1 or not isinstance(value[0], str):
676            raise QAPISemError(info,
677                               "%s: array type must contain single type name" %
678                               source)
679        value = value[0]
680
681    # Check if type name for value is okay
682    if isinstance(value, str):
683        if value not in all_names:
684            raise QAPISemError(info, "%s uses unknown type '%s'"
685                               % (source, value))
686        if not all_names[value] in allow_metas:
687            raise QAPISemError(info, "%s cannot use %s type '%s'" %
688                               (source, all_names[value], value))
689        return
690
691    if not allow_dict:
692        raise QAPISemError(info, "%s should be a type name" % source)
693
694    if not isinstance(value, OrderedDict):
695        raise QAPISemError(info,
696                           "%s should be a dictionary or type name" % source)
697
698    # value is a dictionary, check that each member is okay
699    for (key, arg) in value.items():
700        check_name(info, "Member of %s" % source, key,
701                   allow_optional=allow_optional)
702        if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
703            raise QAPISemError(info, "Member of %s uses reserved name '%s'"
704                               % (source, key))
705        # Todo: allow dictionaries to represent default values of
706        # an optional argument.
707        check_type(info, "Member '%s' of %s" % (key, source), arg,
708                   allow_array=True,
709                   allow_metas=['built-in', 'union', 'alternate', 'struct',
710                                'enum'])
711
712
713def check_command(expr, info):
714    name = expr['command']
715    boxed = expr.get('boxed', False)
716
717    args_meta = ['struct']
718    if boxed:
719        args_meta += ['union', 'alternate']
720    check_type(info, "'data' for command '%s'" % name,
721               expr.get('data'), allow_dict=not boxed, allow_optional=True,
722               allow_metas=args_meta)
723    returns_meta = ['union', 'struct']
724    if name in returns_whitelist:
725        returns_meta += ['built-in', 'alternate', 'enum']
726    check_type(info, "'returns' for command '%s'" % name,
727               expr.get('returns'), allow_array=True,
728               allow_optional=True, allow_metas=returns_meta)
729
730
731def check_event(expr, info):
732    name = expr['event']
733    boxed = expr.get('boxed', False)
734
735    meta = ['struct']
736    if boxed:
737        meta += ['union', 'alternate']
738    check_type(info, "'data' for event '%s'" % name,
739               expr.get('data'), allow_dict=not boxed, allow_optional=True,
740               allow_metas=meta)
741
742
743def check_union(expr, info):
744    name = expr['union']
745    base = expr.get('base')
746    discriminator = expr.get('discriminator')
747    members = expr['data']
748
749    # Two types of unions, determined by discriminator.
750
751    # With no discriminator it is a simple union.
752    if discriminator is None:
753        enum_define = None
754        allow_metas = ['built-in', 'union', 'alternate', 'struct', 'enum']
755        if base is not None:
756            raise QAPISemError(info, "Simple union '%s' must not have a base" %
757                               name)
758
759    # Else, it's a flat union.
760    else:
761        # The object must have a string or dictionary 'base'.
762        check_type(info, "'base' for union '%s'" % name,
763                   base, allow_dict=True, allow_optional=True,
764                   allow_metas=['struct'])
765        if not base:
766            raise QAPISemError(info, "Flat union '%s' must have a base"
767                               % name)
768        base_members = find_base_members(base)
769        assert base_members is not None
770
771        # The value of member 'discriminator' must name a non-optional
772        # member of the base struct.
773        check_name(info, "Discriminator of flat union '%s'" % name,
774                   discriminator)
775        discriminator_type = base_members.get(discriminator)
776        if not discriminator_type:
777            raise QAPISemError(info,
778                               "Discriminator '%s' is not a member of base "
779                               "struct '%s'"
780                               % (discriminator, base))
781        enum_define = enum_types.get(discriminator_type)
782        allow_metas = ['struct']
783        # Do not allow string discriminator
784        if not enum_define:
785            raise QAPISemError(info,
786                               "Discriminator '%s' must be of enumeration "
787                               "type" % discriminator)
788
789    # Check every branch; don't allow an empty union
790    if len(members) == 0:
791        raise QAPISemError(info, "Union '%s' cannot have empty 'data'" % name)
792    for (key, value) in members.items():
793        check_name(info, "Member of union '%s'" % name, key)
794
795        # Each value must name a known type
796        check_type(info, "Member '%s' of union '%s'" % (key, name),
797                   value, allow_array=not base, allow_metas=allow_metas)
798
799        # If the discriminator names an enum type, then all members
800        # of 'data' must also be members of the enum type.
801        if enum_define:
802            if key not in enum_define['data']:
803                raise QAPISemError(info,
804                                   "Discriminator value '%s' is not found in "
805                                   "enum '%s'"
806                                   % (key, enum_define['enum']))
807
808
809def check_alternate(expr, info):
810    name = expr['alternate']
811    members = expr['data']
812    types_seen = {}
813
814    # Check every branch; require at least two branches
815    if len(members) < 2:
816        raise QAPISemError(info,
817                           "Alternate '%s' should have at least two branches "
818                           "in 'data'" % name)
819    for (key, value) in members.items():
820        check_name(info, "Member of alternate '%s'" % name, key)
821
822        # Ensure alternates have no type conflicts.
823        check_type(info, "Member '%s' of alternate '%s'" % (key, name),
824                   value,
825                   allow_metas=['built-in', 'union', 'struct', 'enum'])
826        qtype = find_alternate_member_qtype(value)
827        if not qtype:
828            raise QAPISemError(info, "Alternate '%s' member '%s' cannot use "
829                               "type '%s'" % (name, key, value))
830        conflicting = set([qtype])
831        if qtype == 'QTYPE_QSTRING':
832            enum_expr = enum_types.get(value)
833            if enum_expr:
834                for v in enum_expr['data']:
835                    if v in ['on', 'off']:
836                        conflicting.add('QTYPE_QBOOL')
837                    if re.match(r'[-+0-9.]', v): # lazy, could be tightened
838                        conflicting.add('QTYPE_QNUM')
839            else:
840                conflicting.add('QTYPE_QNUM')
841                conflicting.add('QTYPE_QBOOL')
842        for qt in conflicting:
843            if qt in types_seen:
844                raise QAPISemError(info, "Alternate '%s' member '%s' can't "
845                                   "be distinguished from member '%s'"
846                                   % (name, key, types_seen[qt]))
847            types_seen[qt] = key
848
849
850def check_enum(expr, info):
851    name = expr['enum']
852    members = expr.get('data')
853    prefix = expr.get('prefix')
854
855    if not isinstance(members, list):
856        raise QAPISemError(info,
857                           "Enum '%s' requires an array for 'data'" % name)
858    if prefix is not None and not isinstance(prefix, str):
859        raise QAPISemError(info,
860                           "Enum '%s' requires a string for 'prefix'" % name)
861    for member in members:
862        check_name(info, "Member of enum '%s'" % name, member,
863                   enum_member=True)
864
865
866def check_struct(expr, info):
867    name = expr['struct']
868    members = expr['data']
869
870    check_type(info, "'data' for struct '%s'" % name, members,
871               allow_dict=True, allow_optional=True)
872    check_type(info, "'base' for struct '%s'" % name, expr.get('base'),
873               allow_metas=['struct'])
874
875
876def check_keys(expr_elem, meta, required, optional=[]):
877    expr = expr_elem['expr']
878    info = expr_elem['info']
879    name = expr[meta]
880    if not isinstance(name, str):
881        raise QAPISemError(info, "'%s' key must have a string value" % meta)
882    required = required + [meta]
883    for (key, value) in expr.items():
884        if key not in required and key not in optional:
885            raise QAPISemError(info, "Unknown key '%s' in %s '%s'"
886                               % (key, meta, name))
887        if key in ['gen', 'success-response'] and value is not False:
888            raise QAPISemError(info,
889                               "'%s' of %s '%s' should only use false value"
890                               % (key, meta, name))
891        if (key in ['boxed', 'allow-oob', 'allow-preconfig']
892                and value is not True):
893            raise QAPISemError(info,
894                               "'%s' of %s '%s' should only use true value"
895                               % (key, meta, name))
896        if key == 'if':
897            check_if(expr, info)
898    for key in required:
899        if key not in expr:
900            raise QAPISemError(info, "Key '%s' is missing from %s '%s'"
901                               % (key, meta, name))
902
903
904def check_exprs(exprs):
905    global all_names
906
907    # Populate name table with names of built-in types
908    for builtin in builtin_types.keys():
909        all_names[builtin] = 'built-in'
910
911    # Learn the types and check for valid expression keys
912    for expr_elem in exprs:
913        expr = expr_elem['expr']
914        info = expr_elem['info']
915        doc = expr_elem.get('doc')
916
917        if 'include' in expr:
918            continue
919
920        if not doc and doc_required:
921            raise QAPISemError(info,
922                               "Expression missing documentation comment")
923
924        if 'enum' in expr:
925            meta = 'enum'
926            check_keys(expr_elem, 'enum', ['data'], ['if', 'prefix'])
927            enum_types[expr[meta]] = expr
928        elif 'union' in expr:
929            meta = 'union'
930            check_keys(expr_elem, 'union', ['data'],
931                       ['base', 'discriminator', 'if'])
932            union_types[expr[meta]] = expr
933        elif 'alternate' in expr:
934            meta = 'alternate'
935            check_keys(expr_elem, 'alternate', ['data'], ['if'])
936        elif 'struct' in expr:
937            meta = 'struct'
938            check_keys(expr_elem, 'struct', ['data'], ['base', 'if'])
939            struct_types[expr[meta]] = expr
940        elif 'command' in expr:
941            meta = 'command'
942            check_keys(expr_elem, 'command', [],
943                       ['data', 'returns', 'gen', 'success-response',
944                        'boxed', 'allow-oob', 'allow-preconfig', 'if'])
945        elif 'event' in expr:
946            meta = 'event'
947            check_keys(expr_elem, 'event', [], ['data', 'boxed', 'if'])
948        else:
949            raise QAPISemError(expr_elem['info'],
950                               "Expression is missing metatype")
951        name = expr[meta]
952        add_name(name, info, meta)
953        if doc and doc.symbol != name:
954            raise QAPISemError(info, "Definition of '%s' follows documentation"
955                               " for '%s'" % (name, doc.symbol))
956
957    # Try again for hidden UnionKind enum
958    for expr_elem in exprs:
959        expr = expr_elem['expr']
960
961        if 'include' in expr:
962            continue
963        if 'union' in expr and not discriminator_find_enum_define(expr):
964            name = '%sKind' % expr['union']
965        elif 'alternate' in expr:
966            name = '%sKind' % expr['alternate']
967        else:
968            continue
969        enum_types[name] = {'enum': name}
970        add_name(name, info, 'enum', implicit=True)
971
972    # Validate that exprs make sense
973    for expr_elem in exprs:
974        expr = expr_elem['expr']
975        info = expr_elem['info']
976        doc = expr_elem.get('doc')
977
978        if 'include' in expr:
979            continue
980        if 'enum' in expr:
981            check_enum(expr, info)
982        elif 'union' in expr:
983            check_union(expr, info)
984        elif 'alternate' in expr:
985            check_alternate(expr, info)
986        elif 'struct' in expr:
987            check_struct(expr, info)
988        elif 'command' in expr:
989            check_command(expr, info)
990        elif 'event' in expr:
991            check_event(expr, info)
992        else:
993            assert False, 'unexpected meta type'
994
995        if doc:
996            doc.check_expr(expr)
997
998    return exprs
999
1000
1001#
1002# Schema compiler frontend
1003#
1004
1005def listify_cond(ifcond):
1006    if not ifcond:
1007        return []
1008    if not isinstance(ifcond, list):
1009        return [ifcond]
1010    return ifcond
1011
1012
1013class QAPISchemaEntity(object):
1014    def __init__(self, name, info, doc, ifcond=None):
1015        assert name is None or isinstance(name, str)
1016        self.name = name
1017        self.module = None
1018        # For explicitly defined entities, info points to the (explicit)
1019        # definition.  For builtins (and their arrays), info is None.
1020        # For implicitly defined entities, info points to a place that
1021        # triggered the implicit definition (there may be more than one
1022        # such place).
1023        self.info = info
1024        self.doc = doc
1025        self._ifcond = ifcond  # self.ifcond is set only after .check()
1026
1027    def c_name(self):
1028        return c_name(self.name)
1029
1030    def check(self, schema):
1031        if isinstance(self._ifcond, QAPISchemaType):
1032            # inherit the condition from a type
1033            typ = self._ifcond
1034            typ.check(schema)
1035            self.ifcond = typ.ifcond
1036        else:
1037            self.ifcond = listify_cond(self._ifcond)
1038
1039    def is_implicit(self):
1040        return not self.info
1041
1042    def visit(self, visitor):
1043        pass
1044
1045
1046class QAPISchemaVisitor(object):
1047    def visit_begin(self, schema):
1048        pass
1049
1050    def visit_end(self):
1051        pass
1052
1053    def visit_module(self, fname):
1054        pass
1055
1056    def visit_needed(self, entity):
1057        # Default to visiting everything
1058        return True
1059
1060    def visit_include(self, fname, info):
1061        pass
1062
1063    def visit_builtin_type(self, name, info, json_type):
1064        pass
1065
1066    def visit_enum_type(self, name, info, ifcond, values, prefix):
1067        pass
1068
1069    def visit_array_type(self, name, info, ifcond, element_type):
1070        pass
1071
1072    def visit_object_type(self, name, info, ifcond, base, members, variants):
1073        pass
1074
1075    def visit_object_type_flat(self, name, info, ifcond, members, variants):
1076        pass
1077
1078    def visit_alternate_type(self, name, info, ifcond, variants):
1079        pass
1080
1081    def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
1082                      success_response, boxed, allow_oob, allow_preconfig):
1083        pass
1084
1085    def visit_event(self, name, info, ifcond, arg_type, boxed):
1086        pass
1087
1088
1089class QAPISchemaInclude(QAPISchemaEntity):
1090
1091    def __init__(self, fname, info):
1092        QAPISchemaEntity.__init__(self, None, info, None)
1093        self.fname = fname
1094
1095    def visit(self, visitor):
1096        visitor.visit_include(self.fname, self.info)
1097
1098
1099class QAPISchemaType(QAPISchemaEntity):
1100    # Return the C type for common use.
1101    # For the types we commonly box, this is a pointer type.
1102    def c_type(self):
1103        pass
1104
1105    # Return the C type to be used in a parameter list.
1106    def c_param_type(self):
1107        return self.c_type()
1108
1109    # Return the C type to be used where we suppress boxing.
1110    def c_unboxed_type(self):
1111        return self.c_type()
1112
1113    def json_type(self):
1114        pass
1115
1116    def alternate_qtype(self):
1117        json2qtype = {
1118            'null':    'QTYPE_QNULL',
1119            'string':  'QTYPE_QSTRING',
1120            'number':  'QTYPE_QNUM',
1121            'int':     'QTYPE_QNUM',
1122            'boolean': 'QTYPE_QBOOL',
1123            'object':  'QTYPE_QDICT'
1124        }
1125        return json2qtype.get(self.json_type())
1126
1127    def doc_type(self):
1128        if self.is_implicit():
1129            return None
1130        return self.name
1131
1132
1133class QAPISchemaBuiltinType(QAPISchemaType):
1134    def __init__(self, name, json_type, c_type):
1135        QAPISchemaType.__init__(self, name, None, None)
1136        assert not c_type or isinstance(c_type, str)
1137        assert json_type in ('string', 'number', 'int', 'boolean', 'null',
1138                             'value')
1139        self._json_type_name = json_type
1140        self._c_type_name = c_type
1141
1142    def c_name(self):
1143        return self.name
1144
1145    def c_type(self):
1146        return self._c_type_name
1147
1148    def c_param_type(self):
1149        if self.name == 'str':
1150            return 'const ' + self._c_type_name
1151        return self._c_type_name
1152
1153    def json_type(self):
1154        return self._json_type_name
1155
1156    def doc_type(self):
1157        return self.json_type()
1158
1159    def visit(self, visitor):
1160        visitor.visit_builtin_type(self.name, self.info, self.json_type())
1161
1162
1163class QAPISchemaEnumType(QAPISchemaType):
1164    def __init__(self, name, info, doc, ifcond, values, prefix):
1165        QAPISchemaType.__init__(self, name, info, doc, ifcond)
1166        for v in values:
1167            assert isinstance(v, QAPISchemaMember)
1168            v.set_owner(name)
1169        assert prefix is None or isinstance(prefix, str)
1170        self.values = values
1171        self.prefix = prefix
1172
1173    def check(self, schema):
1174        QAPISchemaType.check(self, schema)
1175        seen = {}
1176        for v in self.values:
1177            v.check_clash(self.info, seen)
1178            if self.doc:
1179                self.doc.connect_member(v)
1180
1181    def is_implicit(self):
1182        # See QAPISchema._make_implicit_enum_type() and ._def_predefineds()
1183        return self.name.endswith('Kind') or self.name == 'QType'
1184
1185    def c_type(self):
1186        return c_name(self.name)
1187
1188    def member_names(self):
1189        return [v.name for v in self.values]
1190
1191    def json_type(self):
1192        return 'string'
1193
1194    def visit(self, visitor):
1195        visitor.visit_enum_type(self.name, self.info, self.ifcond,
1196                                self.member_names(), self.prefix)
1197
1198
1199class QAPISchemaArrayType(QAPISchemaType):
1200    def __init__(self, name, info, element_type):
1201        QAPISchemaType.__init__(self, name, info, None, None)
1202        assert isinstance(element_type, str)
1203        self._element_type_name = element_type
1204        self.element_type = None
1205
1206    def check(self, schema):
1207        QAPISchemaType.check(self, schema)
1208        self.element_type = schema.lookup_type(self._element_type_name)
1209        assert self.element_type
1210        self.element_type.check(schema)
1211        self.ifcond = self.element_type.ifcond
1212
1213    def is_implicit(self):
1214        return True
1215
1216    def c_type(self):
1217        return c_name(self.name) + pointer_suffix
1218
1219    def json_type(self):
1220        return 'array'
1221
1222    def doc_type(self):
1223        elt_doc_type = self.element_type.doc_type()
1224        if not elt_doc_type:
1225            return None
1226        return 'array of ' + elt_doc_type
1227
1228    def visit(self, visitor):
1229        visitor.visit_array_type(self.name, self.info, self.ifcond,
1230                                 self.element_type)
1231
1232
1233class QAPISchemaObjectType(QAPISchemaType):
1234    def __init__(self, name, info, doc, ifcond,
1235                 base, local_members, variants):
1236        # struct has local_members, optional base, and no variants
1237        # flat union has base, variants, and no local_members
1238        # simple union has local_members, variants, and no base
1239        QAPISchemaType.__init__(self, name, info, doc, ifcond)
1240        assert base is None or isinstance(base, str)
1241        for m in local_members:
1242            assert isinstance(m, QAPISchemaObjectTypeMember)
1243            m.set_owner(name)
1244        if variants is not None:
1245            assert isinstance(variants, QAPISchemaObjectTypeVariants)
1246            variants.set_owner(name)
1247        self._base_name = base
1248        self.base = None
1249        self.local_members = local_members
1250        self.variants = variants
1251        self.members = None
1252
1253    def check(self, schema):
1254        QAPISchemaType.check(self, schema)
1255        if self.members is False:               # check for cycles
1256            raise QAPISemError(self.info,
1257                               "Object %s contains itself" % self.name)
1258        if self.members:
1259            return
1260        self.members = False                    # mark as being checked
1261        seen = OrderedDict()
1262        if self._base_name:
1263            self.base = schema.lookup_type(self._base_name)
1264            assert isinstance(self.base, QAPISchemaObjectType)
1265            self.base.check(schema)
1266            self.base.check_clash(self.info, seen)
1267        for m in self.local_members:
1268            m.check(schema)
1269            m.check_clash(self.info, seen)
1270            if self.doc:
1271                self.doc.connect_member(m)
1272        self.members = seen.values()
1273        if self.variants:
1274            self.variants.check(schema, seen)
1275            assert self.variants.tag_member in self.members
1276            self.variants.check_clash(self.info, seen)
1277        if self.doc:
1278            self.doc.check()
1279
1280    # Check that the members of this type do not cause duplicate JSON members,
1281    # and update seen to track the members seen so far. Report any errors
1282    # on behalf of info, which is not necessarily self.info
1283    def check_clash(self, info, seen):
1284        assert not self.variants       # not implemented
1285        for m in self.members:
1286            m.check_clash(info, seen)
1287
1288    def is_implicit(self):
1289        # See QAPISchema._make_implicit_object_type(), as well as
1290        # _def_predefineds()
1291        return self.name.startswith('q_')
1292
1293    def is_empty(self):
1294        assert self.members is not None
1295        return not self.members and not self.variants
1296
1297    def c_name(self):
1298        assert self.name != 'q_empty'
1299        return QAPISchemaType.c_name(self)
1300
1301    def c_type(self):
1302        assert not self.is_implicit()
1303        return c_name(self.name) + pointer_suffix
1304
1305    def c_unboxed_type(self):
1306        return c_name(self.name)
1307
1308    def json_type(self):
1309        return 'object'
1310
1311    def visit(self, visitor):
1312        visitor.visit_object_type(self.name, self.info, self.ifcond,
1313                                  self.base, self.local_members, self.variants)
1314        visitor.visit_object_type_flat(self.name, self.info, self.ifcond,
1315                                       self.members, self.variants)
1316
1317
1318class QAPISchemaMember(object):
1319    role = 'member'
1320
1321    def __init__(self, name):
1322        assert isinstance(name, str)
1323        self.name = name
1324        self.owner = None
1325
1326    def set_owner(self, name):
1327        assert not self.owner
1328        self.owner = name
1329
1330    def check_clash(self, info, seen):
1331        cname = c_name(self.name)
1332        if cname.lower() != cname and self.owner not in name_case_whitelist:
1333            raise QAPISemError(info,
1334                               "%s should not use uppercase" % self.describe())
1335        if cname in seen:
1336            raise QAPISemError(info, "%s collides with %s" %
1337                               (self.describe(), seen[cname].describe()))
1338        seen[cname] = self
1339
1340    def _pretty_owner(self):
1341        owner = self.owner
1342        if owner.startswith('q_obj_'):
1343            # See QAPISchema._make_implicit_object_type() - reverse the
1344            # mapping there to create a nice human-readable description
1345            owner = owner[6:]
1346            if owner.endswith('-arg'):
1347                return '(parameter of %s)' % owner[:-4]
1348            elif owner.endswith('-base'):
1349                return '(base of %s)' % owner[:-5]
1350            else:
1351                assert owner.endswith('-wrapper')
1352                # Unreachable and not implemented
1353                assert False
1354        if owner.endswith('Kind'):
1355            # See QAPISchema._make_implicit_enum_type()
1356            return '(branch of %s)' % owner[:-4]
1357        return '(%s of %s)' % (self.role, owner)
1358
1359    def describe(self):
1360        return "'%s' %s" % (self.name, self._pretty_owner())
1361
1362
1363class QAPISchemaObjectTypeMember(QAPISchemaMember):
1364    def __init__(self, name, typ, optional):
1365        QAPISchemaMember.__init__(self, name)
1366        assert isinstance(typ, str)
1367        assert isinstance(optional, bool)
1368        self._type_name = typ
1369        self.type = None
1370        self.optional = optional
1371
1372    def check(self, schema):
1373        assert self.owner
1374        self.type = schema.lookup_type(self._type_name)
1375        assert self.type
1376
1377
1378class QAPISchemaObjectTypeVariants(object):
1379    def __init__(self, tag_name, tag_member, variants):
1380        # Flat unions pass tag_name but not tag_member.
1381        # Simple unions and alternates pass tag_member but not tag_name.
1382        # After check(), tag_member is always set, and tag_name remains
1383        # a reliable witness of being used by a flat union.
1384        assert bool(tag_member) != bool(tag_name)
1385        assert (isinstance(tag_name, str) or
1386                isinstance(tag_member, QAPISchemaObjectTypeMember))
1387        assert len(variants) > 0
1388        for v in variants:
1389            assert isinstance(v, QAPISchemaObjectTypeVariant)
1390        self._tag_name = tag_name
1391        self.tag_member = tag_member
1392        self.variants = variants
1393
1394    def set_owner(self, name):
1395        for v in self.variants:
1396            v.set_owner(name)
1397
1398    def check(self, schema, seen):
1399        if not self.tag_member:    # flat union
1400            self.tag_member = seen[c_name(self._tag_name)]
1401            assert self._tag_name == self.tag_member.name
1402        assert isinstance(self.tag_member.type, QAPISchemaEnumType)
1403        if self._tag_name:    # flat union
1404            # branches that are not explicitly covered get an empty type
1405            cases = set([v.name for v in self.variants])
1406            for val in self.tag_member.type.values:
1407                if val.name not in cases:
1408                    v = QAPISchemaObjectTypeVariant(val.name, 'q_empty')
1409                    v.set_owner(self.tag_member.owner)
1410                    self.variants.append(v)
1411        for v in self.variants:
1412            v.check(schema)
1413            # Union names must match enum values; alternate names are
1414            # checked separately. Use 'seen' to tell the two apart.
1415            if seen:
1416                assert v.name in self.tag_member.type.member_names()
1417                assert isinstance(v.type, QAPISchemaObjectType)
1418                v.type.check(schema)
1419
1420    def check_clash(self, info, seen):
1421        for v in self.variants:
1422            # Reset seen map for each variant, since qapi names from one
1423            # branch do not affect another branch
1424            assert isinstance(v.type, QAPISchemaObjectType)
1425            v.type.check_clash(info, dict(seen))
1426
1427
1428class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
1429    role = 'branch'
1430
1431    def __init__(self, name, typ):
1432        QAPISchemaObjectTypeMember.__init__(self, name, typ, False)
1433
1434
1435class QAPISchemaAlternateType(QAPISchemaType):
1436    def __init__(self, name, info, doc, ifcond, variants):
1437        QAPISchemaType.__init__(self, name, info, doc, ifcond)
1438        assert isinstance(variants, QAPISchemaObjectTypeVariants)
1439        assert variants.tag_member
1440        variants.set_owner(name)
1441        variants.tag_member.set_owner(self.name)
1442        self.variants = variants
1443
1444    def check(self, schema):
1445        QAPISchemaType.check(self, schema)
1446        self.variants.tag_member.check(schema)
1447        # Not calling self.variants.check_clash(), because there's nothing
1448        # to clash with
1449        self.variants.check(schema, {})
1450        # Alternate branch names have no relation to the tag enum values;
1451        # so we have to check for potential name collisions ourselves.
1452        seen = {}
1453        for v in self.variants.variants:
1454            v.check_clash(self.info, seen)
1455            if self.doc:
1456                self.doc.connect_member(v)
1457        if self.doc:
1458            self.doc.check()
1459
1460    def c_type(self):
1461        return c_name(self.name) + pointer_suffix
1462
1463    def json_type(self):
1464        return 'value'
1465
1466    def visit(self, visitor):
1467        visitor.visit_alternate_type(self.name, self.info, self.ifcond,
1468                                     self.variants)
1469
1470    def is_empty(self):
1471        return False
1472
1473
1474class QAPISchemaCommand(QAPISchemaEntity):
1475    def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
1476                 gen, success_response, boxed, allow_oob, allow_preconfig):
1477        QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
1478        assert not arg_type or isinstance(arg_type, str)
1479        assert not ret_type or isinstance(ret_type, str)
1480        self._arg_type_name = arg_type
1481        self.arg_type = None
1482        self._ret_type_name = ret_type
1483        self.ret_type = None
1484        self.gen = gen
1485        self.success_response = success_response
1486        self.boxed = boxed
1487        self.allow_oob = allow_oob
1488        self.allow_preconfig = allow_preconfig
1489
1490    def check(self, schema):
1491        QAPISchemaEntity.check(self, schema)
1492        if self._arg_type_name:
1493            self.arg_type = schema.lookup_type(self._arg_type_name)
1494            assert (isinstance(self.arg_type, QAPISchemaObjectType) or
1495                    isinstance(self.arg_type, QAPISchemaAlternateType))
1496            self.arg_type.check(schema)
1497            if self.boxed:
1498                if self.arg_type.is_empty():
1499                    raise QAPISemError(self.info,
1500                                       "Cannot use 'boxed' with empty type")
1501            else:
1502                assert not isinstance(self.arg_type, QAPISchemaAlternateType)
1503                assert not self.arg_type.variants
1504        elif self.boxed:
1505            raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
1506        if self._ret_type_name:
1507            self.ret_type = schema.lookup_type(self._ret_type_name)
1508            assert isinstance(self.ret_type, QAPISchemaType)
1509
1510    def visit(self, visitor):
1511        visitor.visit_command(self.name, self.info, self.ifcond,
1512                              self.arg_type, self.ret_type,
1513                              self.gen, self.success_response,
1514                              self.boxed, self.allow_oob,
1515                              self.allow_preconfig)
1516
1517
1518class QAPISchemaEvent(QAPISchemaEntity):
1519    def __init__(self, name, info, doc, ifcond, arg_type, boxed):
1520        QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
1521        assert not arg_type or isinstance(arg_type, str)
1522        self._arg_type_name = arg_type
1523        self.arg_type = None
1524        self.boxed = boxed
1525
1526    def check(self, schema):
1527        QAPISchemaEntity.check(self, schema)
1528        if self._arg_type_name:
1529            self.arg_type = schema.lookup_type(self._arg_type_name)
1530            assert (isinstance(self.arg_type, QAPISchemaObjectType) or
1531                    isinstance(self.arg_type, QAPISchemaAlternateType))
1532            self.arg_type.check(schema)
1533            if self.boxed:
1534                if self.arg_type.is_empty():
1535                    raise QAPISemError(self.info,
1536                                       "Cannot use 'boxed' with empty type")
1537            else:
1538                assert not isinstance(self.arg_type, QAPISchemaAlternateType)
1539                assert not self.arg_type.variants
1540        elif self.boxed:
1541            raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
1542
1543    def visit(self, visitor):
1544        visitor.visit_event(self.name, self.info, self.ifcond,
1545                            self.arg_type, self.boxed)
1546
1547
1548class QAPISchema(object):
1549    def __init__(self, fname):
1550        self._fname = fname
1551        if sys.version_info[0] >= 3:
1552            f = open(fname, 'r', encoding='utf-8')
1553        else:
1554            f = open(fname, 'r')
1555        parser = QAPISchemaParser(f)
1556        exprs = check_exprs(parser.exprs)
1557        self.docs = parser.docs
1558        self._entity_list = []
1559        self._entity_dict = {}
1560        self._predefining = True
1561        self._def_predefineds()
1562        self._predefining = False
1563        self._def_exprs(exprs)
1564        self.check()
1565
1566    def _def_entity(self, ent):
1567        # Only the predefined types are allowed to not have info
1568        assert ent.info or self._predefining
1569        assert ent.name is None or ent.name not in self._entity_dict
1570        self._entity_list.append(ent)
1571        if ent.name is not None:
1572            self._entity_dict[ent.name] = ent
1573        if ent.info:
1574            ent.module = os.path.relpath(ent.info['file'],
1575                                         os.path.dirname(self._fname))
1576
1577    def lookup_entity(self, name, typ=None):
1578        ent = self._entity_dict.get(name)
1579        if typ and not isinstance(ent, typ):
1580            return None
1581        return ent
1582
1583    def lookup_type(self, name):
1584        return self.lookup_entity(name, QAPISchemaType)
1585
1586    def _def_include(self, expr, info, doc):
1587        include = expr['include']
1588        assert doc is None
1589        main_info = info
1590        while main_info['parent']:
1591            main_info = main_info['parent']
1592        fname = os.path.relpath(include, os.path.dirname(main_info['file']))
1593        self._def_entity(QAPISchemaInclude(fname, info))
1594
1595    def _def_builtin_type(self, name, json_type, c_type):
1596        self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
1597        # Instantiating only the arrays that are actually used would
1598        # be nice, but we can't as long as their generated code
1599        # (qapi-builtin-types.[ch]) may be shared by some other
1600        # schema.
1601        self._make_array_type(name, None)
1602
1603    def _def_predefineds(self):
1604        for t in [('str',    'string',  'char' + pointer_suffix),
1605                  ('number', 'number',  'double'),
1606                  ('int',    'int',     'int64_t'),
1607                  ('int8',   'int',     'int8_t'),
1608                  ('int16',  'int',     'int16_t'),
1609                  ('int32',  'int',     'int32_t'),
1610                  ('int64',  'int',     'int64_t'),
1611                  ('uint8',  'int',     'uint8_t'),
1612                  ('uint16', 'int',     'uint16_t'),
1613                  ('uint32', 'int',     'uint32_t'),
1614                  ('uint64', 'int',     'uint64_t'),
1615                  ('size',   'int',     'uint64_t'),
1616                  ('bool',   'boolean', 'bool'),
1617                  ('any',    'value',   'QObject' + pointer_suffix),
1618                  ('null',   'null',    'QNull' + pointer_suffix)]:
1619            self._def_builtin_type(*t)
1620        self.the_empty_object_type = QAPISchemaObjectType(
1621            'q_empty', None, None, None, None, [], None)
1622        self._def_entity(self.the_empty_object_type)
1623        qtype_values = self._make_enum_members(['none', 'qnull', 'qnum',
1624                                                'qstring', 'qdict', 'qlist',
1625                                                'qbool'])
1626        self._def_entity(QAPISchemaEnumType('QType', None, None, None,
1627                                            qtype_values, 'QTYPE'))
1628
1629    def _make_enum_members(self, values):
1630        return [QAPISchemaMember(v) for v in values]
1631
1632    def _make_implicit_enum_type(self, name, info, ifcond, values):
1633        # See also QAPISchemaObjectTypeMember._pretty_owner()
1634        name = name + 'Kind'   # Use namespace reserved by add_name()
1635        self._def_entity(QAPISchemaEnumType(
1636            name, info, None, ifcond, self._make_enum_members(values), None))
1637        return name
1638
1639    def _make_array_type(self, element_type, info):
1640        name = element_type + 'List'   # Use namespace reserved by add_name()
1641        if not self.lookup_type(name):
1642            self._def_entity(QAPISchemaArrayType(name, info, element_type))
1643        return name
1644
1645    def _make_implicit_object_type(self, name, info, doc, ifcond,
1646                                   role, members):
1647        if not members:
1648            return None
1649        # See also QAPISchemaObjectTypeMember._pretty_owner()
1650        name = 'q_obj_%s-%s' % (name, role)
1651        typ = self.lookup_entity(name, QAPISchemaObjectType)
1652        if typ:
1653            # The implicit object type has multiple users.  This can
1654            # happen only for simple unions' implicit wrapper types.
1655            # Its ifcond should be the disjunction of its user's
1656            # ifconds.  Not implemented.  Instead, we always pass the
1657            # wrapped type's ifcond, which is trivially the same for all
1658            # users.  It's also necessary for the wrapper to compile.
1659            # But it's not tight: the disjunction need not imply it.  We
1660            # may end up compiling useless wrapper types.
1661            # TODO kill simple unions or implement the disjunction
1662            assert ifcond == typ._ifcond # pylint: disable=protected-access
1663        else:
1664            self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond,
1665                                                  None, members, None))
1666        return name
1667
1668    def _def_enum_type(self, expr, info, doc):
1669        name = expr['enum']
1670        data = expr['data']
1671        prefix = expr.get('prefix')
1672        ifcond = expr.get('if')
1673        self._def_entity(QAPISchemaEnumType(
1674            name, info, doc, ifcond,
1675            self._make_enum_members(data), prefix))
1676
1677    def _make_member(self, name, typ, info):
1678        optional = False
1679        if name.startswith('*'):
1680            name = name[1:]
1681            optional = True
1682        if isinstance(typ, list):
1683            assert len(typ) == 1
1684            typ = self._make_array_type(typ[0], info)
1685        return QAPISchemaObjectTypeMember(name, typ, optional)
1686
1687    def _make_members(self, data, info):
1688        return [self._make_member(key, value, info)
1689                for (key, value) in data.items()]
1690
1691    def _def_struct_type(self, expr, info, doc):
1692        name = expr['struct']
1693        base = expr.get('base')
1694        data = expr['data']
1695        ifcond = expr.get('if')
1696        self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond, base,
1697                                              self._make_members(data, info),
1698                                              None))
1699
1700    def _make_variant(self, case, typ):
1701        return QAPISchemaObjectTypeVariant(case, typ)
1702
1703    def _make_simple_variant(self, case, typ, info):
1704        if isinstance(typ, list):
1705            assert len(typ) == 1
1706            typ = self._make_array_type(typ[0], info)
1707        typ = self._make_implicit_object_type(
1708            typ, info, None, self.lookup_type(typ),
1709            'wrapper', [self._make_member('data', typ, info)])
1710        return QAPISchemaObjectTypeVariant(case, typ)
1711
1712    def _def_union_type(self, expr, info, doc):
1713        name = expr['union']
1714        data = expr['data']
1715        base = expr.get('base')
1716        ifcond = expr.get('if')
1717        tag_name = expr.get('discriminator')
1718        tag_member = None
1719        if isinstance(base, dict):
1720            base = self._make_implicit_object_type(
1721                name, info, doc, ifcond,
1722                'base', self._make_members(base, info))
1723        if tag_name:
1724            variants = [self._make_variant(key, value)
1725                        for (key, value) in data.items()]
1726            members = []
1727        else:
1728            variants = [self._make_simple_variant(key, value, info)
1729                        for (key, value) in data.items()]
1730            typ = self._make_implicit_enum_type(name, info, ifcond,
1731                                                [v.name for v in variants])
1732            tag_member = QAPISchemaObjectTypeMember('type', typ, False)
1733            members = [tag_member]
1734        self._def_entity(
1735            QAPISchemaObjectType(name, info, doc, ifcond, base, members,
1736                                 QAPISchemaObjectTypeVariants(tag_name,
1737                                                              tag_member,
1738                                                              variants)))
1739
1740    def _def_alternate_type(self, expr, info, doc):
1741        name = expr['alternate']
1742        data = expr['data']
1743        ifcond = expr.get('if')
1744        variants = [self._make_variant(key, value)
1745                    for (key, value) in data.items()]
1746        tag_member = QAPISchemaObjectTypeMember('type', 'QType', False)
1747        self._def_entity(
1748            QAPISchemaAlternateType(name, info, doc, ifcond,
1749                                    QAPISchemaObjectTypeVariants(None,
1750                                                                 tag_member,
1751                                                                 variants)))
1752
1753    def _def_command(self, expr, info, doc):
1754        name = expr['command']
1755        data = expr.get('data')
1756        rets = expr.get('returns')
1757        gen = expr.get('gen', True)
1758        success_response = expr.get('success-response', True)
1759        boxed = expr.get('boxed', False)
1760        allow_oob = expr.get('allow-oob', False)
1761        allow_preconfig = expr.get('allow-preconfig', False)
1762        ifcond = expr.get('if')
1763        if isinstance(data, OrderedDict):
1764            data = self._make_implicit_object_type(
1765                name, info, doc, ifcond, 'arg', self._make_members(data, info))
1766        if isinstance(rets, list):
1767            assert len(rets) == 1
1768            rets = self._make_array_type(rets[0], info)
1769        self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, data, rets,
1770                                           gen, success_response,
1771                                           boxed, allow_oob, allow_preconfig))
1772
1773    def _def_event(self, expr, info, doc):
1774        name = expr['event']
1775        data = expr.get('data')
1776        boxed = expr.get('boxed', False)
1777        ifcond = expr.get('if')
1778        if isinstance(data, OrderedDict):
1779            data = self._make_implicit_object_type(
1780                name, info, doc, ifcond, 'arg', self._make_members(data, info))
1781        self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, data, boxed))
1782
1783    def _def_exprs(self, exprs):
1784        for expr_elem in exprs:
1785            expr = expr_elem['expr']
1786            info = expr_elem['info']
1787            doc = expr_elem.get('doc')
1788            if 'enum' in expr:
1789                self._def_enum_type(expr, info, doc)
1790            elif 'struct' in expr:
1791                self._def_struct_type(expr, info, doc)
1792            elif 'union' in expr:
1793                self._def_union_type(expr, info, doc)
1794            elif 'alternate' in expr:
1795                self._def_alternate_type(expr, info, doc)
1796            elif 'command' in expr:
1797                self._def_command(expr, info, doc)
1798            elif 'event' in expr:
1799                self._def_event(expr, info, doc)
1800            elif 'include' in expr:
1801                self._def_include(expr, info, doc)
1802            else:
1803                assert False
1804
1805    def check(self):
1806        for ent in self._entity_list:
1807            ent.check(self)
1808
1809    def visit(self, visitor):
1810        visitor.visit_begin(self)
1811        module = None
1812        for entity in self._entity_list:
1813            if visitor.visit_needed(entity):
1814                if entity.module != module:
1815                    module = entity.module
1816                    visitor.visit_module(module)
1817                entity.visit(visitor)
1818        visitor.visit_end()
1819
1820
1821#
1822# Code generation helpers
1823#
1824
1825def camel_case(name):
1826    new_name = ''
1827    first = True
1828    for ch in name:
1829        if ch in ['_', '-']:
1830            first = True
1831        elif first:
1832            new_name += ch.upper()
1833            first = False
1834        else:
1835            new_name += ch.lower()
1836    return new_name
1837
1838
1839# ENUMName -> ENUM_NAME, EnumName1 -> ENUM_NAME1
1840# ENUM_NAME -> ENUM_NAME, ENUM_NAME1 -> ENUM_NAME1, ENUM_Name2 -> ENUM_NAME2
1841# ENUM24_Name -> ENUM24_NAME
1842def camel_to_upper(value):
1843    c_fun_str = c_name(value, False)
1844    if value.isupper():
1845        return c_fun_str
1846
1847    new_name = ''
1848    length = len(c_fun_str)
1849    for i in range(length):
1850        c = c_fun_str[i]
1851        # When c is upper and no '_' appears before, do more checks
1852        if c.isupper() and (i > 0) and c_fun_str[i - 1] != '_':
1853            if i < length - 1 and c_fun_str[i + 1].islower():
1854                new_name += '_'
1855            elif c_fun_str[i - 1].isdigit():
1856                new_name += '_'
1857        new_name += c
1858    return new_name.lstrip('_').upper()
1859
1860
1861def c_enum_const(type_name, const_name, prefix=None):
1862    if prefix is not None:
1863        type_name = prefix
1864    return camel_to_upper(type_name) + '_' + c_name(const_name, False).upper()
1865
1866
1867if hasattr(str, 'maketrans'):
1868    c_name_trans = str.maketrans('.-', '__')
1869else:
1870    c_name_trans = string.maketrans('.-', '__')
1871
1872
1873# Map @name to a valid C identifier.
1874# If @protect, avoid returning certain ticklish identifiers (like
1875# C keywords) by prepending 'q_'.
1876#
1877# Used for converting 'name' from a 'name':'type' qapi definition
1878# into a generated struct member, as well as converting type names
1879# into substrings of a generated C function name.
1880# '__a.b_c' -> '__a_b_c', 'x-foo' -> 'x_foo'
1881# protect=True: 'int' -> 'q_int'; protect=False: 'int' -> 'int'
1882def c_name(name, protect=True):
1883    # ANSI X3J11/88-090, 3.1.1
1884    c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue',
1885                     'default', 'do', 'double', 'else', 'enum', 'extern',
1886                     'float', 'for', 'goto', 'if', 'int', 'long', 'register',
1887                     'return', 'short', 'signed', 'sizeof', 'static',
1888                     'struct', 'switch', 'typedef', 'union', 'unsigned',
1889                     'void', 'volatile', 'while'])
1890    # ISO/IEC 9899:1999, 6.4.1
1891    c99_words = set(['inline', 'restrict', '_Bool', '_Complex', '_Imaginary'])
1892    # ISO/IEC 9899:2011, 6.4.1
1893    c11_words = set(['_Alignas', '_Alignof', '_Atomic', '_Generic',
1894                     '_Noreturn', '_Static_assert', '_Thread_local'])
1895    # GCC http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/C-Extensions.html
1896    # excluding _.*
1897    gcc_words = set(['asm', 'typeof'])
1898    # C++ ISO/IEC 14882:2003 2.11
1899    cpp_words = set(['bool', 'catch', 'class', 'const_cast', 'delete',
1900                     'dynamic_cast', 'explicit', 'false', 'friend', 'mutable',
1901                     'namespace', 'new', 'operator', 'private', 'protected',
1902                     'public', 'reinterpret_cast', 'static_cast', 'template',
1903                     'this', 'throw', 'true', 'try', 'typeid', 'typename',
1904                     'using', 'virtual', 'wchar_t',
1905                     # alternative representations
1906                     'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not',
1907                     'not_eq', 'or', 'or_eq', 'xor', 'xor_eq'])
1908    # namespace pollution:
1909    polluted_words = set(['unix', 'errno', 'mips', 'sparc', 'i386'])
1910    name = name.translate(c_name_trans)
1911    if protect and (name in c89_words | c99_words | c11_words | gcc_words
1912                    | cpp_words | polluted_words):
1913        return 'q_' + name
1914    return name
1915
1916
1917eatspace = '\033EATSPACE.'
1918pointer_suffix = ' *' + eatspace
1919
1920
1921def genindent(count):
1922    ret = ''
1923    for _ in range(count):
1924        ret += ' '
1925    return ret
1926
1927
1928indent_level = 0
1929
1930
1931def push_indent(indent_amount=4):
1932    global indent_level
1933    indent_level += indent_amount
1934
1935
1936def pop_indent(indent_amount=4):
1937    global indent_level
1938    indent_level -= indent_amount
1939
1940
1941# Generate @code with @kwds interpolated.
1942# Obey indent_level, and strip eatspace.
1943def cgen(code, **kwds):
1944    raw = code % kwds
1945    if indent_level:
1946        indent = genindent(indent_level)
1947        # re.subn() lacks flags support before Python 2.7, use re.compile()
1948        raw = re.subn(re.compile(r'^(?!(#|$))', re.MULTILINE),
1949                      indent, raw)
1950        raw = raw[0]
1951    return re.sub(re.escape(eatspace) + r' *', '', raw)
1952
1953
1954def mcgen(code, **kwds):
1955    if code[0] == '\n':
1956        code = code[1:]
1957    return cgen(code, **kwds)
1958
1959
1960def guardname(filename):
1961    return re.sub(r'[^A-Za-z0-9_]', '_', filename).upper()
1962
1963
1964def guardstart(name):
1965    return mcgen('''
1966#ifndef %(name)s
1967#define %(name)s
1968
1969''',
1970                 name=guardname(name))
1971
1972
1973def guardend(name):
1974    return mcgen('''
1975
1976#endif /* %(name)s */
1977''',
1978                 name=guardname(name))
1979
1980
1981def gen_if(ifcond):
1982    ret = ''
1983    for ifc in ifcond:
1984        ret += mcgen('''
1985#if %(cond)s
1986''', cond=ifc)
1987    return ret
1988
1989
1990def gen_endif(ifcond):
1991    ret = ''
1992    for ifc in reversed(ifcond):
1993        ret += mcgen('''
1994#endif /* %(cond)s */
1995''', cond=ifc)
1996    return ret
1997
1998
1999def _wrap_ifcond(ifcond, before, after):
2000    if before == after:
2001        return after   # suppress empty #if ... #endif
2002
2003    assert after.startswith(before)
2004    out = before
2005    added = after[len(before):]
2006    if added[0] == '\n':
2007        out += '\n'
2008        added = added[1:]
2009    out += gen_if(ifcond)
2010    out += added
2011    out += gen_endif(ifcond)
2012    return out
2013
2014
2015def gen_enum_lookup(name, values, prefix=None):
2016    ret = mcgen('''
2017
2018const QEnumLookup %(c_name)s_lookup = {
2019    .array = (const char *const[]) {
2020''',
2021                c_name=c_name(name))
2022    for value in values:
2023        index = c_enum_const(name, value, prefix)
2024        ret += mcgen('''
2025        [%(index)s] = "%(value)s",
2026''',
2027                     index=index, value=value)
2028
2029    ret += mcgen('''
2030    },
2031    .size = %(max_index)s
2032};
2033''',
2034                 max_index=c_enum_const(name, '_MAX', prefix))
2035    return ret
2036
2037
2038def gen_enum(name, values, prefix=None):
2039    # append automatically generated _MAX value
2040    enum_values = values + ['_MAX']
2041
2042    ret = mcgen('''
2043
2044typedef enum %(c_name)s {
2045''',
2046                c_name=c_name(name))
2047
2048    i = 0
2049    for value in enum_values:
2050        ret += mcgen('''
2051    %(c_enum)s = %(i)d,
2052''',
2053                     c_enum=c_enum_const(name, value, prefix),
2054                     i=i)
2055        i += 1
2056
2057    ret += mcgen('''
2058} %(c_name)s;
2059''',
2060                 c_name=c_name(name))
2061
2062    ret += mcgen('''
2063
2064#define %(c_name)s_str(val) \\
2065    qapi_enum_lookup(&%(c_name)s_lookup, (val))
2066
2067extern const QEnumLookup %(c_name)s_lookup;
2068''',
2069                 c_name=c_name(name))
2070    return ret
2071
2072
2073def build_params(arg_type, boxed, extra=None):
2074    ret = ''
2075    sep = ''
2076    if boxed:
2077        assert arg_type
2078        ret += '%s arg' % arg_type.c_param_type()
2079        sep = ', '
2080    elif arg_type:
2081        assert not arg_type.variants
2082        for memb in arg_type.members:
2083            ret += sep
2084            sep = ', '
2085            if memb.optional:
2086                ret += 'bool has_%s, ' % c_name(memb.name)
2087            ret += '%s %s' % (memb.type.c_param_type(),
2088                              c_name(memb.name))
2089    if extra:
2090        ret += sep + extra
2091    return ret if ret else 'void'
2092
2093
2094#
2095# Accumulate and write output
2096#
2097
2098class QAPIGen(object):
2099
2100    def __init__(self):
2101        self._preamble = ''
2102        self._body = ''
2103
2104    def preamble_add(self, text):
2105        self._preamble += text
2106
2107    def add(self, text):
2108        self._body += text
2109
2110    def get_content(self, fname=None):
2111        return (self._top(fname) + self._preamble + self._body
2112                + self._bottom(fname))
2113
2114    def _top(self, fname):
2115        return ''
2116
2117    def _bottom(self, fname):
2118        return ''
2119
2120    def write(self, output_dir, fname):
2121        pathname = os.path.join(output_dir, fname)
2122        dir = os.path.dirname(pathname)
2123        if dir:
2124            try:
2125                os.makedirs(dir)
2126            except os.error as e:
2127                if e.errno != errno.EEXIST:
2128                    raise
2129        fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
2130        if sys.version_info[0] >= 3:
2131            f = open(fd, 'r+', encoding='utf-8')
2132        else:
2133            f = os.fdopen(fd, 'r+')
2134        text = self.get_content(fname)
2135        oldtext = f.read(len(text) + 1)
2136        if text != oldtext:
2137            f.seek(0)
2138            f.truncate(0)
2139            f.write(text)
2140        f.close()
2141
2142
2143@contextmanager
2144def ifcontext(ifcond, *args):
2145    """A 'with' statement context manager to wrap with start_if()/end_if()
2146
2147    *args: any number of QAPIGenCCode
2148
2149    Example::
2150
2151        with ifcontext(ifcond, self._genh, self._genc):
2152            modify self._genh and self._genc ...
2153
2154    Is equivalent to calling::
2155
2156        self._genh.start_if(ifcond)
2157        self._genc.start_if(ifcond)
2158        modify self._genh and self._genc ...
2159        self._genh.end_if()
2160        self._genc.end_if()
2161    """
2162    for arg in args:
2163        arg.start_if(ifcond)
2164    yield
2165    for arg in args:
2166        arg.end_if()
2167
2168
2169class QAPIGenCCode(QAPIGen):
2170
2171    def __init__(self):
2172        QAPIGen.__init__(self)
2173        self._start_if = None
2174
2175    def start_if(self, ifcond):
2176        assert self._start_if is None
2177        self._start_if = (ifcond, self._body, self._preamble)
2178
2179    def end_if(self):
2180        assert self._start_if
2181        self._wrap_ifcond()
2182        self._start_if = None
2183
2184    def _wrap_ifcond(self):
2185        self._body = _wrap_ifcond(self._start_if[0],
2186                                  self._start_if[1], self._body)
2187        self._preamble = _wrap_ifcond(self._start_if[0],
2188                                      self._start_if[2], self._preamble)
2189
2190    def get_content(self, fname=None):
2191        assert self._start_if is None
2192        return QAPIGen.get_content(self, fname)
2193
2194
2195class QAPIGenC(QAPIGenCCode):
2196
2197    def __init__(self, blurb, pydoc):
2198        QAPIGenCCode.__init__(self)
2199        self._blurb = blurb
2200        self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
2201                                                  re.MULTILINE))
2202
2203    def _top(self, fname):
2204        return mcgen('''
2205/* AUTOMATICALLY GENERATED, DO NOT MODIFY */
2206
2207/*
2208%(blurb)s
2209 *
2210 * %(copyright)s
2211 *
2212 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
2213 * See the COPYING.LIB file in the top-level directory.
2214 */
2215
2216''',
2217                     blurb=self._blurb, copyright=self._copyright)
2218
2219    def _bottom(self, fname):
2220        return mcgen('''
2221
2222/* Dummy declaration to prevent empty .o file */
2223char dummy_%(name)s;
2224''',
2225                     name=c_name(fname))
2226
2227
2228class QAPIGenH(QAPIGenC):
2229
2230    def _top(self, fname):
2231        return QAPIGenC._top(self, fname) + guardstart(fname)
2232
2233    def _bottom(self, fname):
2234        return guardend(fname)
2235
2236
2237class QAPIGenDoc(QAPIGen):
2238
2239    def _top(self, fname):
2240        return (QAPIGen._top(self, fname)
2241                + '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n')
2242
2243
2244class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
2245
2246    def __init__(self, prefix, what, blurb, pydoc):
2247        self._prefix = prefix
2248        self._what = what
2249        self._genc = QAPIGenC(blurb, pydoc)
2250        self._genh = QAPIGenH(blurb, pydoc)
2251
2252    def write(self, output_dir):
2253        self._genc.write(output_dir, self._prefix + self._what + '.c')
2254        self._genh.write(output_dir, self._prefix + self._what + '.h')
2255
2256
2257class QAPISchemaModularCVisitor(QAPISchemaVisitor):
2258
2259    def __init__(self, prefix, what, blurb, pydoc):
2260        self._prefix = prefix
2261        self._what = what
2262        self._blurb = blurb
2263        self._pydoc = pydoc
2264        self._module = {}
2265        self._main_module = None
2266
2267    def _module_basename(self, what, name):
2268        if name is None:
2269            return re.sub(r'-', '-builtin-', what)
2270        basename = os.path.join(os.path.dirname(name),
2271                                self._prefix + what)
2272        if name == self._main_module:
2273            return basename
2274        return basename + '-' + os.path.splitext(os.path.basename(name))[0]
2275
2276    def _add_module(self, name, blurb):
2277        if self._main_module is None and name is not None:
2278            self._main_module = name
2279        genc = QAPIGenC(blurb, self._pydoc)
2280        genh = QAPIGenH(blurb, self._pydoc)
2281        self._module[name] = (genc, genh)
2282        self._set_module(name)
2283
2284    def _set_module(self, name):
2285        self._genc, self._genh = self._module[name]
2286
2287    def write(self, output_dir, opt_builtins=False):
2288        for name in self._module:
2289            if name is None and not opt_builtins:
2290                continue
2291            basename = self._module_basename(self._what, name)
2292            (genc, genh) = self._module[name]
2293            genc.write(output_dir, basename + '.c')
2294            genh.write(output_dir, basename + '.h')
2295
2296    def _begin_module(self, name):
2297        pass
2298
2299    def visit_module(self, name):
2300        if name in self._module:
2301            self._set_module(name)
2302            return
2303        self._add_module(name, self._blurb)
2304        self._begin_module(name)
2305
2306    def visit_include(self, name, info):
2307        basename = self._module_basename(self._what, name)
2308        self._genh.preamble_add(mcgen('''
2309#include "%(basename)s.h"
2310''',
2311                                      basename=basename))
2312