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