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