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