qapi.py 69.2 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
# Are documentation comments required?
doc_required = False

43
# Whitelist of commands allowed to return a non-dictionary
44
returns_whitelist = []
45

46
# Whitelist of entities allowed to violate case conventions
47
name_case_whitelist = []
48

49 50 51 52 53 54
enum_types = []
struct_types = []
union_types = []
events = []
all_names = {}

55 56 57 58
#
# Parsing the schema into expressions
#

E
Eric Blake 已提交
59

60 61 62 63 64 65 66 67
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 已提交
68

M
Marc-André Lureau 已提交
69 70
class QAPIError(Exception):
    def __init__(self, fname, line, col, incl_info, msg):
71
        Exception.__init__(self)
M
Marc-André Lureau 已提交
72 73 74 75
        self.fname = fname
        self.line = line
        self.col = col
        self.info = incl_info
76 77 78
        self.msg = msg

    def __str__(self):
M
Marc-André Lureau 已提交
79 80 81 82
        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)
83

E
Eric Blake 已提交
84

M
Marc-André Lureau 已提交
85 86 87 88 89 90 91 92 93 94
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)
95

M
Marc-André Lureau 已提交
96 97 98 99 100

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

E
Eric Blake 已提交
102

M
Marc-André Lureau 已提交
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 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
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)


219
class QAPISchemaParser(object):
220

E
Eric Blake 已提交
221
    def __init__(self, fp, previously_included=[], incl_info=None):
222
        abs_fname = os.path.abspath(fp.name)
223
        fname = fp.name
224 225 226
        self.fname = fname
        previously_included.append(abs_fname)
        self.incl_info = incl_info
227 228 229 230
        self.src = fp.read()
        if self.src == '' or self.src[-1] != '\n':
            self.src += '\n'
        self.cursor = 0
231 232
        self.line = 1
        self.line_pos = 0
233
        self.exprs = []
M
Marc-André Lureau 已提交
234
        self.docs = []
235 236
        self.accept()

E
Eric Blake 已提交
237
        while self.tok is not None:
M
Marc-André Lureau 已提交
238 239
            info = {'file': fname, 'line': self.line,
                    'parent': self.incl_info}
M
Marc-André Lureau 已提交
240 241 242 243 244
            if self.tok == '#':
                doc = self.get_doc(info)
                self.docs.append(doc)
                continue

245
            expr = self.get_expr(False)
246
            if 'include' in expr:
247
                if len(expr) != 1:
M
Marc-André Lureau 已提交
248
                    raise QAPISemError(info, "Invalid 'include' directive")
249 250
                include = expr["include"]
                if not isinstance(include, str):
M
Marc-André Lureau 已提交
251 252
                    raise QAPISemError(info,
                                       "Value of 'include' must be a string")
253 254
                self._include(include, info, os.path.dirname(abs_fname),
                              previously_included)
255 256 257 258 259 260 261 262 263
            elif "pragma" in expr:
                if len(expr) != 1:
                    raise QAPISemError(info, "Invalid 'pragma' directive")
                pragma = expr['pragma']
                if not isinstance(pragma, dict):
                    raise QAPISemError(
                        info, "Value of 'pragma' must be a dictionary")
                for name, value in pragma.iteritems():
                    self._pragma(name, value, info)
264 265
            else:
                expr_elem = {'expr': expr,
M
Marc-André Lureau 已提交
266
                             'info': info}
M
Marc-André Lureau 已提交
267 268 269 270 271 272
                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]

273
                self.exprs.append(expr_elem)
274

275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
    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)

295
    def _pragma(self, name, value, info):
296
        global doc_required, returns_whitelist, name_case_whitelist
297 298 299 300 301
        if name == 'doc-required':
            if not isinstance(value, bool):
                raise QAPISemError(info,
                                   "Pragma 'doc-required' must be boolean")
            doc_required = value
302 303 304 305 306 307 308
        elif name == 'returns-whitelist':
            if (not isinstance(value, list)
                    or any([not isinstance(elt, str) for elt in value])):
                raise QAPISemError(info,
                                   "Pragma returns-whitelist must be"
                                   " a list of strings")
            returns_whitelist = value
309 310 311 312 313 314 315
        elif name == 'name-case-whitelist':
            if (not isinstance(value, list)
                    or any([not isinstance(elt, str) for elt in value])):
                raise QAPISemError(info,
                                   "Pragma name-case-whitelist must be"
                                   " a list of strings")
            name_case_whitelist = value
316 317 318
        else:
            raise QAPISemError(info, "Unknown pragma '%s'" % name)

M
Marc-André Lureau 已提交
319
    def accept(self, skip_comment=True):
320 321
        while True:
            self.tok = self.src[self.cursor]
322
            self.pos = self.cursor
323 324 325
            self.cursor += 1
            self.val = None

326
            if self.tok == '#':
M
Marc-André Lureau 已提交
327 328 329
                if self.src[self.cursor] == '#':
                    # Start of doc comment
                    skip_comment = False
330
                self.cursor = self.src.find('\n', self.cursor)
M
Marc-André Lureau 已提交
331 332 333
                if not skip_comment:
                    self.val = self.src[self.pos:self.cursor]
                    return
334
            elif self.tok in "{}:,[]":
335 336 337 338 339 340 341 342
                return
            elif self.tok == "'":
                string = ''
                esc = False
                while True:
                    ch = self.src[self.cursor]
                    self.cursor += 1
                    if ch == '\n':
M
Marc-André Lureau 已提交
343
                        raise QAPIParseError(self, 'Missing terminating "\'"')
344
                    if esc:
345 346 347 348 349 350 351 352 353 354 355 356
                        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 已提交
357
                            for _ in range(0, 4):
358 359 360
                                ch = self.src[self.cursor]
                                self.cursor += 1
                                if ch not in "0123456789abcdefABCDEF":
M
Marc-André Lureau 已提交
361 362 363
                                    raise QAPIParseError(self,
                                                         '\\u escape needs 4 '
                                                         'hex digits')
364 365 366 367 368 369
                                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 已提交
370 371 372 373
                                raise QAPIParseError(self,
                                                     'For now, \\u escape '
                                                     'only supports non-zero '
                                                     'values up to \\u007f')
374 375 376 377
                            string += chr(value)
                        elif ch in "\\/'\"":
                            string += ch
                        else:
M
Marc-André Lureau 已提交
378 379
                            raise QAPIParseError(self,
                                                 "Unknown escape \\%s" % ch)
380 381 382 383 384 385 386 387
                        esc = False
                    elif ch == "\\":
                        esc = True
                    elif ch == "'":
                        self.val = string
                        return
                    else:
                        string += ch
388 389 390 391 392 393 394 395 396 397 398 399
            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
400 401 402 403
            elif self.tok == '\n':
                if self.cursor == len(self.src):
                    self.tok = None
                    return
404 405
                self.line += 1
                self.line_pos = self.cursor
406
            elif not self.tok.isspace():
M
Marc-André Lureau 已提交
407
                raise QAPIParseError(self, 'Stray "%s"' % self.tok)
408 409 410

    def get_members(self):
        expr = OrderedDict()
411 412 413 414
        if self.tok == '}':
            self.accept()
            return expr
        if self.tok != "'":
M
Marc-André Lureau 已提交
415
            raise QAPIParseError(self, 'Expected string or "}"')
416
        while True:
417 418
            key = self.val
            self.accept()
419
            if self.tok != ':':
M
Marc-André Lureau 已提交
420
                raise QAPIParseError(self, 'Expected ":"')
421
            self.accept()
422
            if key in expr:
M
Marc-André Lureau 已提交
423
                raise QAPIParseError(self, 'Duplicate key "%s"' % key)
424
            expr[key] = self.get_expr(True)
425
            if self.tok == '}':
426
                self.accept()
427 428
                return expr
            if self.tok != ',':
M
Marc-André Lureau 已提交
429
                raise QAPIParseError(self, 'Expected "," or "}"')
430 431
            self.accept()
            if self.tok != "'":
M
Marc-André Lureau 已提交
432
                raise QAPIParseError(self, 'Expected string')
433 434 435

    def get_values(self):
        expr = []
436 437 438
        if self.tok == ']':
            self.accept()
            return expr
E
Eric Blake 已提交
439
        if self.tok not in "{['tfn":
M
Marc-André Lureau 已提交
440 441
            raise QAPIParseError(self, 'Expected "{", "[", "]", string, '
                                 'boolean or "null"')
442
        while True:
443
            expr.append(self.get_expr(True))
444
            if self.tok == ']':
445
                self.accept()
446 447
                return expr
            if self.tok != ',':
M
Marc-André Lureau 已提交
448
                raise QAPIParseError(self, 'Expected "," or "]"')
449
            self.accept()
450

451 452
    def get_expr(self, nested):
        if self.tok != '{' and not nested:
M
Marc-André Lureau 已提交
453
            raise QAPIParseError(self, 'Expected "{"')
454 455 456 457 458 459
        if self.tok == '{':
            self.accept()
            expr = self.get_members()
        elif self.tok == '[':
            self.accept()
            expr = self.get_values()
460
        elif self.tok in "'tfn":
461 462
            expr = self.val
            self.accept()
463
        else:
M
Marc-André Lureau 已提交
464
            raise QAPIParseError(self, 'Expected "{", "[" or string')
465
        return expr
K
Kevin Wolf 已提交
466

M
Marc-André Lureau 已提交
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
    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 '##'")


489 490
#
# Semantic analysis of schema expressions
491 492
# TODO fold into QAPISchema
# TODO catching name collisions in generated code would be nice
493 494
#

E
Eric Blake 已提交
495

496
def find_base_members(base):
497 498
    if isinstance(base, dict):
        return base
499 500 501 502 503
    base_struct_define = find_struct(base)
    if not base_struct_define:
        return None
    return base_struct_define['data']

E
Eric Blake 已提交
504

505 506
# Return the qtype of an alternate branch, or None on error.
def find_alternate_member_qtype(qapi_type):
E
Eric Blake 已提交
507
    if qapi_type in builtin_types:
E
Eric Blake 已提交
508 509 510 511 512
        return builtin_types[qapi_type]
    elif find_struct(qapi_type):
        return "QTYPE_QDICT"
    elif find_enum(qapi_type):
        return "QTYPE_QSTRING"
513 514
    elif find_union(qapi_type):
        return "QTYPE_QDICT"
E
Eric Blake 已提交
515 516
    return None

E
Eric Blake 已提交
517

518 519 520 521 522 523 524 525 526
# 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

527 528
    base_members = find_base_members(base)
    if not base_members:
529 530
        return None

531
    discriminator_type = base_members.get(discriminator)
532 533 534 535 536
    if not discriminator_type:
        return None

    return find_enum(discriminator_type)

E
Eric Blake 已提交
537

538 539 540 541 542
# 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 已提交
543 544


M
Marc-André Lureau 已提交
545
def check_name(info, source, name, allow_optional=False,
E
Eric Blake 已提交
546
               enum_member=False):
E
Eric Blake 已提交
547 548 549 550
    global valid_name
    membername = name

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

E
Eric Blake 已提交
567 568

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

E
Eric Blake 已提交
581

582 583 584 585 586 587
def add_struct(definition, info):
    global struct_types
    name = definition['struct']
    add_name(name, info, 'struct')
    struct_types.append(definition)

E
Eric Blake 已提交
588

589 590 591 592 593 594 595
def find_struct(name):
    global struct_types
    for struct in struct_types:
        if struct['struct'] == name:
            return struct
    return None

E
Eric Blake 已提交
596

597 598 599 600 601 602
def add_union(definition, info):
    global union_types
    name = definition['union']
    add_name(name, info, 'union')
    union_types.append(definition)

E
Eric Blake 已提交
603

604 605 606 607 608 609 610
def find_union(name):
    global union_types
    for union in union_types:
        if union['union'] == name:
            return union
    return None

E
Eric Blake 已提交
611 612

def add_enum(name, info, enum_values=None, implicit=False):
613 614 615 616
    global enum_types
    add_name(name, info, 'enum', implicit)
    enum_types.append({"enum_name": name, "enum_values": enum_values})

E
Eric Blake 已提交
617

618 619 620 621 622 623 624
def find_enum(name):
    global enum_types
    for enum in enum_types:
        if enum['enum_name'] == name:
            return enum
    return None

E
Eric Blake 已提交
625

626
def is_enum(name):
E
Eric Blake 已提交
627 628
    return find_enum(name) is not None

629

M
Marc-André Lureau 已提交
630
def check_type(info, source, value, allow_array=False,
E
Eric Blake 已提交
631 632
               allow_dict=False, allow_optional=False,
               allow_metas=[]):
633 634 635 636 637 638 639 640
    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 已提交
641
            raise QAPISemError(info, "%s cannot be an array" % source)
642
        if len(value) != 1 or not isinstance(value[0], str):
M
Marc-André Lureau 已提交
643 644 645
            raise QAPISemError(info,
                               "%s: array type must contain single type name" %
                               source)
646 647 648 649
        value = value[0]

    # Check if type name for value is okay
    if isinstance(value, str):
E
Eric Blake 已提交
650
        if value not in all_names:
M
Marc-André Lureau 已提交
651 652
            raise QAPISemError(info, "%s uses unknown type '%s'"
                               % (source, value))
653
        if not all_names[value] in allow_metas:
M
Marc-André Lureau 已提交
654 655
            raise QAPISemError(info, "%s cannot use %s type '%s'" %
                               (source, all_names[value], value))
656 657 658
        return

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

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

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

E
Eric Blake 已提交
679

M
Marc-André Lureau 已提交
680
def check_command(expr, info):
681
    name = expr['command']
682
    boxed = expr.get('boxed', False)
683

684 685 686
    args_meta = ['struct']
    if boxed:
        args_meta += ['union', 'alternate']
M
Marc-André Lureau 已提交
687
    check_type(info, "'data' for command '%s'" % name,
688 689
               expr.get('data'), allow_dict=not boxed, allow_optional=True,
               allow_metas=args_meta)
690 691 692
    returns_meta = ['union', 'struct']
    if name in returns_whitelist:
        returns_meta += ['built-in', 'alternate', 'enum']
M
Marc-André Lureau 已提交
693
    check_type(info, "'returns' for command '%s'" % name,
694
               expr.get('returns'), allow_array=True,
695
               allow_optional=True, allow_metas=returns_meta)
696

E
Eric Blake 已提交
697

M
Marc-André Lureau 已提交
698
def check_event(expr, info):
699 700
    global events
    name = expr['event']
701
    boxed = expr.get('boxed', False)
702

703 704 705
    meta = ['struct']
    if boxed:
        meta += ['union', 'alternate']
706
    events.append(name)
M
Marc-André Lureau 已提交
707
    check_type(info, "'data' for event '%s'" % name,
708 709
               expr.get('data'), allow_dict=not boxed, allow_optional=True,
               allow_metas=meta)
W
Wenchao Xia 已提交
710

E
Eric Blake 已提交
711

M
Marc-André Lureau 已提交
712
def check_union(expr, info):
713 714 715 716 717
    name = expr['union']
    base = expr.get('base')
    discriminator = expr.get('discriminator')
    members = expr['data']

718 719 720 721
    # Two types of unions, determined by discriminator.

    # With no discriminator it is a simple union.
    if discriminator is None:
722
        enum_define = None
E
Eric Blake 已提交
723
        allow_metas = ['built-in', 'union', 'alternate', 'struct', 'enum']
E
Eric Blake 已提交
724
        if base is not None:
M
Marc-André Lureau 已提交
725 726
            raise QAPISemError(info, "Simple union '%s' must not have a base" %
                               name)
727 728 729

    # Else, it's a flat union.
    else:
730
        # The object must have a string or dictionary 'base'.
M
Marc-André Lureau 已提交
731
        check_type(info, "'base' for union '%s'" % name,
732 733
                   base, allow_dict=True, allow_optional=True,
                   allow_metas=['struct'])
734
        if not base:
M
Marc-André Lureau 已提交
735 736
            raise QAPISemError(info, "Flat union '%s' must have a base"
                               % name)
737
        base_members = find_base_members(base)
738
        assert base_members is not None
E
Eric Blake 已提交
739

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

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

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

E
Eric Blake 已提交
768
        # If the discriminator names an enum type, then all members
769
        # of 'data' must also be members of the enum type.
E
Eric Blake 已提交
770
        if enum_define:
E
Eric Blake 已提交
771
            if key not in enum_define['enum_values']:
M
Marc-André Lureau 已提交
772 773 774 775
                raise QAPISemError(info,
                                   "Discriminator value '%s' is not found in "
                                   "enum '%s'"
                                   % (key, enum_define["enum_name"]))
E
Eric Blake 已提交
776

777 778 779 780
    # 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 已提交
781 782
                raise QAPISemError(info, "Union '%s' data missing '%s' branch"
                                   % (name, value))
783

E
Eric Blake 已提交
784

M
Marc-André Lureau 已提交
785
def check_alternate(expr, info):
786
    name = expr['alternate']
787 788 789
    members = expr['data']
    types_seen = {}

790 791
    # Check every branch; require at least two branches
    if len(members) < 2:
M
Marc-André Lureau 已提交
792 793 794
        raise QAPISemError(info,
                           "Alternate '%s' should have at least two branches "
                           "in 'data'" % name)
795
    for (key, value) in members.items():
M
Marc-André Lureau 已提交
796
        check_name(info, "Member of alternate '%s'" % name, key)
E
Eric Blake 已提交
797

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

E
Eric Blake 已提交
812

M
Marc-André Lureau 已提交
813
def check_enum(expr, info):
814 815
    name = expr['enum']
    members = expr.get('data')
816
    prefix = expr.get('prefix')
817 818

    if not isinstance(members, list):
M
Marc-André Lureau 已提交
819 820
        raise QAPISemError(info,
                           "Enum '%s' requires an array for 'data'" % name)
821
    if prefix is not None and not isinstance(prefix, str):
M
Marc-André Lureau 已提交
822 823
        raise QAPISemError(info,
                           "Enum '%s' requires a string for 'prefix'" % name)
824
    for member in members:
M
Marc-André Lureau 已提交
825
        check_name(info, "Member of enum '%s'" % name, member,
E
Eric Blake 已提交
826
                   enum_member=True)
827

E
Eric Blake 已提交
828

M
Marc-André Lureau 已提交
829
def check_struct(expr, info):
830
    name = expr['struct']
831 832
    members = expr['data']

M
Marc-André Lureau 已提交
833
    check_type(info, "'data' for struct '%s'" % name, members,
E
Eric Blake 已提交
834
               allow_dict=True, allow_optional=True)
M
Marc-André Lureau 已提交
835
    check_type(info, "'base' for struct '%s'" % name, expr.get('base'),
836 837
               allow_metas=['struct'])

E
Eric Blake 已提交
838

839 840 841 842 843
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 已提交
844
        raise QAPISemError(info, "'%s' key must have a string value" % meta)
E
Eric Blake 已提交
845
    required = required + [meta]
846
    for (key, value) in expr.items():
E
Eric Blake 已提交
847
        if key not in required and key not in optional:
M
Marc-André Lureau 已提交
848 849
            raise QAPISemError(info, "Unknown key '%s' in %s '%s'"
                               % (key, meta, name))
E
Eric Blake 已提交
850
        if (key == 'gen' or key == 'success-response') and value is not False:
M
Marc-André Lureau 已提交
851 852 853
            raise QAPISemError(info,
                               "'%s' of %s '%s' should only use false value"
                               % (key, meta, name))
854
        if key == 'boxed' and value is not True:
M
Marc-André Lureau 已提交
855 856 857
            raise QAPISemError(info,
                               "'%s' of %s '%s' should only use true value"
                               % (key, meta, name))
858
    for key in required:
E
Eric Blake 已提交
859
        if key not in expr:
M
Marc-André Lureau 已提交
860 861
            raise QAPISemError(info, "Key '%s' is missing from %s '%s'"
                               % (key, meta, name))
862

E
Eric Blake 已提交
863

864
def check_exprs(exprs):
865 866
    global all_names

867 868 869 870 871 872
    # 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 已提交
873

874
        if 'doc' not in expr_elem and doc_required:
M
Marc-André Lureau 已提交
875 876 877
            raise QAPISemError(info,
                               "Expression missing documentation comment")

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

902 903 904
    # Try again for hidden UnionKind enum
    for expr_elem in exprs:
        expr = expr_elem['expr']
E
Eric Blake 已提交
905
        if 'union' in expr:
906 907
            if not discriminator_find_enum_define(expr):
                add_enum('%sKind' % expr['union'], expr_elem['info'],
908
                         implicit=True)
E
Eric Blake 已提交
909
        elif 'alternate' in expr:
910 911 912 913 914 915 916
            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']
917

E
Eric Blake 已提交
918
        if 'enum' in expr:
919
            check_enum(expr, info)
E
Eric Blake 已提交
920
        elif 'union' in expr:
921
            check_union(expr, info)
E
Eric Blake 已提交
922
        elif 'alternate' in expr:
923
            check_alternate(expr, info)
E
Eric Blake 已提交
924
        elif 'struct' in expr:
925
            check_struct(expr, info)
E
Eric Blake 已提交
926
        elif 'command' in expr:
927
            check_command(expr, info)
E
Eric Blake 已提交
928
        elif 'event' in expr:
929 930 931 932
            check_event(expr, info)
        else:
            assert False, 'unexpected meta type'

933 934 935
    return exprs


M
Marc-André Lureau 已提交
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 1010 1011 1012 1013 1014 1015 1016 1017
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


1018 1019 1020 1021 1022 1023 1024 1025
#
# Schema compiler frontend
#

class QAPISchemaEntity(object):
    def __init__(self, name, info):
        assert isinstance(name, str)
        self.name = name
1026 1027 1028 1029 1030
        # 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).
1031 1032
        self.info = info

1033 1034 1035
    def c_name(self):
        return c_name(self.name)

1036 1037 1038
    def check(self, schema):
        pass

1039 1040 1041
    def is_implicit(self):
        return not self.info

M
Markus Armbruster 已提交
1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052
    def visit(self, visitor):
        pass


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

    def visit_end(self):
        pass

1053 1054 1055 1056
    def visit_needed(self, entity):
        # Default to visiting everything
        return True

M
Markus Armbruster 已提交
1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068
    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

1069 1070 1071
    def visit_object_type_flat(self, name, info, members, variants):
        pass

M
Markus Armbruster 已提交
1072 1073 1074 1075
    def visit_alternate_type(self, name, info, variants):
        pass

    def visit_command(self, name, info, arg_type, ret_type,
1076
                      gen, success_response, boxed):
M
Markus Armbruster 已提交
1077 1078
        pass

1079
    def visit_event(self, name, info, arg_type, boxed):
M
Markus Armbruster 已提交
1080 1081
        pass

1082 1083

class QAPISchemaType(QAPISchemaEntity):
E
Eric Blake 已提交
1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095
    # 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()
1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108

    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())
1109 1110 1111


class QAPISchemaBuiltinType(QAPISchemaType):
E
Eric Blake 已提交
1112
    def __init__(self, name, json_type, c_type):
1113
        QAPISchemaType.__init__(self, name, None)
1114 1115 1116 1117 1118 1119 1120 1121 1122
        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 已提交
1123 1124 1125 1126 1127
    def c_type(self):
        return self._c_type_name

    def c_param_type(self):
        if self.name == 'str':
1128 1129 1130 1131 1132
            return 'const ' + self._c_type_name
        return self._c_type_name

    def json_type(self):
        return self._json_type_name
1133

M
Markus Armbruster 已提交
1134 1135 1136
    def visit(self, visitor):
        visitor.visit_builtin_type(self.name, self.info, self.json_type())

1137 1138 1139 1140 1141

class QAPISchemaEnumType(QAPISchemaType):
    def __init__(self, name, info, values, prefix):
        QAPISchemaType.__init__(self, name, info)
        for v in values:
1142 1143
            assert isinstance(v, QAPISchemaMember)
            v.set_owner(name)
1144 1145 1146 1147 1148
        assert prefix is None or isinstance(prefix, str)
        self.values = values
        self.prefix = prefix

    def check(self, schema):
1149 1150 1151
        seen = {}
        for v in self.values:
            v.check_clash(self.info, seen)
1152

1153 1154
    def is_implicit(self):
        # See QAPISchema._make_implicit_enum_type()
1155
        return self.name.endswith('Kind')
1156

E
Eric Blake 已提交
1157
    def c_type(self):
1158 1159
        return c_name(self.name)

1160 1161 1162
    def member_names(self):
        return [v.name for v in self.values]

1163 1164 1165
    def json_type(self):
        return 'string'

M
Markus Armbruster 已提交
1166 1167
    def visit(self, visitor):
        visitor.visit_enum_type(self.name, self.info,
1168
                                self.member_names(), self.prefix)
M
Markus Armbruster 已提交
1169

1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181

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

1182 1183 1184
    def is_implicit(self):
        return True

E
Eric Blake 已提交
1185 1186 1187
    def c_type(self):
        return c_name(self.name) + pointer_suffix

1188 1189 1190
    def json_type(self):
        return 'array'

M
Markus Armbruster 已提交
1191 1192 1193
    def visit(self, visitor):
        visitor.visit_array_type(self.name, self.info, self.element_type)

1194 1195 1196

class QAPISchemaObjectType(QAPISchemaType):
    def __init__(self, name, info, base, local_members, variants):
1197 1198 1199
        # 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
1200 1201 1202 1203
        QAPISchemaType.__init__(self, name, info)
        assert base is None or isinstance(base, str)
        for m in local_members:
            assert isinstance(m, QAPISchemaObjectTypeMember)
1204 1205 1206 1207
            m.set_owner(name)
        if variants is not None:
            assert isinstance(variants, QAPISchemaObjectTypeVariants)
            variants.set_owner(name)
1208 1209 1210 1211 1212 1213 1214
        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 已提交
1215
        if self.members is False:               # check for cycles
M
Marc-André Lureau 已提交
1216 1217
            raise QAPISemError(self.info,
                               "Object %s contains itself" % self.name)
1218 1219 1220
        if self.members:
            return
        self.members = False                    # mark as being checked
1221
        seen = OrderedDict()
1222 1223 1224 1225
        if self._base_name:
            self.base = schema.lookup_type(self._base_name)
            assert isinstance(self.base, QAPISchemaObjectType)
            self.base.check(schema)
1226
            self.base.check_clash(schema, self.info, seen)
1227
        for m in self.local_members:
1228
            m.check(schema)
1229
            m.check_clash(self.info, seen)
1230
        self.members = seen.values()
1231
        if self.variants:
1232
            self.variants.check(schema, seen)
1233
            assert self.variants.tag_member in self.members
1234
            self.variants.check_clash(schema, self.info, seen)
1235

1236
    # Check that the members of this type do not cause duplicate JSON members,
1237 1238 1239
    # 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):
1240 1241
        assert not self.variants       # not implemented
        for m in self.members:
1242
            m.check_clash(info, seen)
1243

1244
    def is_implicit(self):
1245 1246 1247
        # See QAPISchema._make_implicit_object_type(), as well as
        # _def_predefineds()
        return self.name.startswith('q_')
1248

E
Eric Blake 已提交
1249 1250 1251 1252
    def is_empty(self):
        assert self.members is not None
        return not self.members and not self.variants

1253
    def c_name(self):
1254
        assert self.name != 'q_empty'
1255 1256
        return QAPISchemaType.c_name(self)

E
Eric Blake 已提交
1257
    def c_type(self):
1258
        assert not self.is_implicit()
1259
        return c_name(self.name) + pointer_suffix
1260

E
Eric Blake 已提交
1261 1262 1263
    def c_unboxed_type(self):
        return c_name(self.name)

1264 1265 1266
    def json_type(self):
        return 'object'

M
Markus Armbruster 已提交
1267 1268 1269
    def visit(self, visitor):
        visitor.visit_object_type(self.name, self.info,
                                  self.base, self.local_members, self.variants)
1270 1271
        visitor.visit_object_type_flat(self.name, self.info,
                                       self.members, self.variants)
M
Markus Armbruster 已提交
1272

1273

1274
class QAPISchemaMember(object):
1275 1276
    role = 'member'

1277
    def __init__(self, name):
1278 1279
        assert isinstance(name, str)
        self.name = name
1280 1281 1282 1283 1284
        self.owner = None

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

1286 1287
    def check_clash(self, info, seen):
        cname = c_name(self.name)
1288
        if cname.lower() != cname and self.owner not in name_case_whitelist:
M
Marc-André Lureau 已提交
1289 1290
            raise QAPISemError(info,
                               "%s should not use uppercase" % self.describe())
1291
        if cname in seen:
M
Marc-André Lureau 已提交
1292 1293
            raise QAPISemError(info, "%s collides with %s" %
                               (self.describe(), seen[cname].describe()))
1294
        seen[cname] = self
1295

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

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

1318

1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333
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


1334
class QAPISchemaObjectTypeVariants(object):
1335 1336 1337 1338 1339 1340 1341 1342
    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))
1343
        assert len(variants) > 0
1344 1345
        for v in variants:
            assert isinstance(v, QAPISchemaObjectTypeVariant)
1346
        self._tag_name = tag_name
1347
        self.tag_member = tag_member
1348 1349
        self.variants = variants

1350 1351 1352 1353
    def set_owner(self, name):
        for v in self.variants:
            v.set_owner(name)

1354
    def check(self, schema, seen):
1355
        if not self.tag_member:    # flat union
1356 1357
            self.tag_member = seen[c_name(self._tag_name)]
            assert self._tag_name == self.tag_member.name
1358 1359
        assert isinstance(self.tag_member.type, QAPISchemaEnumType)
        for v in self.variants:
1360
            v.check(schema)
1361 1362 1363
            # Union names must match enum values; alternate names are
            # checked separately. Use 'seen' to tell the two apart.
            if seen:
1364
                assert v.name in self.tag_member.type.member_names()
1365
                assert isinstance(v.type, QAPISchemaObjectType)
1366 1367
                v.type.check(schema)

1368
    def check_clash(self, schema, info, seen):
1369 1370 1371 1372
        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)
1373
            v.type.check_clash(schema, info, dict(seen))
1374

E
Eric Blake 已提交
1375

1376
class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
1377 1378
    role = 'branch'

1379 1380 1381 1382 1383 1384 1385 1386
    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)
1387
        assert variants.tag_member
1388 1389
        variants.set_owner(name)
        variants.tag_member.set_owner(self.name)
1390 1391 1392
        self.variants = variants

    def check(self, schema):
1393
        self.variants.tag_member.check(schema)
1394 1395
        # Not calling self.variants.check_clash(), because there's nothing
        # to clash with
1396
        self.variants.check(schema, {})
1397 1398 1399 1400 1401
        # 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)
1402

E
Eric Blake 已提交
1403 1404 1405
    def c_type(self):
        return c_name(self.name) + pointer_suffix

1406 1407 1408
    def json_type(self):
        return 'value'

M
Markus Armbruster 已提交
1409 1410 1411
    def visit(self, visitor):
        visitor.visit_alternate_type(self.name, self.info, self.variants)

1412 1413 1414
    def is_empty(self):
        return False

1415 1416

class QAPISchemaCommand(QAPISchemaEntity):
1417 1418
    def __init__(self, name, info, arg_type, ret_type, gen, success_response,
                 boxed):
1419 1420 1421 1422 1423 1424 1425 1426 1427
        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
1428
        self.boxed = boxed
1429 1430 1431 1432

    def check(self, schema):
        if self._arg_type_name:
            self.arg_type = schema.lookup_type(self._arg_type_name)
1433 1434 1435 1436 1437
            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 已提交
1438 1439
                    raise QAPISemError(self.info,
                                       "Cannot use 'boxed' with empty type")
1440 1441 1442 1443
            else:
                assert not isinstance(self.arg_type, QAPISchemaAlternateType)
                assert not self.arg_type.variants
        elif self.boxed:
M
Marc-André Lureau 已提交
1444
            raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
1445 1446 1447 1448
        if self._ret_type_name:
            self.ret_type = schema.lookup_type(self._ret_type_name)
            assert isinstance(self.ret_type, QAPISchemaType)

M
Markus Armbruster 已提交
1449 1450 1451
    def visit(self, visitor):
        visitor.visit_command(self.name, self.info,
                              self.arg_type, self.ret_type,
1452
                              self.gen, self.success_response, self.boxed)
M
Markus Armbruster 已提交
1453

1454 1455

class QAPISchemaEvent(QAPISchemaEntity):
1456
    def __init__(self, name, info, arg_type, boxed):
1457 1458 1459 1460
        QAPISchemaEntity.__init__(self, name, info)
        assert not arg_type or isinstance(arg_type, str)
        self._arg_type_name = arg_type
        self.arg_type = None
1461
        self.boxed = boxed
1462 1463 1464 1465

    def check(self, schema):
        if self._arg_type_name:
            self.arg_type = schema.lookup_type(self._arg_type_name)
1466 1467 1468 1469 1470
            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 已提交
1471 1472
                    raise QAPISemError(self.info,
                                       "Cannot use 'boxed' with empty type")
1473 1474 1475 1476
            else:
                assert not isinstance(self.arg_type, QAPISchemaAlternateType)
                assert not self.arg_type.variants
        elif self.boxed:
M
Marc-André Lureau 已提交
1477
            raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
1478

M
Markus Armbruster 已提交
1479
    def visit(self, visitor):
1480
        visitor.visit_event(self.name, self.info, self.arg_type, self.boxed)
M
Markus Armbruster 已提交
1481

1482 1483 1484 1485

class QAPISchema(object):
    def __init__(self, fname):
        try:
M
Marc-André Lureau 已提交
1486 1487 1488
            parser = QAPISchemaParser(open(fname, "r"))
            self.exprs = check_exprs(parser.exprs)
            self.docs = check_docs(parser.docs)
1489
            self._entity_dict = {}
1490
            self._predefining = True
1491
            self._def_predefineds()
1492
            self._predefining = False
1493 1494
            self._def_exprs()
            self.check()
M
Marc-André Lureau 已提交
1495
        except QAPIError as err:
1496 1497 1498 1499
            print >>sys.stderr, err
            exit(1)

    def _def_entity(self, ent):
1500 1501
        # Only the predefined types are allowed to not have info
        assert ent.info or self._predefining
1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513
        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 已提交
1514 1515
    def _def_builtin_type(self, name, json_type, c_type):
        self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
E
Eric Blake 已提交
1516 1517 1518 1519 1520
        # 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.
1521
        self._make_array_type(name, None)
1522 1523

    def _def_predefineds(self):
E
Eric Blake 已提交
1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537
        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)]:
1538
            self._def_builtin_type(*t)
1539 1540
        self.the_empty_object_type = QAPISchemaObjectType('q_empty', None,
                                                          None, [], None)
1541
        self._def_entity(self.the_empty_object_type)
1542 1543 1544 1545
        qtype_values = self._make_enum_members(['none', 'qnull', 'qint',
                                                'qstring', 'qdict', 'qlist',
                                                'qfloat', 'qbool'])
        self._def_entity(QAPISchemaEnumType('QType', None, qtype_values,
1546
                                            'QTYPE'))
1547

1548 1549 1550
    def _make_enum_members(self, values):
        return [QAPISchemaMember(v) for v in values]

1551
    def _make_implicit_enum_type(self, name, info, values):
1552
        # See also QAPISchemaObjectTypeMember._pretty_owner()
1553
        name = name + 'Kind'   # Use namespace reserved by add_name()
1554 1555
        self._def_entity(QAPISchemaEnumType(
            name, info, self._make_enum_members(values), None))
1556 1557
        return name

1558
    def _make_array_type(self, element_type, info):
1559
        name = element_type + 'List'   # Use namespace reserved by add_name()
1560
        if not self.lookup_type(name):
1561
            self._def_entity(QAPISchemaArrayType(name, info, element_type))
1562 1563
        return name

1564
    def _make_implicit_object_type(self, name, info, role, members):
1565 1566
        if not members:
            return None
1567
        # See also QAPISchemaObjectTypeMember._pretty_owner()
1568
        name = 'q_obj_%s-%s' % (name, role)
1569
        if not self.lookup_entity(name, QAPISchemaObjectType):
1570
            self._def_entity(QAPISchemaObjectType(name, info, None,
1571 1572 1573 1574 1575 1576 1577
                                                  members, None))
        return name

    def _def_enum_type(self, expr, info):
        name = expr['enum']
        data = expr['data']
        prefix = expr.get('prefix')
1578 1579
        self._def_entity(QAPISchemaEnumType(
            name, info, self._make_enum_members(data), prefix))
1580

1581
    def _make_member(self, name, typ, info):
1582 1583 1584 1585 1586 1587
        optional = False
        if name.startswith('*'):
            name = name[1:]
            optional = True
        if isinstance(typ, list):
            assert len(typ) == 1
1588
            typ = self._make_array_type(typ[0], info)
1589 1590
        return QAPISchemaObjectTypeMember(name, typ, optional)

1591 1592
    def _make_members(self, data, info):
        return [self._make_member(key, value, info)
1593 1594 1595 1596 1597 1598 1599
                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,
1600
                                              self._make_members(data, info),
1601 1602 1603 1604 1605
                                              None))

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

1606
    def _make_simple_variant(self, case, typ, info):
1607 1608
        if isinstance(typ, list):
            assert len(typ) == 1
1609 1610 1611
            typ = self._make_array_type(typ[0], info)
        typ = self._make_implicit_object_type(
            typ, info, 'wrapper', [self._make_member('data', typ, info)])
1612 1613 1614 1615 1616 1617 1618
        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')
1619
        tag_member = None
1620 1621 1622
        if isinstance(base, dict):
            base = (self._make_implicit_object_type(
                    name, info, 'base', self._make_members(base, info)))
1623 1624 1625
        if tag_name:
            variants = [self._make_variant(key, value)
                        for (key, value) in data.iteritems()]
1626
            members = []
1627
        else:
1628
            variants = [self._make_simple_variant(key, value, info)
1629
                        for (key, value) in data.iteritems()]
E
Eric Blake 已提交
1630 1631 1632
            typ = self._make_implicit_enum_type(name, info,
                                                [v.name for v in variants])
            tag_member = QAPISchemaObjectTypeMember('type', typ, False)
1633
            members = [tag_member]
1634
        self._def_entity(
1635
            QAPISchemaObjectType(name, info, base, members,
1636
                                 QAPISchemaObjectTypeVariants(tag_name,
1637
                                                              tag_member,
1638 1639 1640 1641 1642 1643 1644
                                                              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()]
1645
        tag_member = QAPISchemaObjectTypeMember('type', 'QType', False)
1646 1647 1648
        self._def_entity(
            QAPISchemaAlternateType(name, info,
                                    QAPISchemaObjectTypeVariants(None,
1649
                                                                 tag_member,
1650 1651 1652 1653 1654 1655 1656 1657
                                                                 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)
1658
        boxed = expr.get('boxed', False)
1659
        if isinstance(data, OrderedDict):
1660 1661
            data = self._make_implicit_object_type(
                name, info, 'arg', self._make_members(data, info))
1662 1663
        if isinstance(rets, list):
            assert len(rets) == 1
1664
            rets = self._make_array_type(rets[0], info)
1665
        self._def_entity(QAPISchemaCommand(name, info, data, rets, gen,
1666
                                           success_response, boxed))
1667 1668 1669 1670

    def _def_event(self, expr, info):
        name = expr['event']
        data = expr.get('data')
1671
        boxed = expr.get('boxed', False)
1672
        if isinstance(data, OrderedDict):
1673 1674
            data = self._make_implicit_object_type(
                name, info, 'arg', self._make_members(data, info))
1675
        self._def_entity(QAPISchemaEvent(name, info, data, boxed))
1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698

    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)
1699

M
Markus Armbruster 已提交
1700
    def visit(self, visitor):
1701 1702 1703 1704
        visitor.visit_begin(self)
        for (name, entity) in sorted(self._entity_dict.items()):
            if visitor.visit_needed(entity):
                entity.visit(visitor)
M
Markus Armbruster 已提交
1705 1706
        visitor.visit_end()

1707

1708 1709 1710 1711
#
# Code generation helpers
#

1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724
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 已提交
1725

1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739
# 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 已提交
1740 1741 1742
            if i < l - 1 and c_fun_str[i + 1].islower():
                new_name += '_'
            elif c_fun_str[i - 1].isdigit():
1743 1744 1745 1746
                new_name += '_'
        new_name += c
    return new_name.lstrip('_').upper()

E
Eric Blake 已提交
1747

1748 1749 1750
def c_enum_const(type_name, const_name, prefix=None):
    if prefix is not None:
        type_name = prefix
1751
    return camel_to_upper(type_name) + '_' + c_name(const_name, False).upper()
1752

1753
c_name_trans = string.maketrans('.-', '__')
1754

E
Eric Blake 已提交
1755

1756 1757 1758 1759 1760 1761 1762 1763 1764
# 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'
1765
def c_name(name, protect=True):
B
Blue Swirl 已提交
1766 1767
    # ANSI X3J11/88-090, 3.1.1
    c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue',
E
Eric Blake 已提交
1768 1769 1770 1771 1772
                     '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 已提交
1773 1774 1775
    # 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 已提交
1776 1777
    c11_words = set(['_Alignas', '_Alignof', '_Atomic', '_Generic',
                     '_Noreturn', '_Static_assert', '_Thread_local'])
B
Blue Swirl 已提交
1778 1779 1780
    # GCC http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/C-Extensions.html
    # excluding _.*
    gcc_words = set(['asm', 'typeof'])
1781 1782 1783 1784 1785 1786 1787 1788 1789 1790
    # 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'])
1791
    # namespace pollution:
1792
    polluted_words = set(['unix', 'errno', 'mips', 'sparc'])
E
Eric Blake 已提交
1793
    name = name.translate(c_name_trans)
E
Eric Blake 已提交
1794 1795
    if protect and (name in c89_words | c99_words | c11_words | gcc_words
                    | cpp_words | polluted_words):
B
Blue Swirl 已提交
1796
        return "q_" + name
E
Eric Blake 已提交
1797
    return name
1798

1799
eatspace = '\033EATSPACE.'
E
Eric Blake 已提交
1800
pointer_suffix = ' *' + eatspace
1801

E
Eric Blake 已提交
1802

1803 1804
def genindent(count):
    ret = ""
E
Eric Blake 已提交
1805
    for _ in range(count):
1806 1807 1808 1809 1810
        ret += " "
    return ret

indent_level = 0

E
Eric Blake 已提交
1811

1812 1813 1814 1815
def push_indent(indent_amount=4):
    global indent_level
    indent_level += indent_amount

E
Eric Blake 已提交
1816

1817 1818 1819 1820
def pop_indent(indent_amount=4):
    global indent_level
    indent_level -= indent_amount

E
Eric Blake 已提交
1821

1822 1823
# Generate @code with @kwds interpolated.
# Obey indent_level, and strip eatspace.
1824
def cgen(code, **kwds):
1825 1826 1827
    raw = code % kwds
    if indent_level:
        indent = genindent(indent_level)
1828 1829 1830
        # re.subn() lacks flags support before Python 2.7, use re.compile()
        raw = re.subn(re.compile("^.", re.MULTILINE),
                      indent + r'\g<0>', raw)
1831 1832
        raw = raw[0]
    return re.sub(re.escape(eatspace) + ' *', '', raw)
1833

E
Eric Blake 已提交
1834

1835
def mcgen(code, **kwds):
1836 1837 1838
    if code[0] == '\n':
        code = code[1:]
    return cgen(code, **kwds)
1839 1840 1841


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

E
Eric Blake 已提交
1844

1845 1846 1847 1848 1849 1850 1851 1852 1853
def guardstart(name):
    return mcgen('''

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

''',
                 name=guardname(name))

E
Eric Blake 已提交
1854

1855 1856 1857 1858 1859 1860 1861
def guardend(name):
    return mcgen('''

#endif /* %(name)s */

''',
                 name=guardname(name))
1862

E
Eric Blake 已提交
1863

1864
def gen_enum_lookup(name, values, prefix=None):
1865 1866
    ret = mcgen('''

1867
const char *const %(c_name)s_lookup[] = {
1868
''',
1869
                c_name=c_name(name))
1870 1871 1872 1873 1874
    for value in values:
        index = c_enum_const(name, value, prefix)
        ret += mcgen('''
    [%(index)s] = "%(value)s",
''',
1875
                     index=index, value=value)
1876

1877
    max_index = c_enum_const(name, '_MAX', prefix)
1878 1879 1880 1881
    ret += mcgen('''
    [%(max_index)s] = NULL,
};
''',
1882
                 max_index=max_index)
1883 1884
    return ret

E
Eric Blake 已提交
1885

1886 1887
def gen_enum(name, values, prefix=None):
    # append automatically generated _MAX value
1888
    enum_values = values + ['_MAX']
1889

1890
    ret = mcgen('''
1891

1892
typedef enum %(c_name)s {
1893
''',
1894
                c_name=c_name(name))
1895 1896 1897

    i = 0
    for value in enum_values:
1898 1899
        ret += mcgen('''
    %(c_enum)s = %(i)d,
1900
''',
1901
                     c_enum=c_enum_const(name, value, prefix),
1902 1903 1904
                     i=i)
        i += 1

1905 1906
    ret += mcgen('''
} %(c_name)s;
1907
''',
1908 1909 1910
                 c_name=c_name(name))

    ret += mcgen('''
1911

1912 1913 1914 1915
extern const char *const %(c_name)s_lookup[];
''',
                 c_name=c_name(name))
    return ret
1916

E
Eric Blake 已提交
1917

1918
def gen_params(arg_type, boxed, extra):
1919
    if not arg_type:
1920
        assert not boxed
1921 1922 1923
        return extra
    ret = ''
    sep = ''
1924
    if boxed:
1925 1926
        ret += '%s arg' % arg_type.c_param_type()
        sep = ', '
1927 1928 1929 1930 1931 1932 1933 1934 1935
    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))
1936 1937 1938 1939
    if extra:
        ret += sep + extra
    return ret

E
Eric Blake 已提交
1940

1941 1942 1943 1944
#
# Common command line parsing
#

E
Eric Blake 已提交
1945 1946

def parse_command_line(extra_options="", extra_long_options=[]):
1947 1948 1949

    try:
        opts, args = getopt.gnu_getopt(sys.argv[1:],
1950
                                       "chp:o:" + extra_options,
1951
                                       ["source", "header", "prefix=",
1952
                                        "output-dir="] + extra_long_options)
1953
    except getopt.GetoptError as err:
1954
        print >>sys.stderr, "%s: %s" % (sys.argv[0], str(err))
1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965
        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"):
1966 1967 1968 1969 1970 1971
            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)
1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985
            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

1986 1987
    if len(args) != 1:
        print >>sys.stderr, "%s: need exactly one argument" % sys.argv[0]
1988
        sys.exit(1)
1989
    fname = args[0]
1990

1991
    return (fname, output_dir, do_c, do_h, prefix, extra_opts)
1992

1993 1994 1995 1996
#
# Generate output files with boilerplate
#

E
Eric Blake 已提交
1997

1998 1999
def open_output(output_dir, do_c, do_h, prefix, c_file, h_file,
                c_comment, h_comment):
M
Markus Armbruster 已提交
2000
    guard = guardname(prefix + h_file)
2001 2002 2003
    c_file = output_dir + prefix + c_file
    h_file = output_dir + prefix + h_file

2004 2005 2006
    if output_dir:
        try:
            os.makedirs(output_dir)
2007
        except os.error as e:
2008 2009
            if e.errno != errno.EEXIST:
                raise
2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024

    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 已提交
2025
                     comment=c_comment))
2026 2027 2028 2029 2030 2031 2032 2033

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

''',
E
Eric Blake 已提交
2034
                      comment=h_comment, guard=guard))
2035 2036 2037

    return (fdef, fdecl)

E
Eric Blake 已提交
2038

2039 2040 2041 2042 2043 2044
def close_output(fdef, fdecl):
    fdecl.write('''
#endif
''')
    fdecl.close()
    fdef.close()