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