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