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