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