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