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