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