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