xref: /openbmc/qemu/scripts/qapi/common.py (revision eeb57c85da04b0c120562b25e3eac5cb16d8bf15)
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):
696
697    def check_if_str(ifcond, info):
698        if not isinstance(ifcond, str):
699            raise QAPISemError(
700                info, "'if' condition must be a string or a list of strings")
701        if ifcond.strip() == '':
702            raise QAPISemError(info, "'if' condition '%s' makes no sense"
703                               % ifcond)
704
705    ifcond = expr.get('if')
706    if ifcond is None:
707        return
708    if isinstance(ifcond, list):
709        if ifcond == []:
710            raise QAPISemError(info, "'if' condition [] is useless")
711        for elt in ifcond:
712            check_if_str(elt, info)
713    else:
714        check_if_str(ifcond, info)
715
716
717def check_type(value, info, source,
718               allow_array=False, allow_dict=False):
719    if value is None:
720        return
721
722    # Array type
723    if isinstance(value, list):
724        if not allow_array:
725            raise QAPISemError(info, "%s cannot be an array" % source)
726        if len(value) != 1 or not isinstance(value[0], str):
727            raise QAPISemError(info,
728                               "%s: array type must contain single type name" %
729                               source)
730        return
731
732    # Type name
733    if isinstance(value, str):
734        return
735
736    # Anonymous type
737
738    if not allow_dict:
739        raise QAPISemError(info, "%s should be a type name" % source)
740
741    if not isinstance(value, OrderedDict):
742        raise QAPISemError(info,
743                           "%s should be an object or type name" % source)
744
745    permit_upper = allow_dict in name_case_whitelist
746
747    # value is a dictionary, check that each member is okay
748    for (key, arg) in value.items():
749        key_source = "%s member '%s'" % (source, key)
750        check_name_str(key, info, key_source,
751                       allow_optional=True, permit_upper=permit_upper)
752        if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
753            raise QAPISemError(info, "%s uses reserved name" % key_source)
754        check_known_keys(arg, info, key_source, ['type'], ['if'])
755        check_if(arg, info)
756        normalize_if(arg)
757        check_type(arg['type'], info, key_source, allow_array=True)
758
759
760def check_command(expr, info):
761    args = expr.get('data')
762    rets = expr.get('returns')
763    boxed = expr.get('boxed', False)
764
765    if boxed and args is None:
766        raise QAPISemError(info, "'boxed': true requires 'data'")
767    check_type(args, info, "'data'", allow_dict=not boxed)
768    check_type(rets, info, "'returns'", allow_array=True)
769
770
771def check_event(expr, info):
772    args = expr.get('data')
773    boxed = expr.get('boxed', False)
774
775    if boxed and args is None:
776        raise QAPISemError(info, "'boxed': true requires 'data'")
777    check_type(args, info, "'data'", allow_dict=not boxed)
778
779
780def check_union(expr, info):
781    name = expr['union']
782    base = expr.get('base')
783    discriminator = expr.get('discriminator')
784    members = expr['data']
785
786    if discriminator is None:   # simple union
787        if base is not None:
788            raise QAPISemError(info, "'base' requires 'discriminator'")
789    else:                       # flat union
790        check_type(base, info, "'base'", allow_dict=name)
791        if not base:
792            raise QAPISemError(info, "'discriminator' requires 'base'")
793        check_name_is_str(discriminator, info, "'discriminator'")
794
795    for (key, value) in members.items():
796        source = "'data' member '%s'" % key
797        check_name_str(key, info, source)
798        check_known_keys(value, info, source, ['type'], ['if'])
799        check_if(value, info)
800        normalize_if(value)
801        check_type(value['type'], info, source, allow_array=not base)
802
803
804def check_alternate(expr, info):
805    members = expr['data']
806
807    if len(members) == 0:
808        raise QAPISemError(info, "'data' must not be empty")
809    for (key, value) in members.items():
810        source = "'data' member '%s'" % key
811        check_name_str(key, info, source)
812        check_known_keys(value, info, source, ['type'], ['if'])
813        check_if(value, info)
814        normalize_if(value)
815        check_type(value['type'], info, source)
816
817
818def check_enum(expr, info):
819    name = expr['enum']
820    members = expr['data']
821    prefix = expr.get('prefix')
822
823    if not isinstance(members, list):
824        raise QAPISemError(info, "'data' must be an array")
825    if prefix is not None and not isinstance(prefix, str):
826        raise QAPISemError(info, "'prefix' must be a string")
827
828    permit_upper = name in name_case_whitelist
829
830    for member in members:
831        source = "'data' member"
832        check_known_keys(member, info, source, ['name'], ['if'])
833        check_name_is_str(member['name'], info, source)
834        source = "%s '%s'" % (source, member['name'])
835        check_name_str(member['name'], info, source,
836                       enum_member=True, permit_upper=permit_upper)
837        check_if(member, info)
838        normalize_if(member)
839
840
841def check_struct(expr, info):
842    name = expr['struct']
843    members = expr['data']
844    features = expr.get('features')
845
846    check_type(members, info, "'data'", allow_dict=name)
847    check_type(expr.get('base'), info, "'base'")
848
849    if features:
850        if not isinstance(features, list):
851            raise QAPISemError(info, "'features' must be an array")
852        for f in features:
853            source = "'features' member"
854            assert isinstance(f, dict)
855            check_known_keys(f, info, source, ['name'], ['if'])
856            check_name_is_str(f['name'], info, source)
857            source = "%s '%s'" % (source, f['name'])
858            check_name_str(f['name'], info, source)
859            check_if(f, info)
860            normalize_if(f)
861
862
863def check_known_keys(value, info, source, required, optional):
864
865    def pprint(elems):
866        return ', '.join("'" + e + "'" for e in sorted(elems))
867
868    missing = set(required) - set(value)
869    if missing:
870        raise QAPISemError(
871            info,
872            "%s misses key%s %s"
873            % (source, 's' if len(missing) > 1 else '',
874               pprint(missing)))
875    allowed = set(required + optional)
876    unknown = set(value) - allowed
877    if unknown:
878        raise QAPISemError(
879            info,
880            "%s has unknown key%s %s\nValid keys are %s."
881            % (source, 's' if len(unknown) > 1 else '',
882               pprint(unknown), pprint(allowed)))
883
884
885def check_keys(expr, info, meta, required, optional=[]):
886    check_known_keys(expr, info, meta, required + [meta], optional)
887
888
889def check_flags(expr, info):
890    for key in ['gen', 'success-response']:
891        if key in expr and expr[key] is not False:
892            raise QAPISemError(
893                info, "flag '%s' may only use false value" % key)
894    for key in ['boxed', 'allow-oob', 'allow-preconfig']:
895        if key in expr and expr[key] is not True:
896            raise QAPISemError(
897                info, "flag '%s' may only use true value" % key)
898
899
900def normalize_enum(expr):
901    if isinstance(expr['data'], list):
902        expr['data'] = [m if isinstance(m, dict) else {'name': m}
903                        for m in expr['data']]
904
905
906def normalize_members(members):
907    if isinstance(members, OrderedDict):
908        for key, arg in members.items():
909            if isinstance(arg, dict):
910                continue
911            members[key] = {'type': arg}
912
913
914def normalize_features(features):
915    if isinstance(features, list):
916        features[:] = [f if isinstance(f, dict) else {'name': f}
917                       for f in features]
918
919
920def normalize_if(expr):
921    ifcond = expr.get('if')
922    if isinstance(ifcond, str):
923        expr['if'] = [ifcond]
924
925
926def check_exprs(exprs):
927    for expr_elem in exprs:
928        expr = expr_elem['expr']
929        info = expr_elem['info']
930        doc = expr_elem.get('doc')
931
932        if 'include' in expr:
933            continue
934
935        if not doc and doc_required:
936            raise QAPISemError(info,
937                               "definition missing documentation comment")
938
939        if 'enum' in expr:
940            meta = 'enum'
941        elif 'union' in expr:
942            meta = 'union'
943        elif 'alternate' in expr:
944            meta = 'alternate'
945        elif 'struct' in expr:
946            meta = 'struct'
947        elif 'command' in expr:
948            meta = 'command'
949        elif 'event' in expr:
950            meta = 'event'
951        else:
952            raise QAPISemError(info, "expression is missing metatype")
953
954        name = expr[meta]
955        check_name_is_str(name, info, "'%s'" % meta)
956        info.set_defn(meta, name)
957        check_defn_name_str(name, info, meta)
958
959        if doc and doc.symbol != name:
960            raise QAPISemError(
961                info, "documentation comment is for '%s'" % doc.symbol)
962
963        if meta == 'enum':
964            check_keys(expr, info, 'enum', ['data'], ['if', 'prefix'])
965            normalize_enum(expr)
966            check_enum(expr, info)
967        elif meta == 'union':
968            check_keys(expr, info, 'union', ['data'],
969                       ['base', 'discriminator', 'if'])
970            normalize_members(expr.get('base'))
971            normalize_members(expr['data'])
972            check_union(expr, info)
973        elif meta == 'alternate':
974            check_keys(expr, info, 'alternate', ['data'], ['if'])
975            normalize_members(expr['data'])
976            check_alternate(expr, info)
977        elif meta == 'struct':
978            check_keys(expr, info, 'struct', ['data'],
979                       ['base', 'if', 'features'])
980            normalize_members(expr['data'])
981            normalize_features(expr.get('features'))
982            check_struct(expr, info)
983        elif meta == 'command':
984            check_keys(expr, info, 'command', [],
985                       ['data', 'returns', 'gen', 'success-response',
986                        'boxed', 'allow-oob', 'allow-preconfig', 'if'])
987            normalize_members(expr.get('data'))
988            check_command(expr, info)
989        elif meta == 'event':
990            check_keys(expr, info, 'event', [], ['data', 'boxed', 'if'])
991            normalize_members(expr.get('data'))
992            check_event(expr, info)
993        else:
994            assert False, 'unexpected meta type'
995
996        normalize_if(expr)
997        check_if(expr, info)
998        check_flags(expr, info)
999
1000        if doc:
1001            doc.check_expr(expr)
1002
1003    return exprs
1004
1005
1006#
1007# Schema compiler frontend
1008# TODO catching name collisions in generated code would be nice
1009#
1010
1011class QAPISchemaEntity(object):
1012    meta = None
1013
1014    def __init__(self, name, info, doc, ifcond=None):
1015        assert name is None or isinstance(name, str)
1016        self.name = name
1017        self._module = None
1018        # For explicitly defined entities, info points to the (explicit)
1019        # definition.  For builtins (and their arrays), info is None.
1020        # For implicitly defined entities, info points to a place that
1021        # triggered the implicit definition (there may be more than one
1022        # such place).
1023        self.info = info
1024        self.doc = doc
1025        self._ifcond = ifcond or []
1026        self._checked = False
1027
1028    def c_name(self):
1029        return c_name(self.name)
1030
1031    def check(self, schema):
1032        assert not self._checked
1033        if self.info:
1034            self._module = os.path.relpath(self.info.fname,
1035                                           os.path.dirname(schema.fname))
1036        self._checked = True
1037
1038    @property
1039    def ifcond(self):
1040        assert self._checked
1041        return self._ifcond
1042
1043    @property
1044    def module(self):
1045        assert self._checked
1046        return self._module
1047
1048    def is_implicit(self):
1049        return not self.info
1050
1051    def visit(self, visitor):
1052        assert self._checked
1053
1054    def describe(self):
1055        assert self.meta
1056        return "%s '%s'" % (self.meta, self.name)
1057
1058
1059class QAPISchemaVisitor(object):
1060    def visit_begin(self, schema):
1061        pass
1062
1063    def visit_end(self):
1064        pass
1065
1066    def visit_module(self, fname):
1067        pass
1068
1069    def visit_needed(self, entity):
1070        # Default to visiting everything
1071        return True
1072
1073    def visit_include(self, fname, info):
1074        pass
1075
1076    def visit_builtin_type(self, name, info, json_type):
1077        pass
1078
1079    def visit_enum_type(self, name, info, ifcond, members, prefix):
1080        pass
1081
1082    def visit_array_type(self, name, info, ifcond, element_type):
1083        pass
1084
1085    def visit_object_type(self, name, info, ifcond, base, members, variants,
1086                          features):
1087        pass
1088
1089    def visit_object_type_flat(self, name, info, ifcond, members, variants,
1090                               features):
1091        pass
1092
1093    def visit_alternate_type(self, name, info, ifcond, variants):
1094        pass
1095
1096    def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
1097                      success_response, boxed, allow_oob, allow_preconfig):
1098        pass
1099
1100    def visit_event(self, name, info, ifcond, arg_type, boxed):
1101        pass
1102
1103
1104class QAPISchemaInclude(QAPISchemaEntity):
1105
1106    def __init__(self, fname, info):
1107        QAPISchemaEntity.__init__(self, None, info, None)
1108        self.fname = fname
1109
1110    def visit(self, visitor):
1111        QAPISchemaEntity.visit(self, visitor)
1112        visitor.visit_include(self.fname, self.info)
1113
1114
1115class QAPISchemaType(QAPISchemaEntity):
1116    # Return the C type for common use.
1117    # For the types we commonly box, this is a pointer type.
1118    def c_type(self):
1119        pass
1120
1121    # Return the C type to be used in a parameter list.
1122    def c_param_type(self):
1123        return self.c_type()
1124
1125    # Return the C type to be used where we suppress boxing.
1126    def c_unboxed_type(self):
1127        return self.c_type()
1128
1129    def json_type(self):
1130        pass
1131
1132    def alternate_qtype(self):
1133        json2qtype = {
1134            'null':    'QTYPE_QNULL',
1135            'string':  'QTYPE_QSTRING',
1136            'number':  'QTYPE_QNUM',
1137            'int':     'QTYPE_QNUM',
1138            'boolean': 'QTYPE_QBOOL',
1139            'object':  'QTYPE_QDICT'
1140        }
1141        return json2qtype.get(self.json_type())
1142
1143    def doc_type(self):
1144        if self.is_implicit():
1145            return None
1146        return self.name
1147
1148    def describe(self):
1149        assert self.meta
1150        return "%s type '%s'" % (self.meta, self.name)
1151
1152
1153class QAPISchemaBuiltinType(QAPISchemaType):
1154    meta = 'built-in'
1155
1156    def __init__(self, name, json_type, c_type):
1157        QAPISchemaType.__init__(self, name, None, None)
1158        assert not c_type or isinstance(c_type, str)
1159        assert json_type in ('string', 'number', 'int', 'boolean', 'null',
1160                             'value')
1161        self._json_type_name = json_type
1162        self._c_type_name = c_type
1163
1164    def c_name(self):
1165        return self.name
1166
1167    def c_type(self):
1168        return self._c_type_name
1169
1170    def c_param_type(self):
1171        if self.name == 'str':
1172            return 'const ' + self._c_type_name
1173        return self._c_type_name
1174
1175    def json_type(self):
1176        return self._json_type_name
1177
1178    def doc_type(self):
1179        return self.json_type()
1180
1181    def visit(self, visitor):
1182        QAPISchemaType.visit(self, visitor)
1183        visitor.visit_builtin_type(self.name, self.info, self.json_type())
1184
1185
1186class QAPISchemaEnumType(QAPISchemaType):
1187    meta = 'enum'
1188
1189    def __init__(self, name, info, doc, ifcond, members, prefix):
1190        QAPISchemaType.__init__(self, name, info, doc, ifcond)
1191        for m in members:
1192            assert isinstance(m, QAPISchemaEnumMember)
1193            m.set_defined_in(name)
1194        assert prefix is None or isinstance(prefix, str)
1195        self.members = members
1196        self.prefix = prefix
1197
1198    def check(self, schema):
1199        QAPISchemaType.check(self, schema)
1200        seen = {}
1201        for m in self.members:
1202            m.check_clash(self.info, seen)
1203            if self.doc:
1204                self.doc.connect_member(m)
1205
1206    def is_implicit(self):
1207        # See QAPISchema._make_implicit_enum_type() and ._def_predefineds()
1208        return self.name.endswith('Kind') or self.name == 'QType'
1209
1210    def c_type(self):
1211        return c_name(self.name)
1212
1213    def member_names(self):
1214        return [m.name for m in self.members]
1215
1216    def json_type(self):
1217        return 'string'
1218
1219    def visit(self, visitor):
1220        QAPISchemaType.visit(self, visitor)
1221        visitor.visit_enum_type(self.name, self.info, self.ifcond,
1222                                self.members, self.prefix)
1223
1224
1225class QAPISchemaArrayType(QAPISchemaType):
1226    meta = 'array'
1227
1228    def __init__(self, name, info, element_type):
1229        QAPISchemaType.__init__(self, name, info, None, None)
1230        assert isinstance(element_type, str)
1231        self._element_type_name = element_type
1232        self.element_type = None
1233
1234    def check(self, schema):
1235        QAPISchemaType.check(self, schema)
1236        self.element_type = schema.resolve_type(
1237            self._element_type_name, self.info,
1238            self.info and self.info.defn_meta)
1239        assert not isinstance(self.element_type, QAPISchemaArrayType)
1240
1241    @property
1242    def ifcond(self):
1243        assert self._checked
1244        return self.element_type.ifcond
1245
1246    @property
1247    def module(self):
1248        assert self._checked
1249        return self.element_type.module
1250
1251    def is_implicit(self):
1252        return True
1253
1254    def c_type(self):
1255        return c_name(self.name) + pointer_suffix
1256
1257    def json_type(self):
1258        return 'array'
1259
1260    def doc_type(self):
1261        elt_doc_type = self.element_type.doc_type()
1262        if not elt_doc_type:
1263            return None
1264        return 'array of ' + elt_doc_type
1265
1266    def visit(self, visitor):
1267        QAPISchemaType.visit(self, visitor)
1268        visitor.visit_array_type(self.name, self.info, self.ifcond,
1269                                 self.element_type)
1270
1271    def describe(self):
1272        assert self.meta
1273        return "%s type ['%s']" % (self.meta, self._element_type_name)
1274
1275
1276class QAPISchemaObjectType(QAPISchemaType):
1277    def __init__(self, name, info, doc, ifcond,
1278                 base, local_members, variants, features):
1279        # struct has local_members, optional base, and no variants
1280        # flat union has base, variants, and no local_members
1281        # simple union has local_members, variants, and no base
1282        QAPISchemaType.__init__(self, name, info, doc, ifcond)
1283        self.meta = 'union' if variants else 'struct'
1284        assert base is None or isinstance(base, str)
1285        for m in local_members:
1286            assert isinstance(m, QAPISchemaObjectTypeMember)
1287            m.set_defined_in(name)
1288        if variants is not None:
1289            assert isinstance(variants, QAPISchemaObjectTypeVariants)
1290            variants.set_defined_in(name)
1291        for f in features:
1292            assert isinstance(f, QAPISchemaFeature)
1293            f.set_defined_in(name)
1294        self._base_name = base
1295        self.base = None
1296        self.local_members = local_members
1297        self.variants = variants
1298        self.members = None
1299        self.features = features
1300
1301    def check(self, schema):
1302        # This calls another type T's .check() exactly when the C
1303        # struct emitted by gen_object() contains that T's C struct
1304        # (pointers don't count).
1305        if self.members is not None:
1306            # A previous .check() completed: nothing to do
1307            return
1308        if self._checked:
1309            # Recursed: C struct contains itself
1310            raise QAPISemError(self.info,
1311                               "object %s contains itself" % self.name)
1312
1313        QAPISchemaType.check(self, schema)
1314        assert self._checked and self.members is None
1315
1316        seen = OrderedDict()
1317        if self._base_name:
1318            self.base = schema.resolve_type(self._base_name, self.info,
1319                                            "'base'")
1320            if (not isinstance(self.base, QAPISchemaObjectType)
1321                    or self.base.variants):
1322                raise QAPISemError(
1323                    self.info,
1324                    "'base' requires a struct type, %s isn't"
1325                    % self.base.describe())
1326            self.base.check(schema)
1327            self.base.check_clash(self.info, seen)
1328        for m in self.local_members:
1329            m.check(schema)
1330            m.check_clash(self.info, seen)
1331            if self.doc:
1332                self.doc.connect_member(m)
1333        members = seen.values()
1334
1335        if self.variants:
1336            self.variants.check(schema, seen)
1337            self.variants.check_clash(self.info, seen)
1338
1339        # Features are in a name space separate from members
1340        seen = {}
1341        for f in self.features:
1342            f.check_clash(self.info, seen)
1343
1344        if self.doc:
1345            self.doc.check()
1346
1347        self.members = members  # mark completed
1348
1349    # Check that the members of this type do not cause duplicate JSON members,
1350    # and update seen to track the members seen so far. Report any errors
1351    # on behalf of info, which is not necessarily self.info
1352    def check_clash(self, info, seen):
1353        assert self._checked
1354        assert not self.variants       # not implemented
1355        for m in self.members:
1356            m.check_clash(info, seen)
1357
1358    @property
1359    def ifcond(self):
1360        assert self._checked
1361        if isinstance(self._ifcond, QAPISchemaType):
1362            # Simple union wrapper type inherits from wrapped type;
1363            # see _make_implicit_object_type()
1364            return self._ifcond.ifcond
1365        return self._ifcond
1366
1367    def is_implicit(self):
1368        # See QAPISchema._make_implicit_object_type(), as well as
1369        # _def_predefineds()
1370        return self.name.startswith('q_')
1371
1372    def is_empty(self):
1373        assert self.members is not None
1374        return not self.members and not self.variants
1375
1376    def c_name(self):
1377        assert self.name != 'q_empty'
1378        return QAPISchemaType.c_name(self)
1379
1380    def c_type(self):
1381        assert not self.is_implicit()
1382        return c_name(self.name) + pointer_suffix
1383
1384    def c_unboxed_type(self):
1385        return c_name(self.name)
1386
1387    def json_type(self):
1388        return 'object'
1389
1390    def visit(self, visitor):
1391        QAPISchemaType.visit(self, visitor)
1392        visitor.visit_object_type(self.name, self.info, self.ifcond,
1393                                  self.base, self.local_members, self.variants,
1394                                  self.features)
1395        visitor.visit_object_type_flat(self.name, self.info, self.ifcond,
1396                                       self.members, self.variants,
1397                                       self.features)
1398
1399
1400class QAPISchemaMember(object):
1401    """ Represents object members, enum members and features """
1402    role = 'member'
1403
1404    def __init__(self, name, info, ifcond=None):
1405        assert isinstance(name, str)
1406        self.name = name
1407        self.info = info
1408        self.ifcond = ifcond or []
1409        self.defined_in = None
1410
1411    def set_defined_in(self, name):
1412        assert not self.defined_in
1413        self.defined_in = name
1414
1415    def check_clash(self, info, seen):
1416        cname = c_name(self.name)
1417        if cname in seen:
1418            raise QAPISemError(
1419                info,
1420                "%s collides with %s"
1421                % (self.describe(info), seen[cname].describe(info)))
1422        seen[cname] = self
1423
1424    def describe(self, info):
1425        role = self.role
1426        defined_in = self.defined_in
1427        assert defined_in
1428
1429        if defined_in.startswith('q_obj_'):
1430            # See QAPISchema._make_implicit_object_type() - reverse the
1431            # mapping there to create a nice human-readable description
1432            defined_in = defined_in[6:]
1433            if defined_in.endswith('-arg'):
1434                # Implicit type created for a command's dict 'data'
1435                assert role == 'member'
1436                role = 'parameter'
1437            elif defined_in.endswith('-base'):
1438                # Implicit type created for a flat union's dict 'base'
1439                role = 'base ' + role
1440            else:
1441                # Implicit type created for a simple union's branch
1442                assert defined_in.endswith('-wrapper')
1443                # Unreachable and not implemented
1444                assert False
1445        elif defined_in.endswith('Kind'):
1446            # See QAPISchema._make_implicit_enum_type()
1447            # Implicit enum created for simple union's branches
1448            assert role == 'value'
1449            role = 'branch'
1450        elif defined_in != info.defn_name:
1451            return "%s '%s' of type '%s'" % (role, self.name, defined_in)
1452        return "%s '%s'" % (role, self.name)
1453
1454
1455class QAPISchemaEnumMember(QAPISchemaMember):
1456    role = 'value'
1457
1458
1459class QAPISchemaFeature(QAPISchemaMember):
1460    role = 'feature'
1461
1462
1463class QAPISchemaObjectTypeMember(QAPISchemaMember):
1464    def __init__(self, name, info, typ, optional, ifcond=None):
1465        QAPISchemaMember.__init__(self, name, info, ifcond)
1466        assert isinstance(typ, str)
1467        assert isinstance(optional, bool)
1468        self._type_name = typ
1469        self.type = None
1470        self.optional = optional
1471
1472    def check(self, schema):
1473        assert self.defined_in
1474        self.type = schema.resolve_type(self._type_name, self.info,
1475                                        self.describe)
1476
1477
1478class QAPISchemaObjectTypeVariants(object):
1479    def __init__(self, tag_name, info, tag_member, variants):
1480        # Flat unions pass tag_name but not tag_member.
1481        # Simple unions and alternates pass tag_member but not tag_name.
1482        # After check(), tag_member is always set, and tag_name remains
1483        # a reliable witness of being used by a flat union.
1484        assert bool(tag_member) != bool(tag_name)
1485        assert (isinstance(tag_name, str) or
1486                isinstance(tag_member, QAPISchemaObjectTypeMember))
1487        for v in variants:
1488            assert isinstance(v, QAPISchemaObjectTypeVariant)
1489        self._tag_name = tag_name
1490        self.info = info
1491        self.tag_member = tag_member
1492        self.variants = variants
1493
1494    def set_defined_in(self, name):
1495        for v in self.variants:
1496            v.set_defined_in(name)
1497
1498    def check(self, schema, seen):
1499        if not self.tag_member: # flat union
1500            self.tag_member = seen.get(c_name(self._tag_name))
1501            base = "'base'"
1502            # Pointing to the base type when not implicit would be
1503            # nice, but we don't know it here
1504            if not self.tag_member or self._tag_name != self.tag_member.name:
1505                raise QAPISemError(
1506                    self.info,
1507                    "discriminator '%s' is not a member of %s"
1508                    % (self._tag_name, base))
1509            # Here we do:
1510            base_type = schema.lookup_type(self.tag_member.defined_in)
1511            assert base_type
1512            if not base_type.is_implicit():
1513                base = "base type '%s'" % self.tag_member.defined_in
1514            if not isinstance(self.tag_member.type, QAPISchemaEnumType):
1515                raise QAPISemError(
1516                    self.info,
1517                    "discriminator member '%s' of %s must be of enum type"
1518                    % (self._tag_name, base))
1519            if self.tag_member.optional:
1520                raise QAPISemError(
1521                    self.info,
1522                    "discriminator member '%s' of %s must not be optional"
1523                    % (self._tag_name, base))
1524            if self.tag_member.ifcond:
1525                raise QAPISemError(
1526                    self.info,
1527                    "discriminator member '%s' of %s must not be conditional"
1528                    % (self._tag_name, base))
1529        else:                   # simple union
1530            assert isinstance(self.tag_member.type, QAPISchemaEnumType)
1531            assert not self.tag_member.optional
1532            assert self.tag_member.ifcond == []
1533        if self._tag_name:    # flat union
1534            # branches that are not explicitly covered get an empty type
1535            cases = set([v.name for v in self.variants])
1536            for m in self.tag_member.type.members:
1537                if m.name not in cases:
1538                    v = QAPISchemaObjectTypeVariant(m.name, self.info,
1539                                                    'q_empty', m.ifcond)
1540                    v.set_defined_in(self.tag_member.defined_in)
1541                    self.variants.append(v)
1542        if not self.variants:
1543            raise QAPISemError(self.info, "union has no branches")
1544        for v in self.variants:
1545            v.check(schema)
1546            # Union names must match enum values; alternate names are
1547            # checked separately. Use 'seen' to tell the two apart.
1548            if seen:
1549                if v.name not in self.tag_member.type.member_names():
1550                    raise QAPISemError(
1551                        self.info,
1552                        "branch '%s' is not a value of %s"
1553                        % (v.name, self.tag_member.type.describe()))
1554                if (not isinstance(v.type, QAPISchemaObjectType)
1555                        or v.type.variants):
1556                    raise QAPISemError(
1557                        self.info,
1558                        "%s cannot use %s"
1559                        % (v.describe(self.info), v.type.describe()))
1560                v.type.check(schema)
1561
1562    def check_clash(self, info, seen):
1563        for v in self.variants:
1564            # Reset seen map for each variant, since qapi names from one
1565            # branch do not affect another branch
1566            v.type.check_clash(info, dict(seen))
1567
1568
1569class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
1570    role = 'branch'
1571
1572    def __init__(self, name, info, typ, ifcond=None):
1573        QAPISchemaObjectTypeMember.__init__(self, name, info, typ,
1574                                            False, ifcond)
1575
1576
1577class QAPISchemaAlternateType(QAPISchemaType):
1578    meta = 'alternate'
1579
1580    def __init__(self, name, info, doc, ifcond, variants):
1581        QAPISchemaType.__init__(self, name, info, doc, ifcond)
1582        assert isinstance(variants, QAPISchemaObjectTypeVariants)
1583        assert variants.tag_member
1584        variants.set_defined_in(name)
1585        variants.tag_member.set_defined_in(self.name)
1586        self.variants = variants
1587
1588    def check(self, schema):
1589        QAPISchemaType.check(self, schema)
1590        self.variants.tag_member.check(schema)
1591        # Not calling self.variants.check_clash(), because there's nothing
1592        # to clash with
1593        self.variants.check(schema, {})
1594        # Alternate branch names have no relation to the tag enum values;
1595        # so we have to check for potential name collisions ourselves.
1596        seen = {}
1597        types_seen = {}
1598        for v in self.variants.variants:
1599            v.check_clash(self.info, seen)
1600            qtype = v.type.alternate_qtype()
1601            if not qtype:
1602                raise QAPISemError(
1603                    self.info,
1604                    "%s cannot use %s"
1605                    % (v.describe(self.info), v.type.describe()))
1606            conflicting = set([qtype])
1607            if qtype == 'QTYPE_QSTRING':
1608                if isinstance(v.type, QAPISchemaEnumType):
1609                    for m in v.type.members:
1610                        if m.name in ['on', 'off']:
1611                            conflicting.add('QTYPE_QBOOL')
1612                        if re.match(r'[-+0-9.]', m.name):
1613                            # lazy, could be tightened
1614                            conflicting.add('QTYPE_QNUM')
1615                else:
1616                    conflicting.add('QTYPE_QNUM')
1617                    conflicting.add('QTYPE_QBOOL')
1618            for qt in conflicting:
1619                if qt in types_seen:
1620                    raise QAPISemError(
1621                        self.info,
1622                        "%s can't be distinguished from '%s'"
1623                        % (v.describe(self.info), types_seen[qt]))
1624                types_seen[qt] = v.name
1625            if self.doc:
1626                self.doc.connect_member(v)
1627        if self.doc:
1628            self.doc.check()
1629
1630    def c_type(self):
1631        return c_name(self.name) + pointer_suffix
1632
1633    def json_type(self):
1634        return 'value'
1635
1636    def visit(self, visitor):
1637        QAPISchemaType.visit(self, visitor)
1638        visitor.visit_alternate_type(self.name, self.info, self.ifcond,
1639                                     self.variants)
1640
1641
1642class QAPISchemaCommand(QAPISchemaEntity):
1643    meta = 'command'
1644
1645    def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
1646                 gen, success_response, boxed, allow_oob, allow_preconfig):
1647        QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
1648        assert not arg_type or isinstance(arg_type, str)
1649        assert not ret_type or isinstance(ret_type, str)
1650        self._arg_type_name = arg_type
1651        self.arg_type = None
1652        self._ret_type_name = ret_type
1653        self.ret_type = None
1654        self.gen = gen
1655        self.success_response = success_response
1656        self.boxed = boxed
1657        self.allow_oob = allow_oob
1658        self.allow_preconfig = allow_preconfig
1659
1660    def check(self, schema):
1661        QAPISchemaEntity.check(self, schema)
1662        if self._arg_type_name:
1663            self.arg_type = schema.resolve_type(
1664                self._arg_type_name, self.info, "command's 'data'")
1665            if not isinstance(self.arg_type, QAPISchemaObjectType):
1666                raise QAPISemError(
1667                    self.info,
1668                    "command's 'data' cannot take %s"
1669                    % self.arg_type.describe())
1670            if self.arg_type.variants and not self.boxed:
1671                raise QAPISemError(
1672                    self.info,
1673                    "command's 'data' can take %s only with 'boxed': true"
1674                    % self.arg_type.describe())
1675        if self._ret_type_name:
1676            self.ret_type = schema.resolve_type(
1677                self._ret_type_name, self.info, "command's 'returns'")
1678            if self.name not in returns_whitelist:
1679                if not (isinstance(self.ret_type, QAPISchemaObjectType)
1680                        or (isinstance(self.ret_type, QAPISchemaArrayType)
1681                            and isinstance(self.ret_type.element_type,
1682                                           QAPISchemaObjectType))):
1683                    raise QAPISemError(
1684                        self.info,
1685                        "command's 'returns' cannot take %s"
1686                        % self.ret_type.describe())
1687
1688    def visit(self, visitor):
1689        QAPISchemaEntity.visit(self, visitor)
1690        visitor.visit_command(self.name, self.info, self.ifcond,
1691                              self.arg_type, self.ret_type,
1692                              self.gen, self.success_response,
1693                              self.boxed, self.allow_oob,
1694                              self.allow_preconfig)
1695
1696
1697class QAPISchemaEvent(QAPISchemaEntity):
1698    meta = 'event'
1699
1700    def __init__(self, name, info, doc, ifcond, arg_type, boxed):
1701        QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
1702        assert not arg_type or isinstance(arg_type, str)
1703        self._arg_type_name = arg_type
1704        self.arg_type = None
1705        self.boxed = boxed
1706
1707    def check(self, schema):
1708        QAPISchemaEntity.check(self, schema)
1709        if self._arg_type_name:
1710            self.arg_type = schema.resolve_type(
1711                self._arg_type_name, self.info, "event's 'data'")
1712            if not isinstance(self.arg_type, QAPISchemaObjectType):
1713                raise QAPISemError(
1714                    self.info,
1715                    "event's 'data' cannot take %s"
1716                    % self.arg_type.describe())
1717            if self.arg_type.variants and not self.boxed:
1718                raise QAPISemError(
1719                    self.info,
1720                    "event's 'data' can take %s only with 'boxed': true"
1721                    % self.arg_type.describe())
1722
1723    def visit(self, visitor):
1724        QAPISchemaEntity.visit(self, visitor)
1725        visitor.visit_event(self.name, self.info, self.ifcond,
1726                            self.arg_type, self.boxed)
1727
1728
1729class QAPISchema(object):
1730    def __init__(self, fname):
1731        self.fname = fname
1732        if sys.version_info[0] >= 3:
1733            f = open(fname, 'r', encoding='utf-8')
1734        else:
1735            f = open(fname, 'r')
1736        parser = QAPISchemaParser(f)
1737        exprs = check_exprs(parser.exprs)
1738        self.docs = parser.docs
1739        self._entity_list = []
1740        self._entity_dict = {}
1741        self._predefining = True
1742        self._def_predefineds()
1743        self._predefining = False
1744        self._def_exprs(exprs)
1745        self.check()
1746
1747    def _def_entity(self, ent):
1748        # Only the predefined types are allowed to not have info
1749        assert ent.info or self._predefining
1750        self._entity_list.append(ent)
1751        if ent.name is None:
1752            return
1753        # TODO reject names that differ only in '_' vs. '.'  vs. '-',
1754        # because they're liable to clash in generated C.
1755        other_ent = self._entity_dict.get(ent.name)
1756        if other_ent:
1757            raise QAPISemError(
1758                ent.info, "%s is already defined" % other_ent.describe())
1759        self._entity_dict[ent.name] = ent
1760
1761    def lookup_entity(self, name, typ=None):
1762        ent = self._entity_dict.get(name)
1763        if typ and not isinstance(ent, typ):
1764            return None
1765        return ent
1766
1767    def lookup_type(self, name):
1768        return self.lookup_entity(name, QAPISchemaType)
1769
1770    def resolve_type(self, name, info, what):
1771        typ = self.lookup_type(name)
1772        if not typ:
1773            if callable(what):
1774                what = what(info)
1775            raise QAPISemError(
1776                info, "%s uses unknown type '%s'" % (what, name))
1777        return typ
1778
1779    def _def_include(self, expr, info, doc):
1780        include = expr['include']
1781        assert doc is None
1782        main_info = info
1783        while main_info.parent:
1784            main_info = main_info.parent
1785        fname = os.path.relpath(include, os.path.dirname(main_info.fname))
1786        self._def_entity(QAPISchemaInclude(fname, info))
1787
1788    def _def_builtin_type(self, name, json_type, c_type):
1789        self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
1790        # Instantiating only the arrays that are actually used would
1791        # be nice, but we can't as long as their generated code
1792        # (qapi-builtin-types.[ch]) may be shared by some other
1793        # schema.
1794        self._make_array_type(name, None)
1795
1796    def _def_predefineds(self):
1797        for t in [('str',    'string',  'char' + pointer_suffix),
1798                  ('number', 'number',  'double'),
1799                  ('int',    'int',     'int64_t'),
1800                  ('int8',   'int',     'int8_t'),
1801                  ('int16',  'int',     'int16_t'),
1802                  ('int32',  'int',     'int32_t'),
1803                  ('int64',  'int',     'int64_t'),
1804                  ('uint8',  'int',     'uint8_t'),
1805                  ('uint16', 'int',     'uint16_t'),
1806                  ('uint32', 'int',     'uint32_t'),
1807                  ('uint64', 'int',     'uint64_t'),
1808                  ('size',   'int',     'uint64_t'),
1809                  ('bool',   'boolean', 'bool'),
1810                  ('any',    'value',   'QObject' + pointer_suffix),
1811                  ('null',   'null',    'QNull' + pointer_suffix)]:
1812            self._def_builtin_type(*t)
1813        self.the_empty_object_type = QAPISchemaObjectType(
1814            'q_empty', None, None, None, None, [], None, [])
1815        self._def_entity(self.the_empty_object_type)
1816
1817        qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
1818                  'qbool']
1819        qtype_values = self._make_enum_members(
1820            [{'name': n} for n in qtypes], None)
1821
1822        self._def_entity(QAPISchemaEnumType('QType', None, None, None,
1823                                            qtype_values, 'QTYPE'))
1824
1825    def _make_features(self, features, info):
1826        return [QAPISchemaFeature(f['name'], info, f.get('if'))
1827                for f in features]
1828
1829    def _make_enum_members(self, values, info):
1830        return [QAPISchemaEnumMember(v['name'], info, v.get('if'))
1831                for v in values]
1832
1833    def _make_implicit_enum_type(self, name, info, ifcond, values):
1834        # See also QAPISchemaObjectTypeMember.describe()
1835        name = name + 'Kind'    # reserved by check_defn_name_str()
1836        self._def_entity(QAPISchemaEnumType(
1837            name, info, None, ifcond, self._make_enum_members(values, info),
1838            None))
1839        return name
1840
1841    def _make_array_type(self, element_type, info):
1842        name = element_type + 'List'    # reserved by check_defn_name_str()
1843        if not self.lookup_type(name):
1844            self._def_entity(QAPISchemaArrayType(name, info, element_type))
1845        return name
1846
1847    def _make_implicit_object_type(self, name, info, doc, ifcond,
1848                                   role, members):
1849        if not members:
1850            return None
1851        # See also QAPISchemaObjectTypeMember.describe()
1852        name = 'q_obj_%s-%s' % (name, role)
1853        typ = self.lookup_entity(name, QAPISchemaObjectType)
1854        if typ:
1855            # The implicit object type has multiple users.  This can
1856            # happen only for simple unions' implicit wrapper types.
1857            # Its ifcond should be the disjunction of its user's
1858            # ifconds.  Not implemented.  Instead, we always pass the
1859            # wrapped type's ifcond, which is trivially the same for all
1860            # users.  It's also necessary for the wrapper to compile.
1861            # But it's not tight: the disjunction need not imply it.  We
1862            # may end up compiling useless wrapper types.
1863            # TODO kill simple unions or implement the disjunction
1864            assert (ifcond or []) == typ._ifcond # pylint: disable=protected-access
1865        else:
1866            self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond,
1867                                                  None, members, None, []))
1868        return name
1869
1870    def _def_enum_type(self, expr, info, doc):
1871        name = expr['enum']
1872        data = expr['data']
1873        prefix = expr.get('prefix')
1874        ifcond = expr.get('if')
1875        self._def_entity(QAPISchemaEnumType(
1876            name, info, doc, ifcond,
1877            self._make_enum_members(data, info), prefix))
1878
1879    def _make_member(self, name, typ, ifcond, info):
1880        optional = False
1881        if name.startswith('*'):
1882            name = name[1:]
1883            optional = True
1884        if isinstance(typ, list):
1885            assert len(typ) == 1
1886            typ = self._make_array_type(typ[0], info)
1887        return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond)
1888
1889    def _make_members(self, data, info):
1890        return [self._make_member(key, value['type'], value.get('if'), info)
1891                for (key, value) in data.items()]
1892
1893    def _def_struct_type(self, expr, info, doc):
1894        name = expr['struct']
1895        base = expr.get('base')
1896        data = expr['data']
1897        ifcond = expr.get('if')
1898        features = expr.get('features', [])
1899        self._def_entity(QAPISchemaObjectType(
1900            name, info, doc, ifcond, base,
1901            self._make_members(data, info),
1902            None,
1903            self._make_features(features, info)))
1904
1905    def _make_variant(self, case, typ, ifcond, info):
1906        return QAPISchemaObjectTypeVariant(case, info, typ, ifcond)
1907
1908    def _make_simple_variant(self, case, typ, ifcond, info):
1909        if isinstance(typ, list):
1910            assert len(typ) == 1
1911            typ = self._make_array_type(typ[0], info)
1912        typ = self._make_implicit_object_type(
1913            typ, info, None, self.lookup_type(typ),
1914            'wrapper', [self._make_member('data', typ, None, info)])
1915        return QAPISchemaObjectTypeVariant(case, info, typ, ifcond)
1916
1917    def _def_union_type(self, expr, info, doc):
1918        name = expr['union']
1919        data = expr['data']
1920        base = expr.get('base')
1921        ifcond = expr.get('if')
1922        tag_name = expr.get('discriminator')
1923        tag_member = None
1924        if isinstance(base, dict):
1925            base = self._make_implicit_object_type(
1926                name, info, doc, ifcond,
1927                'base', self._make_members(base, info))
1928        if tag_name:
1929            variants = [self._make_variant(key, value['type'],
1930                                           value.get('if'), info)
1931                        for (key, value) in data.items()]
1932            members = []
1933        else:
1934            variants = [self._make_simple_variant(key, value['type'],
1935                                                  value.get('if'), info)
1936                        for (key, value) in data.items()]
1937            enum = [{'name': v.name, 'if': v.ifcond} for v in variants]
1938            typ = self._make_implicit_enum_type(name, info, ifcond, enum)
1939            tag_member = QAPISchemaObjectTypeMember('type', info, typ, False)
1940            members = [tag_member]
1941        self._def_entity(
1942            QAPISchemaObjectType(name, info, doc, ifcond, base, members,
1943                                 QAPISchemaObjectTypeVariants(
1944                                     tag_name, info, tag_member, variants),
1945                                 []))
1946
1947    def _def_alternate_type(self, expr, info, doc):
1948        name = expr['alternate']
1949        data = expr['data']
1950        ifcond = expr.get('if')
1951        variants = [self._make_variant(key, value['type'], value.get('if'),
1952                                       info)
1953                    for (key, value) in data.items()]
1954        tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
1955        self._def_entity(
1956            QAPISchemaAlternateType(name, info, doc, ifcond,
1957                                    QAPISchemaObjectTypeVariants(
1958                                        None, info, tag_member, variants)))
1959
1960    def _def_command(self, expr, info, doc):
1961        name = expr['command']
1962        data = expr.get('data')
1963        rets = expr.get('returns')
1964        gen = expr.get('gen', True)
1965        success_response = expr.get('success-response', True)
1966        boxed = expr.get('boxed', False)
1967        allow_oob = expr.get('allow-oob', False)
1968        allow_preconfig = expr.get('allow-preconfig', False)
1969        ifcond = expr.get('if')
1970        if isinstance(data, OrderedDict):
1971            data = self._make_implicit_object_type(
1972                name, info, doc, ifcond, 'arg', self._make_members(data, info))
1973        if isinstance(rets, list):
1974            assert len(rets) == 1
1975            rets = self._make_array_type(rets[0], info)
1976        self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, data, rets,
1977                                           gen, success_response,
1978                                           boxed, allow_oob, allow_preconfig))
1979
1980    def _def_event(self, expr, info, doc):
1981        name = expr['event']
1982        data = expr.get('data')
1983        boxed = expr.get('boxed', False)
1984        ifcond = expr.get('if')
1985        if isinstance(data, OrderedDict):
1986            data = self._make_implicit_object_type(
1987                name, info, doc, ifcond, 'arg', self._make_members(data, info))
1988        self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, data, boxed))
1989
1990    def _def_exprs(self, exprs):
1991        for expr_elem in exprs:
1992            expr = expr_elem['expr']
1993            info = expr_elem['info']
1994            doc = expr_elem.get('doc')
1995            if 'enum' in expr:
1996                self._def_enum_type(expr, info, doc)
1997            elif 'struct' in expr:
1998                self._def_struct_type(expr, info, doc)
1999            elif 'union' in expr:
2000                self._def_union_type(expr, info, doc)
2001            elif 'alternate' in expr:
2002                self._def_alternate_type(expr, info, doc)
2003            elif 'command' in expr:
2004                self._def_command(expr, info, doc)
2005            elif 'event' in expr:
2006                self._def_event(expr, info, doc)
2007            elif 'include' in expr:
2008                self._def_include(expr, info, doc)
2009            else:
2010                assert False
2011
2012    def check(self):
2013        for ent in self._entity_list:
2014            ent.check(self)
2015
2016    def visit(self, visitor):
2017        visitor.visit_begin(self)
2018        module = None
2019        visitor.visit_module(module)
2020        for entity in self._entity_list:
2021            if visitor.visit_needed(entity):
2022                if entity.module != module:
2023                    module = entity.module
2024                    visitor.visit_module(module)
2025                entity.visit(visitor)
2026        visitor.visit_end()
2027
2028
2029#
2030# Code generation helpers
2031#
2032
2033def camel_case(name):
2034    new_name = ''
2035    first = True
2036    for ch in name:
2037        if ch in ['_', '-']:
2038            first = True
2039        elif first:
2040            new_name += ch.upper()
2041            first = False
2042        else:
2043            new_name += ch.lower()
2044    return new_name
2045
2046
2047# ENUMName -> ENUM_NAME, EnumName1 -> ENUM_NAME1
2048# ENUM_NAME -> ENUM_NAME, ENUM_NAME1 -> ENUM_NAME1, ENUM_Name2 -> ENUM_NAME2
2049# ENUM24_Name -> ENUM24_NAME
2050def camel_to_upper(value):
2051    c_fun_str = c_name(value, False)
2052    if value.isupper():
2053        return c_fun_str
2054
2055    new_name = ''
2056    length = len(c_fun_str)
2057    for i in range(length):
2058        c = c_fun_str[i]
2059        # When c is upper and no '_' appears before, do more checks
2060        if c.isupper() and (i > 0) and c_fun_str[i - 1] != '_':
2061            if i < length - 1 and c_fun_str[i + 1].islower():
2062                new_name += '_'
2063            elif c_fun_str[i - 1].isdigit():
2064                new_name += '_'
2065        new_name += c
2066    return new_name.lstrip('_').upper()
2067
2068
2069def c_enum_const(type_name, const_name, prefix=None):
2070    if prefix is not None:
2071        type_name = prefix
2072    return camel_to_upper(type_name) + '_' + c_name(const_name, False).upper()
2073
2074
2075if hasattr(str, 'maketrans'):
2076    c_name_trans = str.maketrans('.-', '__')
2077else:
2078    c_name_trans = string.maketrans('.-', '__')
2079
2080
2081# Map @name to a valid C identifier.
2082# If @protect, avoid returning certain ticklish identifiers (like
2083# C keywords) by prepending 'q_'.
2084#
2085# Used for converting 'name' from a 'name':'type' qapi definition
2086# into a generated struct member, as well as converting type names
2087# into substrings of a generated C function name.
2088# '__a.b_c' -> '__a_b_c', 'x-foo' -> 'x_foo'
2089# protect=True: 'int' -> 'q_int'; protect=False: 'int' -> 'int'
2090def c_name(name, protect=True):
2091    # ANSI X3J11/88-090, 3.1.1
2092    c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue',
2093                     'default', 'do', 'double', 'else', 'enum', 'extern',
2094                     'float', 'for', 'goto', 'if', 'int', 'long', 'register',
2095                     'return', 'short', 'signed', 'sizeof', 'static',
2096                     'struct', 'switch', 'typedef', 'union', 'unsigned',
2097                     'void', 'volatile', 'while'])
2098    # ISO/IEC 9899:1999, 6.4.1
2099    c99_words = set(['inline', 'restrict', '_Bool', '_Complex', '_Imaginary'])
2100    # ISO/IEC 9899:2011, 6.4.1
2101    c11_words = set(['_Alignas', '_Alignof', '_Atomic', '_Generic',
2102                     '_Noreturn', '_Static_assert', '_Thread_local'])
2103    # GCC http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/C-Extensions.html
2104    # excluding _.*
2105    gcc_words = set(['asm', 'typeof'])
2106    # C++ ISO/IEC 14882:2003 2.11
2107    cpp_words = set(['bool', 'catch', 'class', 'const_cast', 'delete',
2108                     'dynamic_cast', 'explicit', 'false', 'friend', 'mutable',
2109                     'namespace', 'new', 'operator', 'private', 'protected',
2110                     'public', 'reinterpret_cast', 'static_cast', 'template',
2111                     'this', 'throw', 'true', 'try', 'typeid', 'typename',
2112                     'using', 'virtual', 'wchar_t',
2113                     # alternative representations
2114                     'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not',
2115                     'not_eq', 'or', 'or_eq', 'xor', 'xor_eq'])
2116    # namespace pollution:
2117    polluted_words = set(['unix', 'errno', 'mips', 'sparc', 'i386'])
2118    name = name.translate(c_name_trans)
2119    if protect and (name in c89_words | c99_words | c11_words | gcc_words
2120                    | cpp_words | polluted_words):
2121        return 'q_' + name
2122    return name
2123
2124
2125eatspace = '\033EATSPACE.'
2126pointer_suffix = ' *' + eatspace
2127
2128
2129def genindent(count):
2130    ret = ''
2131    for _ in range(count):
2132        ret += ' '
2133    return ret
2134
2135
2136indent_level = 0
2137
2138
2139def push_indent(indent_amount=4):
2140    global indent_level
2141    indent_level += indent_amount
2142
2143
2144def pop_indent(indent_amount=4):
2145    global indent_level
2146    indent_level -= indent_amount
2147
2148
2149# Generate @code with @kwds interpolated.
2150# Obey indent_level, and strip eatspace.
2151def cgen(code, **kwds):
2152    raw = code % kwds
2153    if indent_level:
2154        indent = genindent(indent_level)
2155        # re.subn() lacks flags support before Python 2.7, use re.compile()
2156        raw = re.subn(re.compile(r'^(?!(#|$))', re.MULTILINE),
2157                      indent, raw)
2158        raw = raw[0]
2159    return re.sub(re.escape(eatspace) + r' *', '', raw)
2160
2161
2162def mcgen(code, **kwds):
2163    if code[0] == '\n':
2164        code = code[1:]
2165    return cgen(code, **kwds)
2166
2167
2168def c_fname(filename):
2169    return re.sub(r'[^A-Za-z0-9_]', '_', filename)
2170
2171
2172def guardstart(name):
2173    return mcgen('''
2174#ifndef %(name)s
2175#define %(name)s
2176
2177''',
2178                 name=c_fname(name).upper())
2179
2180
2181def guardend(name):
2182    return mcgen('''
2183
2184#endif /* %(name)s */
2185''',
2186                 name=c_fname(name).upper())
2187
2188
2189def gen_if(ifcond):
2190    ret = ''
2191    for ifc in ifcond:
2192        ret += mcgen('''
2193#if %(cond)s
2194''', cond=ifc)
2195    return ret
2196
2197
2198def gen_endif(ifcond):
2199    ret = ''
2200    for ifc in reversed(ifcond):
2201        ret += mcgen('''
2202#endif /* %(cond)s */
2203''', cond=ifc)
2204    return ret
2205
2206
2207def _wrap_ifcond(ifcond, before, after):
2208    if before == after:
2209        return after   # suppress empty #if ... #endif
2210
2211    assert after.startswith(before)
2212    out = before
2213    added = after[len(before):]
2214    if added[0] == '\n':
2215        out += '\n'
2216        added = added[1:]
2217    out += gen_if(ifcond)
2218    out += added
2219    out += gen_endif(ifcond)
2220    return out
2221
2222
2223def gen_enum_lookup(name, members, prefix=None):
2224    ret = mcgen('''
2225
2226const QEnumLookup %(c_name)s_lookup = {
2227    .array = (const char *const[]) {
2228''',
2229                c_name=c_name(name))
2230    for m in members:
2231        ret += gen_if(m.ifcond)
2232        index = c_enum_const(name, m.name, prefix)
2233        ret += mcgen('''
2234        [%(index)s] = "%(name)s",
2235''',
2236                     index=index, name=m.name)
2237        ret += gen_endif(m.ifcond)
2238
2239    ret += mcgen('''
2240    },
2241    .size = %(max_index)s
2242};
2243''',
2244                 max_index=c_enum_const(name, '_MAX', prefix))
2245    return ret
2246
2247
2248def gen_enum(name, members, prefix=None):
2249    # append automatically generated _MAX value
2250    enum_members = members + [QAPISchemaEnumMember('_MAX', None)]
2251
2252    ret = mcgen('''
2253
2254typedef enum %(c_name)s {
2255''',
2256                c_name=c_name(name))
2257
2258    for m in enum_members:
2259        ret += gen_if(m.ifcond)
2260        ret += mcgen('''
2261    %(c_enum)s,
2262''',
2263                     c_enum=c_enum_const(name, m.name, prefix))
2264        ret += gen_endif(m.ifcond)
2265
2266    ret += mcgen('''
2267} %(c_name)s;
2268''',
2269                 c_name=c_name(name))
2270
2271    ret += mcgen('''
2272
2273#define %(c_name)s_str(val) \\
2274    qapi_enum_lookup(&%(c_name)s_lookup, (val))
2275
2276extern const QEnumLookup %(c_name)s_lookup;
2277''',
2278                 c_name=c_name(name))
2279    return ret
2280
2281
2282def build_params(arg_type, boxed, extra=None):
2283    ret = ''
2284    sep = ''
2285    if boxed:
2286        assert arg_type
2287        ret += '%s arg' % arg_type.c_param_type()
2288        sep = ', '
2289    elif arg_type:
2290        assert not arg_type.variants
2291        for memb in arg_type.members:
2292            ret += sep
2293            sep = ', '
2294            if memb.optional:
2295                ret += 'bool has_%s, ' % c_name(memb.name)
2296            ret += '%s %s' % (memb.type.c_param_type(),
2297                              c_name(memb.name))
2298    if extra:
2299        ret += sep + extra
2300    return ret if ret else 'void'
2301
2302
2303#
2304# Accumulate and write output
2305#
2306
2307class QAPIGen(object):
2308
2309    def __init__(self, fname):
2310        self.fname = fname
2311        self._preamble = ''
2312        self._body = ''
2313
2314    def preamble_add(self, text):
2315        self._preamble += text
2316
2317    def add(self, text):
2318        self._body += text
2319
2320    def get_content(self):
2321        return self._top() + self._preamble + self._body + self._bottom()
2322
2323    def _top(self):
2324        return ''
2325
2326    def _bottom(self):
2327        return ''
2328
2329    def write(self, output_dir):
2330        pathname = os.path.join(output_dir, self.fname)
2331        dir = os.path.dirname(pathname)
2332        if dir:
2333            try:
2334                os.makedirs(dir)
2335            except os.error as e:
2336                if e.errno != errno.EEXIST:
2337                    raise
2338        fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
2339        if sys.version_info[0] >= 3:
2340            f = open(fd, 'r+', encoding='utf-8')
2341        else:
2342            f = os.fdopen(fd, 'r+')
2343        text = self.get_content()
2344        oldtext = f.read(len(text) + 1)
2345        if text != oldtext:
2346            f.seek(0)
2347            f.truncate(0)
2348            f.write(text)
2349        f.close()
2350
2351
2352@contextmanager
2353def ifcontext(ifcond, *args):
2354    """A 'with' statement context manager to wrap with start_if()/end_if()
2355
2356    *args: any number of QAPIGenCCode
2357
2358    Example::
2359
2360        with ifcontext(ifcond, self._genh, self._genc):
2361            modify self._genh and self._genc ...
2362
2363    Is equivalent to calling::
2364
2365        self._genh.start_if(ifcond)
2366        self._genc.start_if(ifcond)
2367        modify self._genh and self._genc ...
2368        self._genh.end_if()
2369        self._genc.end_if()
2370    """
2371    for arg in args:
2372        arg.start_if(ifcond)
2373    yield
2374    for arg in args:
2375        arg.end_if()
2376
2377
2378class QAPIGenCCode(QAPIGen):
2379
2380    def __init__(self, fname):
2381        QAPIGen.__init__(self, fname)
2382        self._start_if = None
2383
2384    def start_if(self, ifcond):
2385        assert self._start_if is None
2386        self._start_if = (ifcond, self._body, self._preamble)
2387
2388    def end_if(self):
2389        assert self._start_if
2390        self._wrap_ifcond()
2391        self._start_if = None
2392
2393    def _wrap_ifcond(self):
2394        self._body = _wrap_ifcond(self._start_if[0],
2395                                  self._start_if[1], self._body)
2396        self._preamble = _wrap_ifcond(self._start_if[0],
2397                                      self._start_if[2], self._preamble)
2398
2399    def get_content(self):
2400        assert self._start_if is None
2401        return QAPIGen.get_content(self)
2402
2403
2404class QAPIGenC(QAPIGenCCode):
2405
2406    def __init__(self, fname, blurb, pydoc):
2407        QAPIGenCCode.__init__(self, fname)
2408        self._blurb = blurb
2409        self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
2410                                                  re.MULTILINE))
2411
2412    def _top(self):
2413        return mcgen('''
2414/* AUTOMATICALLY GENERATED, DO NOT MODIFY */
2415
2416/*
2417%(blurb)s
2418 *
2419 * %(copyright)s
2420 *
2421 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
2422 * See the COPYING.LIB file in the top-level directory.
2423 */
2424
2425''',
2426                     blurb=self._blurb, copyright=self._copyright)
2427
2428    def _bottom(self):
2429        return mcgen('''
2430
2431/* Dummy declaration to prevent empty .o file */
2432char qapi_dummy_%(name)s;
2433''',
2434                     name=c_fname(self.fname))
2435
2436
2437class QAPIGenH(QAPIGenC):
2438
2439    def _top(self):
2440        return QAPIGenC._top(self) + guardstart(self.fname)
2441
2442    def _bottom(self):
2443        return guardend(self.fname)
2444
2445
2446class QAPIGenDoc(QAPIGen):
2447
2448    def _top(self):
2449        return (QAPIGen._top(self)
2450                + '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n')
2451
2452
2453class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
2454
2455    def __init__(self, prefix, what, blurb, pydoc):
2456        self._prefix = prefix
2457        self._what = what
2458        self._genc = QAPIGenC(self._prefix + self._what + '.c',
2459                              blurb, pydoc)
2460        self._genh = QAPIGenH(self._prefix + self._what + '.h',
2461                              blurb, pydoc)
2462
2463    def write(self, output_dir):
2464        self._genc.write(output_dir)
2465        self._genh.write(output_dir)
2466
2467
2468class QAPISchemaModularCVisitor(QAPISchemaVisitor):
2469
2470    def __init__(self, prefix, what, blurb, pydoc):
2471        self._prefix = prefix
2472        self._what = what
2473        self._blurb = blurb
2474        self._pydoc = pydoc
2475        self._genc = None
2476        self._genh = None
2477        self._module = {}
2478        self._main_module = None
2479
2480    @staticmethod
2481    def _is_user_module(name):
2482        return name and not name.startswith('./')
2483
2484    @staticmethod
2485    def _is_builtin_module(name):
2486        return not name
2487
2488    def _module_dirname(self, what, name):
2489        if self._is_user_module(name):
2490            return os.path.dirname(name)
2491        return ''
2492
2493    def _module_basename(self, what, name):
2494        ret = '' if self._is_builtin_module(name) else self._prefix
2495        if self._is_user_module(name):
2496            basename = os.path.basename(name)
2497            ret += what
2498            if name != self._main_module:
2499                ret += '-' + os.path.splitext(basename)[0]
2500        else:
2501            name = name[2:] if name else 'builtin'
2502            ret += re.sub(r'-', '-' + name + '-', what)
2503        return ret
2504
2505    def _module_filename(self, what, name):
2506        return os.path.join(self._module_dirname(what, name),
2507                            self._module_basename(what, name))
2508
2509    def _add_module(self, name, blurb):
2510        basename = self._module_filename(self._what, name)
2511        genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
2512        genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
2513        self._module[name] = (genc, genh)
2514        self._set_module(name)
2515
2516    def _add_user_module(self, name, blurb):
2517        assert self._is_user_module(name)
2518        if self._main_module is None:
2519            self._main_module = name
2520        self._add_module(name, blurb)
2521
2522    def _add_system_module(self, name, blurb):
2523        self._add_module(name and './' + name, blurb)
2524
2525    def _set_module(self, name):
2526        self._genc, self._genh = self._module[name]
2527
2528    def write(self, output_dir, opt_builtins=False):
2529        for name in self._module:
2530            if self._is_builtin_module(name) and not opt_builtins:
2531                continue
2532            (genc, genh) = self._module[name]
2533            genc.write(output_dir)
2534            genh.write(output_dir)
2535
2536    def _begin_user_module(self, name):
2537        pass
2538
2539    def visit_module(self, name):
2540        if name in self._module:
2541            self._set_module(name)
2542        elif self._is_builtin_module(name):
2543            # The built-in module has not been created.  No code may
2544            # be generated.
2545            self._genc = None
2546            self._genh = None
2547        else:
2548            self._add_user_module(name, self._blurb)
2549            self._begin_user_module(name)
2550
2551    def visit_include(self, name, info):
2552        relname = os.path.relpath(self._module_filename(self._what, name),
2553                                  os.path.dirname(self._genh.fname))
2554        self._genh.preamble_add(mcgen('''
2555#include "%(relname)s.h"
2556''',
2557                                      relname=relname))
2558