qapi.py 68.4 KB
Newer Older
1 2 3 4
#
# QAPI helper library
#
# Copyright IBM, Corp. 2011
5
# Copyright (c) 2013-2016 Red Hat Inc.
6 7 8
#
# Authors:
#  Anthony Liguori <aliguori@us.ibm.com>
9
#  Markus Armbruster <armbru@redhat.com>
10
#
11 12
# This work is licensed under the terms of the GNU GPL, version 2.
# See the COPYING file in the top-level directory.
13

14
import re
15
from ordereddict import OrderedDict
16
import errno
17
import getopt
18
import os
19
import sys
20
import string
21

22
builtin_types = {
K
Kevin Wolf 已提交
23 24 25 26 27 28 29 30 31 32 33 34
    'str':      'QTYPE_QSTRING',
    'int':      'QTYPE_QINT',
    'number':   'QTYPE_QFLOAT',
    'bool':     'QTYPE_QBOOL',
    'int8':     'QTYPE_QINT',
    'int16':    'QTYPE_QINT',
    'int32':    'QTYPE_QINT',
    'int64':    'QTYPE_QINT',
    'uint8':    'QTYPE_QINT',
    'uint16':   'QTYPE_QINT',
    'uint32':   'QTYPE_QINT',
    'uint64':   'QTYPE_QINT',
35
    'size':     'QTYPE_QINT',
E
Eric Blake 已提交
36
    'any':      None,           # any QType possible, actually
37
    'QType':    'QTYPE_QSTRING',
K
Kevin Wolf 已提交
38 39
}

40 41 42 43
# Whitelist of commands allowed to return a non-dictionary
returns_whitelist = [
    # From QMP:
    'human-monitor-command',
44
    'qom-get',
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
    'query-migrate-cache-size',
    'query-tpm-models',
    'query-tpm-types',
    'ringbuf-read',

    # From QGA:
    'guest-file-open',
    'guest-fsfreeze-freeze',
    'guest-fsfreeze-freeze-list',
    'guest-fsfreeze-status',
    'guest-fsfreeze-thaw',
    'guest-get-time',
    'guest-set-vcpus',
    'guest-sync',
    'guest-sync-delimited',
]

62 63 64 65 66 67 68 69 70
# Whitelist of entities allowed to violate case conventions
case_whitelist = [
    # From QMP:
    'ACPISlotType',         # DIMM, visible through query-acpi-ospm-status
    'CpuInfoMIPS',          # PC, visible through query-cpu
    'CpuInfoTricore',       # PC, visible through query-cpu
    'QapiErrorClass',       # all members, visible through errors
    'UuidInfo',             # UUID, visible through query-uuid
    'X86CPURegister32',     # all members, visible indirectly through qom-get
71
    'q_obj_CpuInfo-base',   # CPU, visible through query-cpu
72 73
]

74 75 76 77 78 79
enum_types = []
struct_types = []
union_types = []
events = []
all_names = {}

80 81 82 83
#
# Parsing the schema into expressions
#

E
Eric Blake 已提交
84

85 86 87 88 89 90 91 92
def error_path(parent):
    res = ""
    while parent:
        res = ("In file included from %s:%d:\n" % (parent['file'],
                                                   parent['line'])) + res
        parent = parent['parent']
    return res

E
Eric Blake 已提交
93

M
Marc-André Lureau 已提交
94 95
class QAPIError(Exception):
    def __init__(self, fname, line, col, incl_info, msg):
96
        Exception.__init__(self)
M
Marc-André Lureau 已提交
97 98 99 100
        self.fname = fname
        self.line = line
        self.col = col
        self.info = incl_info
101 102 103
        self.msg = msg

    def __str__(self):
M
Marc-André Lureau 已提交
104 105 106 107
        loc = "%s:%d" % (self.fname, self.line)
        if self.col is not None:
            loc += ":%s" % self.col
        return error_path(self.info) + "%s: %s" % (loc, self.msg)
108

E
Eric Blake 已提交
109

M
Marc-André Lureau 已提交
110 111 112 113 114 115 116 117 118 119
class QAPIParseError(QAPIError):
    def __init__(self, parser, msg):
        col = 1
        for ch in parser.src[parser.line_pos:parser.pos]:
            if ch == '\t':
                col = (col + 7) % 8 + 1
            else:
                col += 1
        QAPIError.__init__(self, parser.fname, parser.line, col,
                           parser.incl_info, msg)
120

M
Marc-André Lureau 已提交
121 122 123 124 125

class QAPISemError(QAPIError):
    def __init__(self, info, msg):
        QAPIError.__init__(self, info['file'], info['line'], None,
                           info['parent'], msg)
126

E
Eric Blake 已提交
127

M
Marc-André Lureau 已提交
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
class QAPIDoc(object):
    class Section(object):
        def __init__(self, name=None):
            # optional section name (argument/member or section name)
            self.name = name
            # the list of lines for this section
            self.content = []

        def append(self, line):
            self.content.append(line)

        def __repr__(self):
            return "\n".join(self.content).strip()

    class ArgSection(Section):
        pass

    def __init__(self, parser, info):
        # self.parser is used to report errors with QAPIParseError.  The
        # resulting error position depends on the state of the parser.
        # It happens to be the beginning of the comment.  More or less
        # servicable, but action at a distance.
        self.parser = parser
        self.info = info
        self.symbol = None
        self.body = QAPIDoc.Section()
        # dict mapping parameter name to ArgSection
        self.args = OrderedDict()
        # a list of Section
        self.sections = []
        # the current section
        self.section = self.body
        # associated expression (to be set by expression parser)
        self.expr = None

    def has_section(self, name):
        """Return True if we have a section with this name."""
        for i in self.sections:
            if i.name == name:
                return True
        return False

    def append(self, line):
        """Parse a comment line and add it to the documentation."""
        line = line[1:]
        if not line:
            self._append_freeform(line)
            return

        if line[0] != ' ':
            raise QAPIParseError(self.parser, "Missing space after #")
        line = line[1:]

        # FIXME not nice: things like '#  @foo:' and '# @foo: ' aren't
        # recognized, and get silently treated as ordinary text
        if self.symbol:
            self._append_symbol_line(line)
        elif not self.body.content and line.startswith("@"):
            if not line.endswith(":"):
                raise QAPIParseError(self.parser, "Line should end with :")
            self.symbol = line[1:-1]
            # FIXME invalid names other than the empty string aren't flagged
            if not self.symbol:
                raise QAPIParseError(self.parser, "Invalid name")
        else:
            self._append_freeform(line)

    def _append_symbol_line(self, line):
        name = line.split(' ', 1)[0]

        if name.startswith("@") and name.endswith(":"):
            line = line[len(name)+1:]
            self._start_args_section(name[1:-1])
        elif name in ("Returns:", "Since:",
                      # those are often singular or plural
                      "Note:", "Notes:",
                      "Example:", "Examples:",
                      "TODO:"):
            line = line[len(name)+1:]
            self._start_section(name[:-1])

        self._append_freeform(line)

    def _start_args_section(self, name):
        # FIXME invalid names other than the empty string aren't flagged
        if not name:
            raise QAPIParseError(self.parser, "Invalid parameter name")
        if name in self.args:
            raise QAPIParseError(self.parser,
                                 "'%s' parameter name duplicated" % name)
        if self.sections:
            raise QAPIParseError(self.parser,
                                 "'@%s:' can't follow '%s' section"
                                 % (name, self.sections[0].name))
        self.section = QAPIDoc.ArgSection(name)
        self.args[name] = self.section

    def _start_section(self, name=""):
        if name in ("Returns", "Since") and self.has_section(name):
            raise QAPIParseError(self.parser,
                                 "Duplicated '%s' section" % name)
        self.section = QAPIDoc.Section(name)
        self.sections.append(self.section)

    def _append_freeform(self, line):
        in_arg = isinstance(self.section, QAPIDoc.ArgSection)
        if (in_arg and self.section.content
                and not self.section.content[-1]
                and line and not line[0].isspace()):
            self._start_section()
        if (in_arg or not self.section.name
                or not self.section.name.startswith("Example")):
            line = line.strip()
        self.section.append(line)


244
class QAPISchemaParser(object):
245

E
Eric Blake 已提交
246
    def __init__(self, fp, previously_included=[], incl_info=None):
247
        abs_fname = os.path.abspath(fp.name)
248
        fname = fp.name
249 250 251
        self.fname = fname
        previously_included.append(abs_fname)
        self.incl_info = incl_info
252 253 254 255
        self.src = fp.read()
        if self.src == '' or self.src[-1] != '\n':
            self.src += '\n'
        self.cursor = 0
256 257
        self.line = 1
        self.line_pos = 0
258
        self.exprs = []
M
Marc-André Lureau 已提交
259
        self.docs = []
260 261
        self.accept()

E
Eric Blake 已提交
262
        while self.tok is not None:
M
Marc-André Lureau 已提交
263 264
            info = {'file': fname, 'line': self.line,
                    'parent': self.incl_info}
M
Marc-André Lureau 已提交
265 266 267 268 269
            if self.tok == '#':
                doc = self.get_doc(info)
                self.docs.append(doc)
                continue

270
            expr = self.get_expr(False)
271
            if 'include' in expr:
272
                if len(expr) != 1:
M
Marc-André Lureau 已提交
273
                    raise QAPISemError(info, "Invalid 'include' directive")
274 275
                include = expr["include"]
                if not isinstance(include, str):
M
Marc-André Lureau 已提交
276 277
                    raise QAPISemError(info,
                                       "Value of 'include' must be a string")
278 279
                self._include(include, info, os.path.dirname(abs_fname),
                              previously_included)
280 281
            else:
                expr_elem = {'expr': expr,
M
Marc-André Lureau 已提交
282
                             'info': info}
M
Marc-André Lureau 已提交
283 284 285 286 287 288
                if (self.docs
                        and self.docs[-1].info['file'] == fname
                        and not self.docs[-1].expr):
                    self.docs[-1].expr = expr
                    expr_elem['doc'] = self.docs[-1]

289
                self.exprs.append(expr_elem)
290

291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
    def _include(self, include, info, base_dir, previously_included):
        incl_abs_fname = os.path.join(base_dir, include)
        # catch inclusion cycle
        inf = info
        while inf:
            if incl_abs_fname == os.path.abspath(inf['file']):
                raise QAPISemError(info, "Inclusion loop for %s" % include)
            inf = inf['parent']

        # skip multiple include of the same file
        if incl_abs_fname in previously_included:
            return
        try:
            fobj = open(incl_abs_fname, 'r')
        except IOError as e:
            raise QAPISemError(info, '%s: %s' % (e.strerror, include))
        exprs_include = QAPISchemaParser(fobj, previously_included, info)
        self.exprs.extend(exprs_include.exprs)
        self.docs.extend(exprs_include.docs)

M
Marc-André Lureau 已提交
311
    def accept(self, skip_comment=True):
312 313
        while True:
            self.tok = self.src[self.cursor]
314
            self.pos = self.cursor
315 316 317
            self.cursor += 1
            self.val = None

318
            if self.tok == '#':
M
Marc-André Lureau 已提交
319 320 321
                if self.src[self.cursor] == '#':
                    # Start of doc comment
                    skip_comment = False
322
                self.cursor = self.src.find('\n', self.cursor)
M
Marc-André Lureau 已提交
323 324 325
                if not skip_comment:
                    self.val = self.src[self.pos:self.cursor]
                    return
326
            elif self.tok in "{}:,[]":
327 328 329 330 331 332 333 334
                return
            elif self.tok == "'":
                string = ''
                esc = False
                while True:
                    ch = self.src[self.cursor]
                    self.cursor += 1
                    if ch == '\n':
M
Marc-André Lureau 已提交
335
                        raise QAPIParseError(self, 'Missing terminating "\'"')
336
                    if esc:
337 338 339 340 341 342 343 344 345 346 347 348
                        if ch == 'b':
                            string += '\b'
                        elif ch == 'f':
                            string += '\f'
                        elif ch == 'n':
                            string += '\n'
                        elif ch == 'r':
                            string += '\r'
                        elif ch == 't':
                            string += '\t'
                        elif ch == 'u':
                            value = 0
E
Eric Blake 已提交
349
                            for _ in range(0, 4):
350 351 352
                                ch = self.src[self.cursor]
                                self.cursor += 1
                                if ch not in "0123456789abcdefABCDEF":
M
Marc-André Lureau 已提交
353 354 355
                                    raise QAPIParseError(self,
                                                         '\\u escape needs 4 '
                                                         'hex digits')
356 357 358 359 360 361
                                value = (value << 4) + int(ch, 16)
                            # If Python 2 and 3 didn't disagree so much on
                            # how to handle Unicode, then we could allow
                            # Unicode string defaults.  But most of QAPI is
                            # ASCII-only, so we aren't losing much for now.
                            if not value or value > 0x7f:
M
Marc-André Lureau 已提交
362 363 364 365
                                raise QAPIParseError(self,
                                                     'For now, \\u escape '
                                                     'only supports non-zero '
                                                     'values up to \\u007f')
366 367 368 369
                            string += chr(value)
                        elif ch in "\\/'\"":
                            string += ch
                        else:
M
Marc-André Lureau 已提交
370 371
                            raise QAPIParseError(self,
                                                 "Unknown escape \\%s" % ch)
372 373 374 375 376 377 378 379
                        esc = False
                    elif ch == "\\":
                        esc = True
                    elif ch == "'":
                        self.val = string
                        return
                    else:
                        string += ch
380 381 382 383 384 385 386 387 388 389 390 391
            elif self.src.startswith("true", self.pos):
                self.val = True
                self.cursor += 3
                return
            elif self.src.startswith("false", self.pos):
                self.val = False
                self.cursor += 4
                return
            elif self.src.startswith("null", self.pos):
                self.val = None
                self.cursor += 3
                return
392 393 394 395
            elif self.tok == '\n':
                if self.cursor == len(self.src):
                    self.tok = None
                    return
396 397
                self.line += 1
                self.line_pos = self.cursor
398
            elif not self.tok.isspace():
M
Marc-André Lureau 已提交
399
                raise QAPIParseError(self, 'Stray "%s"' % self.tok)
400 401 402

    def get_members(self):
        expr = OrderedDict()
403 404 405 406
        if self.tok == '}':
            self.accept()
            return expr
        if self.tok != "'":
M
Marc-André Lureau 已提交
407
            raise QAPIParseError(self, 'Expected string or "}"')
408
        while True:
409 410
            key = self.val
            self.accept()
411
            if self.tok != ':':
M
Marc-André Lureau 已提交
412
                raise QAPIParseError(self, 'Expected ":"')
413
            self.accept()
414
            if key in expr:
M
Marc-André Lureau 已提交
415
                raise QAPIParseError(self, 'Duplicate key "%s"' % key)
416
            expr[key] = self.get_expr(True)
417
            if self.tok == '}':
418
                self.accept()
419 420
                return expr
            if self.tok != ',':
M
Marc-André Lureau 已提交
421
                raise QAPIParseError(self, 'Expected "," or "}"')
422 423
            self.accept()
            if self.tok != "'":
M
Marc-André Lureau 已提交
424
                raise QAPIParseError(self, 'Expected string')
425 426 427

    def get_values(self):
        expr = []
428 429 430
        if self.tok == ']':
            self.accept()
            return expr
E
Eric Blake 已提交
431
        if self.tok not in "{['tfn":
M
Marc-André Lureau 已提交
432 433
            raise QAPIParseError(self, 'Expected "{", "[", "]", string, '
                                 'boolean or "null"')
434
        while True:
435
            expr.append(self.get_expr(True))
436
            if self.tok == ']':
437
                self.accept()
438 439
                return expr
            if self.tok != ',':
M
Marc-André Lureau 已提交
440
                raise QAPIParseError(self, 'Expected "," or "]"')
441
            self.accept()
442

443 444
    def get_expr(self, nested):
        if self.tok != '{' and not nested:
M
Marc-André Lureau 已提交
445
            raise QAPIParseError(self, 'Expected "{"')
446 447 448 449 450 451
        if self.tok == '{':
            self.accept()
            expr = self.get_members()
        elif self.tok == '[':
            self.accept()
            expr = self.get_values()
452
        elif self.tok in "'tfn":
453 454
            expr = self.val
            self.accept()
455
        else:
M
Marc-André Lureau 已提交
456
            raise QAPIParseError(self, 'Expected "{", "[" or string')
457
        return expr
K
Kevin Wolf 已提交
458

M
Marc-André Lureau 已提交
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
    def get_doc(self, info):
        if self.val != '##':
            raise QAPIParseError(self, "Junk after '##' at start of "
                                 "documentation comment")

        doc = QAPIDoc(self, info)
        self.accept(False)
        while self.tok == '#':
            if self.val.startswith('##'):
                # End of doc comment
                if self.val != '##':
                    raise QAPIParseError(self, "Junk after '##' at end of "
                                         "documentation comment")
                self.accept()
                return doc
            else:
                doc.append(self.val)
            self.accept(False)

        raise QAPIParseError(self, "Documentation comment must end with '##'")


481 482
#
# Semantic analysis of schema expressions
483 484
# TODO fold into QAPISchema
# TODO catching name collisions in generated code would be nice
485 486
#

E
Eric Blake 已提交
487

488
def find_base_members(base):
489 490
    if isinstance(base, dict):
        return base
491 492 493 494 495
    base_struct_define = find_struct(base)
    if not base_struct_define:
        return None
    return base_struct_define['data']

E
Eric Blake 已提交
496

497 498
# Return the qtype of an alternate branch, or None on error.
def find_alternate_member_qtype(qapi_type):
E
Eric Blake 已提交
499
    if qapi_type in builtin_types:
E
Eric Blake 已提交
500 501 502 503 504
        return builtin_types[qapi_type]
    elif find_struct(qapi_type):
        return "QTYPE_QDICT"
    elif find_enum(qapi_type):
        return "QTYPE_QSTRING"
505 506
    elif find_union(qapi_type):
        return "QTYPE_QDICT"
E
Eric Blake 已提交
507 508
    return None

E
Eric Blake 已提交
509

510 511 512 513 514 515 516 517 518
# Return the discriminator enum define if discriminator is specified as an
# enum type, otherwise return None.
def discriminator_find_enum_define(expr):
    base = expr.get('base')
    discriminator = expr.get('discriminator')

    if not (discriminator and base):
        return None

519 520
    base_members = find_base_members(base)
    if not base_members:
521 522
        return None

523
    discriminator_type = base_members.get(discriminator)
524 525 526 527 528
    if not discriminator_type:
        return None

    return find_enum(discriminator_type)

E
Eric Blake 已提交
529

530 531 532 533 534
# Names must be letters, numbers, -, and _.  They must start with letter,
# except for downstream extensions which must start with __RFQDN_.
# Dots are only valid in the downstream extension prefix.
valid_name = re.compile('^(__[a-zA-Z0-9.-]+_)?'
                        '[a-zA-Z][a-zA-Z0-9_-]*$')
E
Eric Blake 已提交
535 536


M
Marc-André Lureau 已提交
537
def check_name(info, source, name, allow_optional=False,
E
Eric Blake 已提交
538
               enum_member=False):
E
Eric Blake 已提交
539 540 541 542
    global valid_name
    membername = name

    if not isinstance(name, str):
M
Marc-André Lureau 已提交
543
        raise QAPISemError(info, "%s requires a string name" % source)
E
Eric Blake 已提交
544 545 546
    if name.startswith('*'):
        membername = name[1:]
        if not allow_optional:
M
Marc-André Lureau 已提交
547 548
            raise QAPISemError(info, "%s does not allow optional name '%s'"
                               % (source, name))
E
Eric Blake 已提交
549 550
    # Enum members can start with a digit, because the generated C
    # code always prefixes it with the enum name
551 552
    if enum_member and membername[0].isdigit():
        membername = 'D' + membername
553 554
    # Reserve the entire 'q_' namespace for c_name(), and for 'q_empty'
    # and 'q_obj_*' implicit type names.
555 556
    if not valid_name.match(membername) or \
       c_name(membername, False).startswith('q_'):
M
Marc-André Lureau 已提交
557
        raise QAPISemError(info, "%s uses invalid name '%s'" % (source, name))
E
Eric Blake 已提交
558

E
Eric Blake 已提交
559 560

def add_name(name, info, meta, implicit=False):
561 562
    global all_names
    check_name(info, "'%s'" % meta, name)
563 564
    # FIXME should reject names that differ only in '_' vs. '.'
    # vs. '-', because they're liable to clash in generated C.
565
    if name in all_names:
M
Marc-André Lureau 已提交
566 567
        raise QAPISemError(info, "%s '%s' is already defined"
                           % (all_names[name], name))
568
    if not implicit and (name.endswith('Kind') or name.endswith('List')):
M
Marc-André Lureau 已提交
569 570
        raise QAPISemError(info, "%s '%s' should not end in '%s'"
                           % (meta, name, name[-4:]))
571 572
    all_names[name] = meta

E
Eric Blake 已提交
573

574 575 576 577 578 579
def add_struct(definition, info):
    global struct_types
    name = definition['struct']
    add_name(name, info, 'struct')
    struct_types.append(definition)

E
Eric Blake 已提交
580

581 582 583 584 585 586 587
def find_struct(name):
    global struct_types
    for struct in struct_types:
        if struct['struct'] == name:
            return struct
    return None

E
Eric Blake 已提交
588

589 590 591 592 593 594
def add_union(definition, info):
    global union_types
    name = definition['union']
    add_name(name, info, 'union')
    union_types.append(definition)

E
Eric Blake 已提交
595

596 597 598 599 600 601 602
def find_union(name):
    global union_types
    for union in union_types:
        if union['union'] == name:
            return union
    return None

E
Eric Blake 已提交
603 604

def add_enum(name, info, enum_values=None, implicit=False):
605 606 607 608
    global enum_types
    add_name(name, info, 'enum', implicit)
    enum_types.append({"enum_name": name, "enum_values": enum_values})

E
Eric Blake 已提交
609

610 611 612 613 614 615 616
def find_enum(name):
    global enum_types
    for enum in enum_types:
        if enum['enum_name'] == name:
            return enum
    return None

E
Eric Blake 已提交
617

618
def is_enum(name):
E
Eric Blake 已提交
619 620
    return find_enum(name) is not None

621

M
Marc-André Lureau 已提交
622
def check_type(info, source, value, allow_array=False,
E
Eric Blake 已提交
623 624
               allow_dict=False, allow_optional=False,
               allow_metas=[]):
625 626 627 628 629 630 631 632
    global all_names

    if value is None:
        return

    # Check if array type for value is okay
    if isinstance(value, list):
        if not allow_array:
M
Marc-André Lureau 已提交
633
            raise QAPISemError(info, "%s cannot be an array" % source)
634
        if len(value) != 1 or not isinstance(value[0], str):
M
Marc-André Lureau 已提交
635 636 637
            raise QAPISemError(info,
                               "%s: array type must contain single type name" %
                               source)
638 639 640 641
        value = value[0]

    # Check if type name for value is okay
    if isinstance(value, str):
E
Eric Blake 已提交
642
        if value not in all_names:
M
Marc-André Lureau 已提交
643 644
            raise QAPISemError(info, "%s uses unknown type '%s'"
                               % (source, value))
645
        if not all_names[value] in allow_metas:
M
Marc-André Lureau 已提交
646 647
            raise QAPISemError(info, "%s cannot use %s type '%s'" %
                               (source, all_names[value], value))
648 649 650
        return

    if not allow_dict:
M
Marc-André Lureau 已提交
651
        raise QAPISemError(info, "%s should be a type name" % source)
652 653

    if not isinstance(value, OrderedDict):
M
Marc-André Lureau 已提交
654 655
        raise QAPISemError(info,
                           "%s should be a dictionary or type name" % source)
656 657

    # value is a dictionary, check that each member is okay
658
    for (key, arg) in value.items():
M
Marc-André Lureau 已提交
659
        check_name(info, "Member of %s" % source, key,
E
Eric Blake 已提交
660
                   allow_optional=allow_optional)
E
Eric Blake 已提交
661
        if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
M
Marc-André Lureau 已提交
662 663
            raise QAPISemError(info, "Member of %s uses reserved name '%s'"
                               % (source, key))
664 665
        # Todo: allow dictionaries to represent default values of
        # an optional argument.
M
Marc-André Lureau 已提交
666
        check_type(info, "Member '%s' of %s" % (key, source), arg,
667
                   allow_array=True,
668
                   allow_metas=['built-in', 'union', 'alternate', 'struct',
669
                                'enum'])
670

E
Eric Blake 已提交
671

M
Marc-André Lureau 已提交
672
def check_command(expr, info):
673
    name = expr['command']
674
    boxed = expr.get('boxed', False)
675

676 677 678
    args_meta = ['struct']
    if boxed:
        args_meta += ['union', 'alternate']
M
Marc-André Lureau 已提交
679
    check_type(info, "'data' for command '%s'" % name,
680 681
               expr.get('data'), allow_dict=not boxed, allow_optional=True,
               allow_metas=args_meta)
682 683 684
    returns_meta = ['union', 'struct']
    if name in returns_whitelist:
        returns_meta += ['built-in', 'alternate', 'enum']
M
Marc-André Lureau 已提交
685
    check_type(info, "'returns' for command '%s'" % name,
686
               expr.get('returns'), allow_array=True,
687
               allow_optional=True, allow_metas=returns_meta)
688

E
Eric Blake 已提交
689

M
Marc-André Lureau 已提交
690
def check_event(expr, info):
691 692
    global events
    name = expr['event']
693
    boxed = expr.get('boxed', False)
694

695 696 697
    meta = ['struct']
    if boxed:
        meta += ['union', 'alternate']
698
    events.append(name)
M
Marc-André Lureau 已提交
699
    check_type(info, "'data' for event '%s'" % name,
700 701
               expr.get('data'), allow_dict=not boxed, allow_optional=True,
               allow_metas=meta)
W
Wenchao Xia 已提交
702

E
Eric Blake 已提交
703

M
Marc-André Lureau 已提交
704
def check_union(expr, info):
705 706 707 708 709
    name = expr['union']
    base = expr.get('base')
    discriminator = expr.get('discriminator')
    members = expr['data']

710 711 712 713
    # Two types of unions, determined by discriminator.

    # With no discriminator it is a simple union.
    if discriminator is None:
714
        enum_define = None
E
Eric Blake 已提交
715
        allow_metas = ['built-in', 'union', 'alternate', 'struct', 'enum']
E
Eric Blake 已提交
716
        if base is not None:
M
Marc-André Lureau 已提交
717 718
            raise QAPISemError(info, "Simple union '%s' must not have a base" %
                               name)
719 720 721

    # Else, it's a flat union.
    else:
722
        # The object must have a string or dictionary 'base'.
M
Marc-André Lureau 已提交
723
        check_type(info, "'base' for union '%s'" % name,
724 725
                   base, allow_dict=True, allow_optional=True,
                   allow_metas=['struct'])
726
        if not base:
M
Marc-André Lureau 已提交
727 728
            raise QAPISemError(info, "Flat union '%s' must have a base"
                               % name)
729 730
        base_members = find_base_members(base)
        assert base_members
E
Eric Blake 已提交
731

E
Eric Blake 已提交
732
        # The value of member 'discriminator' must name a non-optional
733
        # member of the base struct.
M
Marc-André Lureau 已提交
734
        check_name(info, "Discriminator of flat union '%s'" % name,
E
Eric Blake 已提交
735
                   discriminator)
736
        discriminator_type = base_members.get(discriminator)
737
        if not discriminator_type:
M
Marc-André Lureau 已提交
738 739 740 741
            raise QAPISemError(info,
                               "Discriminator '%s' is not a member of base "
                               "struct '%s'"
                               % (discriminator, base))
742
        enum_define = find_enum(discriminator_type)
E
Eric Blake 已提交
743
        allow_metas = ['struct']
744 745
        # Do not allow string discriminator
        if not enum_define:
M
Marc-André Lureau 已提交
746 747 748
            raise QAPISemError(info,
                               "Discriminator '%s' must be of enumeration "
                               "type" % discriminator)
749

750 751
    # Check every branch; don't allow an empty union
    if len(members) == 0:
M
Marc-André Lureau 已提交
752
        raise QAPISemError(info, "Union '%s' cannot have empty 'data'" % name)
753
    for (key, value) in members.items():
M
Marc-André Lureau 已提交
754
        check_name(info, "Member of union '%s'" % name, key)
E
Eric Blake 已提交
755

756
        # Each value must name a known type
M
Marc-André Lureau 已提交
757
        check_type(info, "Member '%s' of union '%s'" % (key, name),
758
                   value, allow_array=not base, allow_metas=allow_metas)
759

E
Eric Blake 已提交
760
        # If the discriminator names an enum type, then all members
761
        # of 'data' must also be members of the enum type.
E
Eric Blake 已提交
762
        if enum_define:
E
Eric Blake 已提交
763
            if key not in enum_define['enum_values']:
M
Marc-André Lureau 已提交
764 765 766 767
                raise QAPISemError(info,
                                   "Discriminator value '%s' is not found in "
                                   "enum '%s'"
                                   % (key, enum_define["enum_name"]))
E
Eric Blake 已提交
768

769 770 771 772
    # If discriminator is user-defined, ensure all values are covered
    if enum_define:
        for value in enum_define['enum_values']:
            if value not in members.keys():
M
Marc-André Lureau 已提交
773 774
                raise QAPISemError(info, "Union '%s' data missing '%s' branch"
                                   % (name, value))
775

E
Eric Blake 已提交
776

M
Marc-André Lureau 已提交
777
def check_alternate(expr, info):
778
    name = expr['alternate']
779 780 781
    members = expr['data']
    types_seen = {}

782 783
    # Check every branch; require at least two branches
    if len(members) < 2:
M
Marc-André Lureau 已提交
784 785 786
        raise QAPISemError(info,
                           "Alternate '%s' should have at least two branches "
                           "in 'data'" % name)
787
    for (key, value) in members.items():
M
Marc-André Lureau 已提交
788
        check_name(info, "Member of alternate '%s'" % name, key)
E
Eric Blake 已提交
789

790
        # Ensure alternates have no type conflicts.
M
Marc-André Lureau 已提交
791
        check_type(info, "Member '%s' of alternate '%s'" % (key, name),
792 793
                   value,
                   allow_metas=['built-in', 'union', 'struct', 'enum'])
794
        qtype = find_alternate_member_qtype(value)
795
        if not qtype:
M
Marc-André Lureau 已提交
796 797
            raise QAPISemError(info, "Alternate '%s' member '%s' cannot use "
                               "type '%s'" % (name, key, value))
798
        if qtype in types_seen:
M
Marc-André Lureau 已提交
799 800 801
            raise QAPISemError(info, "Alternate '%s' member '%s' can't "
                               "be distinguished from member '%s'"
                               % (name, key, types_seen[qtype]))
802
        types_seen[qtype] = key
803

E
Eric Blake 已提交
804

M
Marc-André Lureau 已提交
805
def check_enum(expr, info):
806 807
    name = expr['enum']
    members = expr.get('data')
808
    prefix = expr.get('prefix')
809 810

    if not isinstance(members, list):
M
Marc-André Lureau 已提交
811 812
        raise QAPISemError(info,
                           "Enum '%s' requires an array for 'data'" % name)
813
    if prefix is not None and not isinstance(prefix, str):
M
Marc-André Lureau 已提交
814 815
        raise QAPISemError(info,
                           "Enum '%s' requires a string for 'prefix'" % name)
816
    for member in members:
M
Marc-André Lureau 已提交
817
        check_name(info, "Member of enum '%s'" % name, member,
E
Eric Blake 已提交
818
                   enum_member=True)
819

E
Eric Blake 已提交
820

M
Marc-André Lureau 已提交
821
def check_struct(expr, info):
822
    name = expr['struct']
823 824
    members = expr['data']

M
Marc-André Lureau 已提交
825
    check_type(info, "'data' for struct '%s'" % name, members,
E
Eric Blake 已提交
826
               allow_dict=True, allow_optional=True)
M
Marc-André Lureau 已提交
827
    check_type(info, "'base' for struct '%s'" % name, expr.get('base'),
828 829
               allow_metas=['struct'])

E
Eric Blake 已提交
830

831 832 833 834 835
def check_keys(expr_elem, meta, required, optional=[]):
    expr = expr_elem['expr']
    info = expr_elem['info']
    name = expr[meta]
    if not isinstance(name, str):
M
Marc-André Lureau 已提交
836
        raise QAPISemError(info, "'%s' key must have a string value" % meta)
E
Eric Blake 已提交
837
    required = required + [meta]
838
    for (key, value) in expr.items():
E
Eric Blake 已提交
839
        if key not in required and key not in optional:
M
Marc-André Lureau 已提交
840 841
            raise QAPISemError(info, "Unknown key '%s' in %s '%s'"
                               % (key, meta, name))
E
Eric Blake 已提交
842
        if (key == 'gen' or key == 'success-response') and value is not False:
M
Marc-André Lureau 已提交
843 844 845
            raise QAPISemError(info,
                               "'%s' of %s '%s' should only use false value"
                               % (key, meta, name))
846
        if key == 'boxed' and value is not True:
M
Marc-André Lureau 已提交
847 848 849
            raise QAPISemError(info,
                               "'%s' of %s '%s' should only use true value"
                               % (key, meta, name))
850
    for key in required:
E
Eric Blake 已提交
851
        if key not in expr:
M
Marc-André Lureau 已提交
852 853
            raise QAPISemError(info, "Key '%s' is missing from %s '%s'"
                               % (key, meta, name))
854

E
Eric Blake 已提交
855

856
def check_exprs(exprs):
857 858
    global all_names

859 860 861 862 863 864
    # Learn the types and check for valid expression keys
    for builtin in builtin_types.keys():
        all_names[builtin] = 'built-in'
    for expr_elem in exprs:
        expr = expr_elem['expr']
        info = expr_elem['info']
M
Marc-André Lureau 已提交
865 866 867 868 869

        if 'doc' not in expr_elem:
            raise QAPISemError(info,
                               "Expression missing documentation comment")

E
Eric Blake 已提交
870
        if 'enum' in expr:
871
            check_keys(expr_elem, 'enum', ['data'], ['prefix'])
872
            add_enum(expr['enum'], info, expr['data'])
E
Eric Blake 已提交
873
        elif 'union' in expr:
874 875 876
            check_keys(expr_elem, 'union', ['data'],
                       ['base', 'discriminator'])
            add_union(expr, info)
E
Eric Blake 已提交
877
        elif 'alternate' in expr:
878 879
            check_keys(expr_elem, 'alternate', ['data'])
            add_name(expr['alternate'], info, 'alternate')
E
Eric Blake 已提交
880
        elif 'struct' in expr:
881 882
            check_keys(expr_elem, 'struct', ['data'], ['base'])
            add_struct(expr, info)
E
Eric Blake 已提交
883
        elif 'command' in expr:
884
            check_keys(expr_elem, 'command', [],
885
                       ['data', 'returns', 'gen', 'success-response', 'boxed'])
886
            add_name(expr['command'], info, 'command')
E
Eric Blake 已提交
887
        elif 'event' in expr:
888
            check_keys(expr_elem, 'event', [], ['data', 'boxed'])
889 890
            add_name(expr['event'], info, 'event')
        else:
M
Marc-André Lureau 已提交
891 892
            raise QAPISemError(expr_elem['info'],
                               "Expression is missing metatype")
893

894 895 896
    # Try again for hidden UnionKind enum
    for expr_elem in exprs:
        expr = expr_elem['expr']
E
Eric Blake 已提交
897
        if 'union' in expr:
898 899
            if not discriminator_find_enum_define(expr):
                add_enum('%sKind' % expr['union'], expr_elem['info'],
900
                         implicit=True)
E
Eric Blake 已提交
901
        elif 'alternate' in expr:
902 903 904 905 906 907 908
            add_enum('%sKind' % expr['alternate'], expr_elem['info'],
                     implicit=True)

    # Validate that exprs make sense
    for expr_elem in exprs:
        expr = expr_elem['expr']
        info = expr_elem['info']
909

E
Eric Blake 已提交
910
        if 'enum' in expr:
911
            check_enum(expr, info)
E
Eric Blake 已提交
912
        elif 'union' in expr:
913
            check_union(expr, info)
E
Eric Blake 已提交
914
        elif 'alternate' in expr:
915
            check_alternate(expr, info)
E
Eric Blake 已提交
916
        elif 'struct' in expr:
917
            check_struct(expr, info)
E
Eric Blake 已提交
918
        elif 'command' in expr:
919
            check_command(expr, info)
E
Eric Blake 已提交
920
        elif 'event' in expr:
921 922 923 924
            check_event(expr, info)
        else:
            assert False, 'unexpected meta type'

925 926 927
    return exprs


M
Marc-André Lureau 已提交
928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
def check_freeform_doc(doc):
    if doc.symbol:
        raise QAPISemError(doc.info,
                           "Documention for '%s' is not followed"
                           " by the definition" % doc.symbol)

    body = str(doc.body)
    if re.search(r'@\S+:', body, re.MULTILINE):
        raise QAPISemError(doc.info,
                           "Free-form documentation block must not contain"
                           " @NAME: sections")


def check_definition_doc(doc, expr, info):
    for i in ('enum', 'union', 'alternate', 'struct', 'command', 'event'):
        if i in expr:
            meta = i
            break

    name = expr[meta]
    if doc.symbol != name:
        raise QAPISemError(info, "Definition of '%s' follows documentation"
                           " for '%s'" % (name, doc.symbol))
    if doc.has_section('Returns') and 'command' not in expr:
        raise QAPISemError(info, "'Returns:' is only valid for commands")

    if meta == 'union':
        args = expr.get('base', [])
    else:
        args = expr.get('data', [])
    if isinstance(args, str):
        return
    if isinstance(args, dict):
        args = args.keys()
    assert isinstance(args, list)

    if (meta == 'alternate'
            or (meta == 'union' and not expr.get('discriminator'))):
        args.append('type')

    for arg in args:
        if arg[0] == '*':
            opt = True
            desc = doc.args.get(arg[1:])
        else:
            opt = False
            desc = doc.args.get(arg)
        if not desc:
            continue
        desc_opt = "#optional" in str(desc)
        if desc_opt and not opt:
            raise QAPISemError(info, "Description has #optional, "
                               "but the declaration doesn't")
        if not desc_opt and opt:
            # silently fix the doc
            # TODO either fix the schema and make this an error,
            # or drop #optional entirely
            desc.append("#optional")

    doc_args = set(doc.args.keys())
    args = set([name.strip('*') for name in args])
    if not doc_args.issubset(args):
        raise QAPISemError(info, "The following documented members are not in "
                           "the declaration: %s" % ", ".join(doc_args - args))


def check_docs(docs):
    for doc in docs:
        for section in doc.args.values() + doc.sections:
            content = str(section)
            if not content or content.isspace():
                raise QAPISemError(doc.info,
                                   "Empty doc section '%s'" % section.name)

        if not doc.expr:
            check_freeform_doc(doc)
        else:
            check_definition_doc(doc, doc.expr, doc.info)

    return docs


1010 1011 1012 1013 1014 1015 1016 1017
#
# Schema compiler frontend
#

class QAPISchemaEntity(object):
    def __init__(self, name, info):
        assert isinstance(name, str)
        self.name = name
1018 1019 1020 1021 1022
        # For explicitly defined entities, info points to the (explicit)
        # definition.  For builtins (and their arrays), info is None.
        # For implicitly defined entities, info points to a place that
        # triggered the implicit definition (there may be more than one
        # such place).
1023 1024
        self.info = info

1025 1026 1027
    def c_name(self):
        return c_name(self.name)

1028 1029 1030
    def check(self, schema):
        pass

1031 1032 1033
    def is_implicit(self):
        return not self.info

M
Markus Armbruster 已提交
1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044
    def visit(self, visitor):
        pass


class QAPISchemaVisitor(object):
    def visit_begin(self, schema):
        pass

    def visit_end(self):
        pass

1045 1046 1047 1048
    def visit_needed(self, entity):
        # Default to visiting everything
        return True

M
Markus Armbruster 已提交
1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060
    def visit_builtin_type(self, name, info, json_type):
        pass

    def visit_enum_type(self, name, info, values, prefix):
        pass

    def visit_array_type(self, name, info, element_type):
        pass

    def visit_object_type(self, name, info, base, members, variants):
        pass

1061 1062 1063
    def visit_object_type_flat(self, name, info, members, variants):
        pass

M
Markus Armbruster 已提交
1064 1065 1066 1067
    def visit_alternate_type(self, name, info, variants):
        pass

    def visit_command(self, name, info, arg_type, ret_type,
1068
                      gen, success_response, boxed):
M
Markus Armbruster 已提交
1069 1070
        pass

1071
    def visit_event(self, name, info, arg_type, boxed):
M
Markus Armbruster 已提交
1072 1073
        pass

1074 1075

class QAPISchemaType(QAPISchemaEntity):
E
Eric Blake 已提交
1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087
    # Return the C type for common use.
    # For the types we commonly box, this is a pointer type.
    def c_type(self):
        pass

    # Return the C type to be used in a parameter list.
    def c_param_type(self):
        return self.c_type()

    # Return the C type to be used where we suppress boxing.
    def c_unboxed_type(self):
        return self.c_type()
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100

    def json_type(self):
        pass

    def alternate_qtype(self):
        json2qtype = {
            'string':  'QTYPE_QSTRING',
            'number':  'QTYPE_QFLOAT',
            'int':     'QTYPE_QINT',
            'boolean': 'QTYPE_QBOOL',
            'object':  'QTYPE_QDICT'
        }
        return json2qtype.get(self.json_type())
1101 1102 1103


class QAPISchemaBuiltinType(QAPISchemaType):
E
Eric Blake 已提交
1104
    def __init__(self, name, json_type, c_type):
1105
        QAPISchemaType.__init__(self, name, None)
1106 1107 1108 1109 1110 1111 1112 1113 1114
        assert not c_type or isinstance(c_type, str)
        assert json_type in ('string', 'number', 'int', 'boolean', 'null',
                             'value')
        self._json_type_name = json_type
        self._c_type_name = c_type

    def c_name(self):
        return self.name

E
Eric Blake 已提交
1115 1116 1117 1118 1119
    def c_type(self):
        return self._c_type_name

    def c_param_type(self):
        if self.name == 'str':
1120 1121 1122 1123 1124
            return 'const ' + self._c_type_name
        return self._c_type_name

    def json_type(self):
        return self._json_type_name
1125

M
Markus Armbruster 已提交
1126 1127 1128
    def visit(self, visitor):
        visitor.visit_builtin_type(self.name, self.info, self.json_type())

1129 1130 1131 1132 1133

class QAPISchemaEnumType(QAPISchemaType):
    def __init__(self, name, info, values, prefix):
        QAPISchemaType.__init__(self, name, info)
        for v in values:
1134 1135
            assert isinstance(v, QAPISchemaMember)
            v.set_owner(name)
1136 1137 1138 1139 1140
        assert prefix is None or isinstance(prefix, str)
        self.values = values
        self.prefix = prefix

    def check(self, schema):
1141 1142 1143
        seen = {}
        for v in self.values:
            v.check_clash(self.info, seen)
1144

1145 1146
    def is_implicit(self):
        # See QAPISchema._make_implicit_enum_type()
1147
        return self.name.endswith('Kind')
1148

E
Eric Blake 已提交
1149
    def c_type(self):
1150 1151
        return c_name(self.name)

1152 1153 1154
    def member_names(self):
        return [v.name for v in self.values]

1155 1156 1157
    def json_type(self):
        return 'string'

M
Markus Armbruster 已提交
1158 1159
    def visit(self, visitor):
        visitor.visit_enum_type(self.name, self.info,
1160
                                self.member_names(), self.prefix)
M
Markus Armbruster 已提交
1161

1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173

class QAPISchemaArrayType(QAPISchemaType):
    def __init__(self, name, info, element_type):
        QAPISchemaType.__init__(self, name, info)
        assert isinstance(element_type, str)
        self._element_type_name = element_type
        self.element_type = None

    def check(self, schema):
        self.element_type = schema.lookup_type(self._element_type_name)
        assert self.element_type

1174 1175 1176
    def is_implicit(self):
        return True

E
Eric Blake 已提交
1177 1178 1179
    def c_type(self):
        return c_name(self.name) + pointer_suffix

1180 1181 1182
    def json_type(self):
        return 'array'

M
Markus Armbruster 已提交
1183 1184 1185
    def visit(self, visitor):
        visitor.visit_array_type(self.name, self.info, self.element_type)

1186 1187 1188

class QAPISchemaObjectType(QAPISchemaType):
    def __init__(self, name, info, base, local_members, variants):
1189 1190 1191
        # struct has local_members, optional base, and no variants
        # flat union has base, variants, and no local_members
        # simple union has local_members, variants, and no base
1192 1193 1194 1195
        QAPISchemaType.__init__(self, name, info)
        assert base is None or isinstance(base, str)
        for m in local_members:
            assert isinstance(m, QAPISchemaObjectTypeMember)
1196 1197 1198 1199
            m.set_owner(name)
        if variants is not None:
            assert isinstance(variants, QAPISchemaObjectTypeVariants)
            variants.set_owner(name)
1200 1201 1202 1203 1204 1205 1206
        self._base_name = base
        self.base = None
        self.local_members = local_members
        self.variants = variants
        self.members = None

    def check(self, schema):
E
Eric Blake 已提交
1207
        if self.members is False:               # check for cycles
M
Marc-André Lureau 已提交
1208 1209
            raise QAPISemError(self.info,
                               "Object %s contains itself" % self.name)
1210 1211 1212
        if self.members:
            return
        self.members = False                    # mark as being checked
1213
        seen = OrderedDict()
1214 1215 1216 1217
        if self._base_name:
            self.base = schema.lookup_type(self._base_name)
            assert isinstance(self.base, QAPISchemaObjectType)
            self.base.check(schema)
1218
            self.base.check_clash(schema, self.info, seen)
1219
        for m in self.local_members:
1220
            m.check(schema)
1221
            m.check_clash(self.info, seen)
1222
        self.members = seen.values()
1223
        if self.variants:
1224
            self.variants.check(schema, seen)
1225
            assert self.variants.tag_member in self.members
1226
            self.variants.check_clash(schema, self.info, seen)
1227

1228
    # Check that the members of this type do not cause duplicate JSON members,
1229 1230 1231
    # and update seen to track the members seen so far. Report any errors
    # on behalf of info, which is not necessarily self.info
    def check_clash(self, schema, info, seen):
1232 1233
        assert not self.variants       # not implemented
        for m in self.members:
1234
            m.check_clash(info, seen)
1235

1236
    def is_implicit(self):
1237 1238 1239
        # See QAPISchema._make_implicit_object_type(), as well as
        # _def_predefineds()
        return self.name.startswith('q_')
1240

E
Eric Blake 已提交
1241 1242 1243 1244
    def is_empty(self):
        assert self.members is not None
        return not self.members and not self.variants

1245
    def c_name(self):
1246
        assert self.name != 'q_empty'
1247 1248
        return QAPISchemaType.c_name(self)

E
Eric Blake 已提交
1249
    def c_type(self):
1250
        assert not self.is_implicit()
1251
        return c_name(self.name) + pointer_suffix
1252

E
Eric Blake 已提交
1253 1254 1255
    def c_unboxed_type(self):
        return c_name(self.name)

1256 1257 1258
    def json_type(self):
        return 'object'

M
Markus Armbruster 已提交
1259 1260 1261
    def visit(self, visitor):
        visitor.visit_object_type(self.name, self.info,
                                  self.base, self.local_members, self.variants)
1262 1263
        visitor.visit_object_type_flat(self.name, self.info,
                                       self.members, self.variants)
M
Markus Armbruster 已提交
1264

1265

1266
class QAPISchemaMember(object):
1267 1268
    role = 'member'

1269
    def __init__(self, name):
1270 1271
        assert isinstance(name, str)
        self.name = name
1272 1273 1274 1275 1276
        self.owner = None

    def set_owner(self, name):
        assert not self.owner
        self.owner = name
1277

1278 1279
    def check_clash(self, info, seen):
        cname = c_name(self.name)
1280
        if cname.lower() != cname and self.owner not in case_whitelist:
M
Marc-André Lureau 已提交
1281 1282
            raise QAPISemError(info,
                               "%s should not use uppercase" % self.describe())
1283
        if cname in seen:
M
Marc-André Lureau 已提交
1284 1285
            raise QAPISemError(info, "%s collides with %s" %
                               (self.describe(), seen[cname].describe()))
1286
        seen[cname] = self
1287

1288 1289
    def _pretty_owner(self):
        owner = self.owner
1290
        if owner.startswith('q_obj_'):
1291 1292
            # See QAPISchema._make_implicit_object_type() - reverse the
            # mapping there to create a nice human-readable description
1293
            owner = owner[6:]
1294 1295
            if owner.endswith('-arg'):
                return '(parameter of %s)' % owner[:-4]
1296 1297
            elif owner.endswith('-base'):
                return '(base of %s)' % owner[:-5]
1298 1299 1300 1301
            else:
                assert owner.endswith('-wrapper')
                # Unreachable and not implemented
                assert False
1302 1303 1304
        if owner.endswith('Kind'):
            # See QAPISchema._make_implicit_enum_type()
            return '(branch of %s)' % owner[:-4]
1305 1306 1307 1308 1309
        return '(%s of %s)' % (self.role, owner)

    def describe(self):
        return "'%s' %s" % (self.name, self._pretty_owner())

1310

1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325
class QAPISchemaObjectTypeMember(QAPISchemaMember):
    def __init__(self, name, typ, optional):
        QAPISchemaMember.__init__(self, name)
        assert isinstance(typ, str)
        assert isinstance(optional, bool)
        self._type_name = typ
        self.type = None
        self.optional = optional

    def check(self, schema):
        assert self.owner
        self.type = schema.lookup_type(self._type_name)
        assert self.type


1326
class QAPISchemaObjectTypeVariants(object):
1327 1328 1329 1330 1331 1332 1333 1334
    def __init__(self, tag_name, tag_member, variants):
        # Flat unions pass tag_name but not tag_member.
        # Simple unions and alternates pass tag_member but not tag_name.
        # After check(), tag_member is always set, and tag_name remains
        # a reliable witness of being used by a flat union.
        assert bool(tag_member) != bool(tag_name)
        assert (isinstance(tag_name, str) or
                isinstance(tag_member, QAPISchemaObjectTypeMember))
1335
        assert len(variants) > 0
1336 1337
        for v in variants:
            assert isinstance(v, QAPISchemaObjectTypeVariant)
1338
        self._tag_name = tag_name
1339
        self.tag_member = tag_member
1340 1341
        self.variants = variants

1342 1343 1344 1345
    def set_owner(self, name):
        for v in self.variants:
            v.set_owner(name)

1346
    def check(self, schema, seen):
1347
        if not self.tag_member:    # flat union
1348 1349
            self.tag_member = seen[c_name(self._tag_name)]
            assert self._tag_name == self.tag_member.name
1350 1351
        assert isinstance(self.tag_member.type, QAPISchemaEnumType)
        for v in self.variants:
1352
            v.check(schema)
1353 1354 1355
            # Union names must match enum values; alternate names are
            # checked separately. Use 'seen' to tell the two apart.
            if seen:
1356
                assert v.name in self.tag_member.type.member_names()
1357
                assert isinstance(v.type, QAPISchemaObjectType)
1358 1359
                v.type.check(schema)

1360
    def check_clash(self, schema, info, seen):
1361 1362 1363 1364
        for v in self.variants:
            # Reset seen map for each variant, since qapi names from one
            # branch do not affect another branch
            assert isinstance(v.type, QAPISchemaObjectType)
1365
            v.type.check_clash(schema, info, dict(seen))
1366

E
Eric Blake 已提交
1367

1368
class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
1369 1370
    role = 'branch'

1371 1372 1373 1374 1375 1376 1377 1378
    def __init__(self, name, typ):
        QAPISchemaObjectTypeMember.__init__(self, name, typ, False)


class QAPISchemaAlternateType(QAPISchemaType):
    def __init__(self, name, info, variants):
        QAPISchemaType.__init__(self, name, info)
        assert isinstance(variants, QAPISchemaObjectTypeVariants)
1379
        assert variants.tag_member
1380 1381
        variants.set_owner(name)
        variants.tag_member.set_owner(self.name)
1382 1383 1384
        self.variants = variants

    def check(self, schema):
1385
        self.variants.tag_member.check(schema)
1386 1387
        # Not calling self.variants.check_clash(), because there's nothing
        # to clash with
1388
        self.variants.check(schema, {})
1389 1390 1391 1392 1393
        # Alternate branch names have no relation to the tag enum values;
        # so we have to check for potential name collisions ourselves.
        seen = {}
        for v in self.variants.variants:
            v.check_clash(self.info, seen)
1394

E
Eric Blake 已提交
1395 1396 1397
    def c_type(self):
        return c_name(self.name) + pointer_suffix

1398 1399 1400
    def json_type(self):
        return 'value'

M
Markus Armbruster 已提交
1401 1402 1403
    def visit(self, visitor):
        visitor.visit_alternate_type(self.name, self.info, self.variants)

1404 1405 1406
    def is_empty(self):
        return False

1407 1408

class QAPISchemaCommand(QAPISchemaEntity):
1409 1410
    def __init__(self, name, info, arg_type, ret_type, gen, success_response,
                 boxed):
1411 1412 1413 1414 1415 1416 1417 1418 1419
        QAPISchemaEntity.__init__(self, name, info)
        assert not arg_type or isinstance(arg_type, str)
        assert not ret_type or isinstance(ret_type, str)
        self._arg_type_name = arg_type
        self.arg_type = None
        self._ret_type_name = ret_type
        self.ret_type = None
        self.gen = gen
        self.success_response = success_response
1420
        self.boxed = boxed
1421 1422 1423 1424

    def check(self, schema):
        if self._arg_type_name:
            self.arg_type = schema.lookup_type(self._arg_type_name)
1425 1426 1427 1428 1429
            assert (isinstance(self.arg_type, QAPISchemaObjectType) or
                    isinstance(self.arg_type, QAPISchemaAlternateType))
            self.arg_type.check(schema)
            if self.boxed:
                if self.arg_type.is_empty():
M
Marc-André Lureau 已提交
1430 1431
                    raise QAPISemError(self.info,
                                       "Cannot use 'boxed' with empty type")
1432 1433 1434 1435
            else:
                assert not isinstance(self.arg_type, QAPISchemaAlternateType)
                assert not self.arg_type.variants
        elif self.boxed:
M
Marc-André Lureau 已提交
1436
            raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
1437 1438 1439 1440
        if self._ret_type_name:
            self.ret_type = schema.lookup_type(self._ret_type_name)
            assert isinstance(self.ret_type, QAPISchemaType)

M
Markus Armbruster 已提交
1441 1442 1443
    def visit(self, visitor):
        visitor.visit_command(self.name, self.info,
                              self.arg_type, self.ret_type,
1444
                              self.gen, self.success_response, self.boxed)
M
Markus Armbruster 已提交
1445

1446 1447

class QAPISchemaEvent(QAPISchemaEntity):
1448
    def __init__(self, name, info, arg_type, boxed):
1449 1450 1451 1452
        QAPISchemaEntity.__init__(self, name, info)
        assert not arg_type or isinstance(arg_type, str)
        self._arg_type_name = arg_type
        self.arg_type = None
1453
        self.boxed = boxed
1454 1455 1456 1457

    def check(self, schema):
        if self._arg_type_name:
            self.arg_type = schema.lookup_type(self._arg_type_name)
1458 1459 1460 1461 1462
            assert (isinstance(self.arg_type, QAPISchemaObjectType) or
                    isinstance(self.arg_type, QAPISchemaAlternateType))
            self.arg_type.check(schema)
            if self.boxed:
                if self.arg_type.is_empty():
M
Marc-André Lureau 已提交
1463 1464
                    raise QAPISemError(self.info,
                                       "Cannot use 'boxed' with empty type")
1465 1466 1467 1468
            else:
                assert not isinstance(self.arg_type, QAPISchemaAlternateType)
                assert not self.arg_type.variants
        elif self.boxed:
M
Marc-André Lureau 已提交
1469
            raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
1470

M
Markus Armbruster 已提交
1471
    def visit(self, visitor):
1472
        visitor.visit_event(self.name, self.info, self.arg_type, self.boxed)
M
Markus Armbruster 已提交
1473

1474 1475 1476 1477

class QAPISchema(object):
    def __init__(self, fname):
        try:
M
Marc-André Lureau 已提交
1478 1479 1480
            parser = QAPISchemaParser(open(fname, "r"))
            self.exprs = check_exprs(parser.exprs)
            self.docs = check_docs(parser.docs)
1481
            self._entity_dict = {}
1482
            self._predefining = True
1483
            self._def_predefineds()
1484
            self._predefining = False
1485 1486
            self._def_exprs()
            self.check()
M
Marc-André Lureau 已提交
1487
        except QAPIError as err:
1488 1489 1490 1491
            print >>sys.stderr, err
            exit(1)

    def _def_entity(self, ent):
1492 1493
        # Only the predefined types are allowed to not have info
        assert ent.info or self._predefining
1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505
        assert ent.name not in self._entity_dict
        self._entity_dict[ent.name] = ent

    def lookup_entity(self, name, typ=None):
        ent = self._entity_dict.get(name)
        if typ and not isinstance(ent, typ):
            return None
        return ent

    def lookup_type(self, name):
        return self.lookup_entity(name, QAPISchemaType)

E
Eric Blake 已提交
1506 1507
    def _def_builtin_type(self, name, json_type, c_type):
        self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
E
Eric Blake 已提交
1508 1509 1510 1511 1512
        # TODO As long as we have QAPI_TYPES_BUILTIN to share multiple
        # qapi-types.h from a single .c, all arrays of builtins must be
        # declared in the first file whether or not they are used.  Nicer
        # would be to use lazy instantiation, while figuring out how to
        # avoid compilation issues with multiple qapi-types.h.
1513
        self._make_array_type(name, None)
1514 1515

    def _def_predefineds(self):
E
Eric Blake 已提交
1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529
        for t in [('str',    'string',  'char' + pointer_suffix),
                  ('number', 'number',  'double'),
                  ('int',    'int',     'int64_t'),
                  ('int8',   'int',     'int8_t'),
                  ('int16',  'int',     'int16_t'),
                  ('int32',  'int',     'int32_t'),
                  ('int64',  'int',     'int64_t'),
                  ('uint8',  'int',     'uint8_t'),
                  ('uint16', 'int',     'uint16_t'),
                  ('uint32', 'int',     'uint32_t'),
                  ('uint64', 'int',     'uint64_t'),
                  ('size',   'int',     'uint64_t'),
                  ('bool',   'boolean', 'bool'),
                  ('any',    'value',   'QObject' + pointer_suffix)]:
1530
            self._def_builtin_type(*t)
1531 1532
        self.the_empty_object_type = QAPISchemaObjectType('q_empty', None,
                                                          None, [], None)
1533
        self._def_entity(self.the_empty_object_type)
1534 1535 1536 1537
        qtype_values = self._make_enum_members(['none', 'qnull', 'qint',
                                                'qstring', 'qdict', 'qlist',
                                                'qfloat', 'qbool'])
        self._def_entity(QAPISchemaEnumType('QType', None, qtype_values,
1538
                                            'QTYPE'))
1539

1540 1541 1542
    def _make_enum_members(self, values):
        return [QAPISchemaMember(v) for v in values]

1543
    def _make_implicit_enum_type(self, name, info, values):
1544
        # See also QAPISchemaObjectTypeMember._pretty_owner()
1545
        name = name + 'Kind'   # Use namespace reserved by add_name()
1546 1547
        self._def_entity(QAPISchemaEnumType(
            name, info, self._make_enum_members(values), None))
1548 1549
        return name

1550
    def _make_array_type(self, element_type, info):
1551
        name = element_type + 'List'   # Use namespace reserved by add_name()
1552
        if not self.lookup_type(name):
1553
            self._def_entity(QAPISchemaArrayType(name, info, element_type))
1554 1555
        return name

1556
    def _make_implicit_object_type(self, name, info, role, members):
1557 1558
        if not members:
            return None
1559
        # See also QAPISchemaObjectTypeMember._pretty_owner()
1560
        name = 'q_obj_%s-%s' % (name, role)
1561
        if not self.lookup_entity(name, QAPISchemaObjectType):
1562
            self._def_entity(QAPISchemaObjectType(name, info, None,
1563 1564 1565 1566 1567 1568 1569
                                                  members, None))
        return name

    def _def_enum_type(self, expr, info):
        name = expr['enum']
        data = expr['data']
        prefix = expr.get('prefix')
1570 1571
        self._def_entity(QAPISchemaEnumType(
            name, info, self._make_enum_members(data), prefix))
1572

1573
    def _make_member(self, name, typ, info):
1574 1575 1576 1577 1578 1579
        optional = False
        if name.startswith('*'):
            name = name[1:]
            optional = True
        if isinstance(typ, list):
            assert len(typ) == 1
1580
            typ = self._make_array_type(typ[0], info)
1581 1582
        return QAPISchemaObjectTypeMember(name, typ, optional)

1583 1584
    def _make_members(self, data, info):
        return [self._make_member(key, value, info)
1585 1586 1587 1588 1589 1590 1591
                for (key, value) in data.iteritems()]

    def _def_struct_type(self, expr, info):
        name = expr['struct']
        base = expr.get('base')
        data = expr['data']
        self._def_entity(QAPISchemaObjectType(name, info, base,
1592
                                              self._make_members(data, info),
1593 1594 1595 1596 1597
                                              None))

    def _make_variant(self, case, typ):
        return QAPISchemaObjectTypeVariant(case, typ)

1598
    def _make_simple_variant(self, case, typ, info):
1599 1600
        if isinstance(typ, list):
            assert len(typ) == 1
1601 1602 1603
            typ = self._make_array_type(typ[0], info)
        typ = self._make_implicit_object_type(
            typ, info, 'wrapper', [self._make_member('data', typ, info)])
1604 1605 1606 1607 1608 1609 1610
        return QAPISchemaObjectTypeVariant(case, typ)

    def _def_union_type(self, expr, info):
        name = expr['union']
        data = expr['data']
        base = expr.get('base')
        tag_name = expr.get('discriminator')
1611
        tag_member = None
1612 1613 1614
        if isinstance(base, dict):
            base = (self._make_implicit_object_type(
                    name, info, 'base', self._make_members(base, info)))
1615 1616 1617
        if tag_name:
            variants = [self._make_variant(key, value)
                        for (key, value) in data.iteritems()]
1618
            members = []
1619
        else:
1620
            variants = [self._make_simple_variant(key, value, info)
1621
                        for (key, value) in data.iteritems()]
E
Eric Blake 已提交
1622 1623 1624
            typ = self._make_implicit_enum_type(name, info,
                                                [v.name for v in variants])
            tag_member = QAPISchemaObjectTypeMember('type', typ, False)
1625
            members = [tag_member]
1626
        self._def_entity(
1627
            QAPISchemaObjectType(name, info, base, members,
1628
                                 QAPISchemaObjectTypeVariants(tag_name,
1629
                                                              tag_member,
1630 1631 1632 1633 1634 1635 1636
                                                              variants)))

    def _def_alternate_type(self, expr, info):
        name = expr['alternate']
        data = expr['data']
        variants = [self._make_variant(key, value)
                    for (key, value) in data.iteritems()]
1637
        tag_member = QAPISchemaObjectTypeMember('type', 'QType', False)
1638 1639 1640
        self._def_entity(
            QAPISchemaAlternateType(name, info,
                                    QAPISchemaObjectTypeVariants(None,
1641
                                                                 tag_member,
1642 1643 1644 1645 1646 1647 1648 1649
                                                                 variants)))

    def _def_command(self, expr, info):
        name = expr['command']
        data = expr.get('data')
        rets = expr.get('returns')
        gen = expr.get('gen', True)
        success_response = expr.get('success-response', True)
1650
        boxed = expr.get('boxed', False)
1651
        if isinstance(data, OrderedDict):
1652 1653
            data = self._make_implicit_object_type(
                name, info, 'arg', self._make_members(data, info))
1654 1655
        if isinstance(rets, list):
            assert len(rets) == 1
1656
            rets = self._make_array_type(rets[0], info)
1657
        self._def_entity(QAPISchemaCommand(name, info, data, rets, gen,
1658
                                           success_response, boxed))
1659 1660 1661 1662

    def _def_event(self, expr, info):
        name = expr['event']
        data = expr.get('data')
1663
        boxed = expr.get('boxed', False)
1664
        if isinstance(data, OrderedDict):
1665 1666
            data = self._make_implicit_object_type(
                name, info, 'arg', self._make_members(data, info))
1667
        self._def_entity(QAPISchemaEvent(name, info, data, boxed))
1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690

    def _def_exprs(self):
        for expr_elem in self.exprs:
            expr = expr_elem['expr']
            info = expr_elem['info']
            if 'enum' in expr:
                self._def_enum_type(expr, info)
            elif 'struct' in expr:
                self._def_struct_type(expr, info)
            elif 'union' in expr:
                self._def_union_type(expr, info)
            elif 'alternate' in expr:
                self._def_alternate_type(expr, info)
            elif 'command' in expr:
                self._def_command(expr, info)
            elif 'event' in expr:
                self._def_event(expr, info)
            else:
                assert False

    def check(self):
        for ent in self._entity_dict.values():
            ent.check(self)
1691

M
Markus Armbruster 已提交
1692
    def visit(self, visitor):
1693 1694 1695 1696
        visitor.visit_begin(self)
        for (name, entity) in sorted(self._entity_dict.items()):
            if visitor.visit_needed(entity):
                entity.visit(visitor)
M
Markus Armbruster 已提交
1697 1698
        visitor.visit_end()

1699

1700 1701 1702 1703
#
# Code generation helpers
#

1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716
def camel_case(name):
    new_name = ''
    first = True
    for ch in name:
        if ch in ['_', '-']:
            first = True
        elif first:
            new_name += ch.upper()
            first = False
        else:
            new_name += ch.lower()
    return new_name

E
Eric Blake 已提交
1717

1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731
# ENUMName -> ENUM_NAME, EnumName1 -> ENUM_NAME1
# ENUM_NAME -> ENUM_NAME, ENUM_NAME1 -> ENUM_NAME1, ENUM_Name2 -> ENUM_NAME2
# ENUM24_Name -> ENUM24_NAME
def camel_to_upper(value):
    c_fun_str = c_name(value, False)
    if value.isupper():
        return c_fun_str

    new_name = ''
    l = len(c_fun_str)
    for i in range(l):
        c = c_fun_str[i]
        # When c is upper and no "_" appears before, do more checks
        if c.isupper() and (i > 0) and c_fun_str[i - 1] != "_":
E
Eric Blake 已提交
1732 1733 1734
            if i < l - 1 and c_fun_str[i + 1].islower():
                new_name += '_'
            elif c_fun_str[i - 1].isdigit():
1735 1736 1737 1738
                new_name += '_'
        new_name += c
    return new_name.lstrip('_').upper()

E
Eric Blake 已提交
1739

1740 1741 1742
def c_enum_const(type_name, const_name, prefix=None):
    if prefix is not None:
        type_name = prefix
1743
    return camel_to_upper(type_name) + '_' + c_name(const_name, False).upper()
1744

1745
c_name_trans = string.maketrans('.-', '__')
1746

E
Eric Blake 已提交
1747

1748 1749 1750 1751 1752 1753 1754 1755 1756
# Map @name to a valid C identifier.
# If @protect, avoid returning certain ticklish identifiers (like
# C keywords) by prepending "q_".
#
# Used for converting 'name' from a 'name':'type' qapi definition
# into a generated struct member, as well as converting type names
# into substrings of a generated C function name.
# '__a.b_c' -> '__a_b_c', 'x-foo' -> 'x_foo'
# protect=True: 'int' -> 'q_int'; protect=False: 'int' -> 'int'
1757
def c_name(name, protect=True):
B
Blue Swirl 已提交
1758 1759
    # ANSI X3J11/88-090, 3.1.1
    c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue',
E
Eric Blake 已提交
1760 1761 1762 1763 1764
                     'default', 'do', 'double', 'else', 'enum', 'extern',
                     'float', 'for', 'goto', 'if', 'int', 'long', 'register',
                     'return', 'short', 'signed', 'sizeof', 'static',
                     'struct', 'switch', 'typedef', 'union', 'unsigned',
                     'void', 'volatile', 'while'])
B
Blue Swirl 已提交
1765 1766 1767
    # ISO/IEC 9899:1999, 6.4.1
    c99_words = set(['inline', 'restrict', '_Bool', '_Complex', '_Imaginary'])
    # ISO/IEC 9899:2011, 6.4.1
E
Eric Blake 已提交
1768 1769
    c11_words = set(['_Alignas', '_Alignof', '_Atomic', '_Generic',
                     '_Noreturn', '_Static_assert', '_Thread_local'])
B
Blue Swirl 已提交
1770 1771 1772
    # GCC http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/C-Extensions.html
    # excluding _.*
    gcc_words = set(['asm', 'typeof'])
1773 1774 1775 1776 1777 1778 1779 1780 1781 1782
    # C++ ISO/IEC 14882:2003 2.11
    cpp_words = set(['bool', 'catch', 'class', 'const_cast', 'delete',
                     'dynamic_cast', 'explicit', 'false', 'friend', 'mutable',
                     'namespace', 'new', 'operator', 'private', 'protected',
                     'public', 'reinterpret_cast', 'static_cast', 'template',
                     'this', 'throw', 'true', 'try', 'typeid', 'typename',
                     'using', 'virtual', 'wchar_t',
                     # alternative representations
                     'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not',
                     'not_eq', 'or', 'or_eq', 'xor', 'xor_eq'])
1783
    # namespace pollution:
1784
    polluted_words = set(['unix', 'errno', 'mips', 'sparc'])
E
Eric Blake 已提交
1785
    name = name.translate(c_name_trans)
E
Eric Blake 已提交
1786 1787
    if protect and (name in c89_words | c99_words | c11_words | gcc_words
                    | cpp_words | polluted_words):
B
Blue Swirl 已提交
1788
        return "q_" + name
E
Eric Blake 已提交
1789
    return name
1790

1791
eatspace = '\033EATSPACE.'
E
Eric Blake 已提交
1792
pointer_suffix = ' *' + eatspace
1793

E
Eric Blake 已提交
1794

1795 1796
def genindent(count):
    ret = ""
E
Eric Blake 已提交
1797
    for _ in range(count):
1798 1799 1800 1801 1802
        ret += " "
    return ret

indent_level = 0

E
Eric Blake 已提交
1803

1804 1805 1806 1807
def push_indent(indent_amount=4):
    global indent_level
    indent_level += indent_amount

E
Eric Blake 已提交
1808

1809 1810 1811 1812
def pop_indent(indent_amount=4):
    global indent_level
    indent_level -= indent_amount

E
Eric Blake 已提交
1813

1814 1815
# Generate @code with @kwds interpolated.
# Obey indent_level, and strip eatspace.
1816
def cgen(code, **kwds):
1817 1818 1819
    raw = code % kwds
    if indent_level:
        indent = genindent(indent_level)
1820 1821 1822
        # re.subn() lacks flags support before Python 2.7, use re.compile()
        raw = re.subn(re.compile("^.", re.MULTILINE),
                      indent + r'\g<0>', raw)
1823 1824
        raw = raw[0]
    return re.sub(re.escape(eatspace) + ' *', '', raw)
1825

E
Eric Blake 已提交
1826

1827
def mcgen(code, **kwds):
1828 1829 1830
    if code[0] == '\n':
        code = code[1:]
    return cgen(code, **kwds)
1831 1832 1833


def guardname(filename):
M
Markus Armbruster 已提交
1834
    return c_name(filename, protect=False).upper()
1835

E
Eric Blake 已提交
1836

1837 1838 1839 1840 1841 1842 1843 1844 1845
def guardstart(name):
    return mcgen('''

#ifndef %(name)s
#define %(name)s

''',
                 name=guardname(name))

E
Eric Blake 已提交
1846

1847 1848 1849 1850 1851 1852 1853
def guardend(name):
    return mcgen('''

#endif /* %(name)s */

''',
                 name=guardname(name))
1854

E
Eric Blake 已提交
1855

1856
def gen_enum_lookup(name, values, prefix=None):
1857 1858
    ret = mcgen('''

1859
const char *const %(c_name)s_lookup[] = {
1860
''',
1861
                c_name=c_name(name))
1862 1863 1864 1865 1866
    for value in values:
        index = c_enum_const(name, value, prefix)
        ret += mcgen('''
    [%(index)s] = "%(value)s",
''',
1867
                     index=index, value=value)
1868

1869
    max_index = c_enum_const(name, '_MAX', prefix)
1870 1871 1872 1873
    ret += mcgen('''
    [%(max_index)s] = NULL,
};
''',
1874
                 max_index=max_index)
1875 1876
    return ret

E
Eric Blake 已提交
1877

1878 1879
def gen_enum(name, values, prefix=None):
    # append automatically generated _MAX value
1880
    enum_values = values + ['_MAX']
1881

1882
    ret = mcgen('''
1883

1884
typedef enum %(c_name)s {
1885
''',
1886
                c_name=c_name(name))
1887 1888 1889

    i = 0
    for value in enum_values:
1890 1891
        ret += mcgen('''
    %(c_enum)s = %(i)d,
1892
''',
1893
                     c_enum=c_enum_const(name, value, prefix),
1894 1895 1896
                     i=i)
        i += 1

1897 1898
    ret += mcgen('''
} %(c_name)s;
1899
''',
1900 1901 1902
                 c_name=c_name(name))

    ret += mcgen('''
1903

1904 1905 1906 1907
extern const char *const %(c_name)s_lookup[];
''',
                 c_name=c_name(name))
    return ret
1908

E
Eric Blake 已提交
1909

1910
def gen_params(arg_type, boxed, extra):
1911
    if not arg_type:
1912
        assert not boxed
1913 1914 1915
        return extra
    ret = ''
    sep = ''
1916
    if boxed:
1917 1918
        ret += '%s arg' % arg_type.c_param_type()
        sep = ', '
1919 1920 1921 1922 1923 1924 1925 1926 1927
    else:
        assert not arg_type.variants
        for memb in arg_type.members:
            ret += sep
            sep = ', '
            if memb.optional:
                ret += 'bool has_%s, ' % c_name(memb.name)
            ret += '%s %s' % (memb.type.c_param_type(),
                              c_name(memb.name))
1928 1929 1930 1931
    if extra:
        ret += sep + extra
    return ret

E
Eric Blake 已提交
1932

1933 1934 1935 1936
#
# Common command line parsing
#

E
Eric Blake 已提交
1937 1938

def parse_command_line(extra_options="", extra_long_options=[]):
1939 1940 1941

    try:
        opts, args = getopt.gnu_getopt(sys.argv[1:],
1942
                                       "chp:o:" + extra_options,
1943
                                       ["source", "header", "prefix=",
1944
                                        "output-dir="] + extra_long_options)
1945
    except getopt.GetoptError as err:
1946
        print >>sys.stderr, "%s: %s" % (sys.argv[0], str(err))
1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957
        sys.exit(1)

    output_dir = ""
    prefix = ""
    do_c = False
    do_h = False
    extra_opts = []

    for oa in opts:
        o, a = oa
        if o in ("-p", "--prefix"):
1958 1959 1960 1961 1962 1963
            match = re.match('([A-Za-z_.-][A-Za-z0-9_.-]*)?', a)
            if match.end() != len(a):
                print >>sys.stderr, \
                    "%s: 'funny character '%s' in argument of --prefix" \
                    % (sys.argv[0], a[match.end()])
                sys.exit(1)
1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977
            prefix = a
        elif o in ("-o", "--output-dir"):
            output_dir = a + "/"
        elif o in ("-c", "--source"):
            do_c = True
        elif o in ("-h", "--header"):
            do_h = True
        else:
            extra_opts.append(oa)

    if not do_c and not do_h:
        do_c = True
        do_h = True

1978 1979
    if len(args) != 1:
        print >>sys.stderr, "%s: need exactly one argument" % sys.argv[0]
1980
        sys.exit(1)
1981
    fname = args[0]
1982

1983
    return (fname, output_dir, do_c, do_h, prefix, extra_opts)
1984

1985 1986 1987 1988
#
# Generate output files with boilerplate
#

E
Eric Blake 已提交
1989

1990 1991
def open_output(output_dir, do_c, do_h, prefix, c_file, h_file,
                c_comment, h_comment):
M
Markus Armbruster 已提交
1992
    guard = guardname(prefix + h_file)
1993 1994 1995
    c_file = output_dir + prefix + c_file
    h_file = output_dir + prefix + h_file

1996 1997 1998
    if output_dir:
        try:
            os.makedirs(output_dir)
1999
        except os.error as e:
2000 2001
            if e.errno != errno.EEXIST:
                raise
2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016

    def maybe_open(really, name, opt):
        if really:
            return open(name, opt)
        else:
            import StringIO
            return StringIO.StringIO()

    fdef = maybe_open(do_c, c_file, 'w')
    fdecl = maybe_open(do_h, h_file, 'w')

    fdef.write(mcgen('''
/* AUTOMATICALLY GENERATED, DO NOT MODIFY */
%(comment)s
''',
E
Eric Blake 已提交
2017
                     comment=c_comment))
2018 2019 2020 2021 2022 2023 2024 2025

    fdecl.write(mcgen('''
/* AUTOMATICALLY GENERATED, DO NOT MODIFY */
%(comment)s
#ifndef %(guard)s
#define %(guard)s

''',
E
Eric Blake 已提交
2026
                      comment=h_comment, guard=guard))
2027 2028 2029

    return (fdef, fdecl)

E
Eric Blake 已提交
2030

2031 2032 2033 2034 2035 2036
def close_output(fdef, fdecl):
    fdecl.write('''
#endif
''')
    fdecl.close()
    fdef.close()