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