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