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 'enum' in expr: 937 meta = 'enum' 938 elif 'union' in expr: 939 meta = 'union' 940 elif 'alternate' in expr: 941 meta = 'alternate' 942 elif 'struct' in expr: 943 meta = 'struct' 944 elif 'command' in expr: 945 meta = 'command' 946 elif 'event' in expr: 947 meta = 'event' 948 else: 949 raise QAPISemError(info, "expression is missing metatype") 950 951 name = expr[meta] 952 check_name_is_str(name, info, "'%s'" % meta) 953 info.set_defn(meta, name) 954 check_defn_name_str(name, info, meta) 955 956 if doc: 957 if doc.symbol != name: 958 raise QAPISemError( 959 info, "documentation comment is for '%s'" % doc.symbol) 960 doc.check_expr(expr) 961 elif doc_required: 962 raise QAPISemError(info, 963 "documentation comment required") 964 965 if meta == 'enum': 966 check_keys(expr, info, meta, 967 ['enum', 'data'], ['if', 'prefix']) 968 normalize_enum(expr) 969 check_enum(expr, info) 970 elif meta == 'union': 971 check_keys(expr, info, meta, 972 ['union', 'data'], 973 ['base', 'discriminator', 'if']) 974 normalize_members(expr.get('base')) 975 normalize_members(expr['data']) 976 check_union(expr, info) 977 elif meta == 'alternate': 978 check_keys(expr, info, meta, 979 ['alternate', 'data'], ['if']) 980 normalize_members(expr['data']) 981 check_alternate(expr, info) 982 elif meta == 'struct': 983 check_keys(expr, info, meta, 984 ['struct', 'data'], ['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, meta, 990 ['command'], 991 ['data', 'returns', 'boxed', 'if', 992 'gen', 'success-response', 'allow-oob', 993 'allow-preconfig']) 994 normalize_members(expr.get('data')) 995 check_command(expr, info) 996 elif meta == 'event': 997 check_keys(expr, info, meta, 998 ['event'], ['data', 'boxed', 'if']) 999 normalize_members(expr.get('data')) 1000 check_event(expr, info) 1001 else: 1002 assert False, 'unexpected meta type' 1003 1004 normalize_if(expr) 1005 check_if(expr, info, meta) 1006 check_flags(expr, info) 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 if other_ent.info: 1763 where = QAPIError(other_ent.info, None, "previous definition") 1764 raise QAPISemError( 1765 ent.info, 1766 "'%s' is already defined\n%s" % (ent.name, where)) 1767 raise QAPISemError( 1768 ent.info, "%s is already defined" % other_ent.describe()) 1769 self._entity_dict[ent.name] = ent 1770 1771 def lookup_entity(self, name, typ=None): 1772 ent = self._entity_dict.get(name) 1773 if typ and not isinstance(ent, typ): 1774 return None 1775 return ent 1776 1777 def lookup_type(self, name): 1778 return self.lookup_entity(name, QAPISchemaType) 1779 1780 def resolve_type(self, name, info, what): 1781 typ = self.lookup_type(name) 1782 if not typ: 1783 if callable(what): 1784 what = what(info) 1785 raise QAPISemError( 1786 info, "%s uses unknown type '%s'" % (what, name)) 1787 return typ 1788 1789 def _def_include(self, expr, info, doc): 1790 include = expr['include'] 1791 assert doc is None 1792 main_info = info 1793 while main_info.parent: 1794 main_info = main_info.parent 1795 fname = os.path.relpath(include, os.path.dirname(main_info.fname)) 1796 self._def_entity(QAPISchemaInclude(fname, info)) 1797 1798 def _def_builtin_type(self, name, json_type, c_type): 1799 self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type)) 1800 # Instantiating only the arrays that are actually used would 1801 # be nice, but we can't as long as their generated code 1802 # (qapi-builtin-types.[ch]) may be shared by some other 1803 # schema. 1804 self._make_array_type(name, None) 1805 1806 def _def_predefineds(self): 1807 for t in [('str', 'string', 'char' + pointer_suffix), 1808 ('number', 'number', 'double'), 1809 ('int', 'int', 'int64_t'), 1810 ('int8', 'int', 'int8_t'), 1811 ('int16', 'int', 'int16_t'), 1812 ('int32', 'int', 'int32_t'), 1813 ('int64', 'int', 'int64_t'), 1814 ('uint8', 'int', 'uint8_t'), 1815 ('uint16', 'int', 'uint16_t'), 1816 ('uint32', 'int', 'uint32_t'), 1817 ('uint64', 'int', 'uint64_t'), 1818 ('size', 'int', 'uint64_t'), 1819 ('bool', 'boolean', 'bool'), 1820 ('any', 'value', 'QObject' + pointer_suffix), 1821 ('null', 'null', 'QNull' + pointer_suffix)]: 1822 self._def_builtin_type(*t) 1823 self.the_empty_object_type = QAPISchemaObjectType( 1824 'q_empty', None, None, None, None, [], None, []) 1825 self._def_entity(self.the_empty_object_type) 1826 1827 qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 1828 'qbool'] 1829 qtype_values = self._make_enum_members( 1830 [{'name': n} for n in qtypes], None) 1831 1832 self._def_entity(QAPISchemaEnumType('QType', None, None, None, 1833 qtype_values, 'QTYPE')) 1834 1835 def _make_features(self, features, info): 1836 return [QAPISchemaFeature(f['name'], info, f.get('if')) 1837 for f in features] 1838 1839 def _make_enum_members(self, values, info): 1840 return [QAPISchemaEnumMember(v['name'], info, v.get('if')) 1841 for v in values] 1842 1843 def _make_implicit_enum_type(self, name, info, ifcond, values): 1844 # See also QAPISchemaObjectTypeMember.describe() 1845 name = name + 'Kind' # reserved by check_defn_name_str() 1846 self._def_entity(QAPISchemaEnumType( 1847 name, info, None, ifcond, self._make_enum_members(values, info), 1848 None)) 1849 return name 1850 1851 def _make_array_type(self, element_type, info): 1852 name = element_type + 'List' # reserved by check_defn_name_str() 1853 if not self.lookup_type(name): 1854 self._def_entity(QAPISchemaArrayType(name, info, element_type)) 1855 return name 1856 1857 def _make_implicit_object_type(self, name, info, doc, ifcond, 1858 role, members): 1859 if not members: 1860 return None 1861 # See also QAPISchemaObjectTypeMember.describe() 1862 name = 'q_obj_%s-%s' % (name, role) 1863 typ = self.lookup_entity(name, QAPISchemaObjectType) 1864 if typ: 1865 # The implicit object type has multiple users. This can 1866 # happen only for simple unions' implicit wrapper types. 1867 # Its ifcond should be the disjunction of its user's 1868 # ifconds. Not implemented. Instead, we always pass the 1869 # wrapped type's ifcond, which is trivially the same for all 1870 # users. It's also necessary for the wrapper to compile. 1871 # But it's not tight: the disjunction need not imply it. We 1872 # may end up compiling useless wrapper types. 1873 # TODO kill simple unions or implement the disjunction 1874 assert (ifcond or []) == typ._ifcond # pylint: disable=protected-access 1875 else: 1876 self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond, 1877 None, members, None, [])) 1878 return name 1879 1880 def _def_enum_type(self, expr, info, doc): 1881 name = expr['enum'] 1882 data = expr['data'] 1883 prefix = expr.get('prefix') 1884 ifcond = expr.get('if') 1885 self._def_entity(QAPISchemaEnumType( 1886 name, info, doc, ifcond, 1887 self._make_enum_members(data, info), prefix)) 1888 1889 def _make_member(self, name, typ, ifcond, info): 1890 optional = False 1891 if name.startswith('*'): 1892 name = name[1:] 1893 optional = True 1894 if isinstance(typ, list): 1895 assert len(typ) == 1 1896 typ = self._make_array_type(typ[0], info) 1897 return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond) 1898 1899 def _make_members(self, data, info): 1900 return [self._make_member(key, value['type'], value.get('if'), info) 1901 for (key, value) in data.items()] 1902 1903 def _def_struct_type(self, expr, info, doc): 1904 name = expr['struct'] 1905 base = expr.get('base') 1906 data = expr['data'] 1907 ifcond = expr.get('if') 1908 features = expr.get('features', []) 1909 self._def_entity(QAPISchemaObjectType( 1910 name, info, doc, ifcond, base, 1911 self._make_members(data, info), 1912 None, 1913 self._make_features(features, info))) 1914 1915 def _make_variant(self, case, typ, ifcond, info): 1916 return QAPISchemaObjectTypeVariant(case, info, typ, ifcond) 1917 1918 def _make_simple_variant(self, case, typ, ifcond, info): 1919 if isinstance(typ, list): 1920 assert len(typ) == 1 1921 typ = self._make_array_type(typ[0], info) 1922 typ = self._make_implicit_object_type( 1923 typ, info, None, self.lookup_type(typ), 1924 'wrapper', [self._make_member('data', typ, None, info)]) 1925 return QAPISchemaObjectTypeVariant(case, info, typ, ifcond) 1926 1927 def _def_union_type(self, expr, info, doc): 1928 name = expr['union'] 1929 data = expr['data'] 1930 base = expr.get('base') 1931 ifcond = expr.get('if') 1932 tag_name = expr.get('discriminator') 1933 tag_member = None 1934 if isinstance(base, dict): 1935 base = self._make_implicit_object_type( 1936 name, info, doc, ifcond, 1937 'base', self._make_members(base, info)) 1938 if tag_name: 1939 variants = [self._make_variant(key, value['type'], 1940 value.get('if'), info) 1941 for (key, value) in data.items()] 1942 members = [] 1943 else: 1944 variants = [self._make_simple_variant(key, value['type'], 1945 value.get('if'), info) 1946 for (key, value) in data.items()] 1947 enum = [{'name': v.name, 'if': v.ifcond} for v in variants] 1948 typ = self._make_implicit_enum_type(name, info, ifcond, enum) 1949 tag_member = QAPISchemaObjectTypeMember('type', info, typ, False) 1950 members = [tag_member] 1951 self._def_entity( 1952 QAPISchemaObjectType(name, info, doc, ifcond, base, members, 1953 QAPISchemaObjectTypeVariants( 1954 tag_name, info, tag_member, variants), 1955 [])) 1956 1957 def _def_alternate_type(self, expr, info, doc): 1958 name = expr['alternate'] 1959 data = expr['data'] 1960 ifcond = expr.get('if') 1961 variants = [self._make_variant(key, value['type'], value.get('if'), 1962 info) 1963 for (key, value) in data.items()] 1964 tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False) 1965 self._def_entity( 1966 QAPISchemaAlternateType(name, info, doc, ifcond, 1967 QAPISchemaObjectTypeVariants( 1968 None, info, tag_member, variants))) 1969 1970 def _def_command(self, expr, info, doc): 1971 name = expr['command'] 1972 data = expr.get('data') 1973 rets = expr.get('returns') 1974 gen = expr.get('gen', True) 1975 success_response = expr.get('success-response', True) 1976 boxed = expr.get('boxed', False) 1977 allow_oob = expr.get('allow-oob', False) 1978 allow_preconfig = expr.get('allow-preconfig', False) 1979 ifcond = expr.get('if') 1980 if isinstance(data, OrderedDict): 1981 data = self._make_implicit_object_type( 1982 name, info, doc, ifcond, 'arg', self._make_members(data, info)) 1983 if isinstance(rets, list): 1984 assert len(rets) == 1 1985 rets = self._make_array_type(rets[0], info) 1986 self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, data, rets, 1987 gen, success_response, 1988 boxed, allow_oob, allow_preconfig)) 1989 1990 def _def_event(self, expr, info, doc): 1991 name = expr['event'] 1992 data = expr.get('data') 1993 boxed = expr.get('boxed', False) 1994 ifcond = expr.get('if') 1995 if isinstance(data, OrderedDict): 1996 data = self._make_implicit_object_type( 1997 name, info, doc, ifcond, 'arg', self._make_members(data, info)) 1998 self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, data, boxed)) 1999 2000 def _def_exprs(self, exprs): 2001 for expr_elem in exprs: 2002 expr = expr_elem['expr'] 2003 info = expr_elem['info'] 2004 doc = expr_elem.get('doc') 2005 if 'enum' in expr: 2006 self._def_enum_type(expr, info, doc) 2007 elif 'struct' in expr: 2008 self._def_struct_type(expr, info, doc) 2009 elif 'union' in expr: 2010 self._def_union_type(expr, info, doc) 2011 elif 'alternate' in expr: 2012 self._def_alternate_type(expr, info, doc) 2013 elif 'command' in expr: 2014 self._def_command(expr, info, doc) 2015 elif 'event' in expr: 2016 self._def_event(expr, info, doc) 2017 elif 'include' in expr: 2018 self._def_include(expr, info, doc) 2019 else: 2020 assert False 2021 2022 def check(self): 2023 for ent in self._entity_list: 2024 ent.check(self) 2025 2026 def visit(self, visitor): 2027 visitor.visit_begin(self) 2028 module = None 2029 visitor.visit_module(module) 2030 for entity in self._entity_list: 2031 if visitor.visit_needed(entity): 2032 if entity.module != module: 2033 module = entity.module 2034 visitor.visit_module(module) 2035 entity.visit(visitor) 2036 visitor.visit_end() 2037 2038 2039# 2040# Code generation helpers 2041# 2042 2043def camel_case(name): 2044 new_name = '' 2045 first = True 2046 for ch in name: 2047 if ch in ['_', '-']: 2048 first = True 2049 elif first: 2050 new_name += ch.upper() 2051 first = False 2052 else: 2053 new_name += ch.lower() 2054 return new_name 2055 2056 2057# ENUMName -> ENUM_NAME, EnumName1 -> ENUM_NAME1 2058# ENUM_NAME -> ENUM_NAME, ENUM_NAME1 -> ENUM_NAME1, ENUM_Name2 -> ENUM_NAME2 2059# ENUM24_Name -> ENUM24_NAME 2060def camel_to_upper(value): 2061 c_fun_str = c_name(value, False) 2062 if value.isupper(): 2063 return c_fun_str 2064 2065 new_name = '' 2066 length = len(c_fun_str) 2067 for i in range(length): 2068 c = c_fun_str[i] 2069 # When c is upper and no '_' appears before, do more checks 2070 if c.isupper() and (i > 0) and c_fun_str[i - 1] != '_': 2071 if i < length - 1 and c_fun_str[i + 1].islower(): 2072 new_name += '_' 2073 elif c_fun_str[i - 1].isdigit(): 2074 new_name += '_' 2075 new_name += c 2076 return new_name.lstrip('_').upper() 2077 2078 2079def c_enum_const(type_name, const_name, prefix=None): 2080 if prefix is not None: 2081 type_name = prefix 2082 return camel_to_upper(type_name) + '_' + c_name(const_name, False).upper() 2083 2084 2085if hasattr(str, 'maketrans'): 2086 c_name_trans = str.maketrans('.-', '__') 2087else: 2088 c_name_trans = string.maketrans('.-', '__') 2089 2090 2091# Map @name to a valid C identifier. 2092# If @protect, avoid returning certain ticklish identifiers (like 2093# C keywords) by prepending 'q_'. 2094# 2095# Used for converting 'name' from a 'name':'type' qapi definition 2096# into a generated struct member, as well as converting type names 2097# into substrings of a generated C function name. 2098# '__a.b_c' -> '__a_b_c', 'x-foo' -> 'x_foo' 2099# protect=True: 'int' -> 'q_int'; protect=False: 'int' -> 'int' 2100def c_name(name, protect=True): 2101 # ANSI X3J11/88-090, 3.1.1 2102 c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue', 2103 'default', 'do', 'double', 'else', 'enum', 'extern', 2104 'float', 'for', 'goto', 'if', 'int', 'long', 'register', 2105 'return', 'short', 'signed', 'sizeof', 'static', 2106 'struct', 'switch', 'typedef', 'union', 'unsigned', 2107 'void', 'volatile', 'while']) 2108 # ISO/IEC 9899:1999, 6.4.1 2109 c99_words = set(['inline', 'restrict', '_Bool', '_Complex', '_Imaginary']) 2110 # ISO/IEC 9899:2011, 6.4.1 2111 c11_words = set(['_Alignas', '_Alignof', '_Atomic', '_Generic', 2112 '_Noreturn', '_Static_assert', '_Thread_local']) 2113 # GCC http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/C-Extensions.html 2114 # excluding _.* 2115 gcc_words = set(['asm', 'typeof']) 2116 # C++ ISO/IEC 14882:2003 2.11 2117 cpp_words = set(['bool', 'catch', 'class', 'const_cast', 'delete', 2118 'dynamic_cast', 'explicit', 'false', 'friend', 'mutable', 2119 'namespace', 'new', 'operator', 'private', 'protected', 2120 'public', 'reinterpret_cast', 'static_cast', 'template', 2121 'this', 'throw', 'true', 'try', 'typeid', 'typename', 2122 'using', 'virtual', 'wchar_t', 2123 # alternative representations 2124 'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not', 2125 'not_eq', 'or', 'or_eq', 'xor', 'xor_eq']) 2126 # namespace pollution: 2127 polluted_words = set(['unix', 'errno', 'mips', 'sparc', 'i386']) 2128 name = name.translate(c_name_trans) 2129 if protect and (name in c89_words | c99_words | c11_words | gcc_words 2130 | cpp_words | polluted_words): 2131 return 'q_' + name 2132 return name 2133 2134 2135eatspace = '\033EATSPACE.' 2136pointer_suffix = ' *' + eatspace 2137 2138 2139def genindent(count): 2140 ret = '' 2141 for _ in range(count): 2142 ret += ' ' 2143 return ret 2144 2145 2146indent_level = 0 2147 2148 2149def push_indent(indent_amount=4): 2150 global indent_level 2151 indent_level += indent_amount 2152 2153 2154def pop_indent(indent_amount=4): 2155 global indent_level 2156 indent_level -= indent_amount 2157 2158 2159# Generate @code with @kwds interpolated. 2160# Obey indent_level, and strip eatspace. 2161def cgen(code, **kwds): 2162 raw = code % kwds 2163 if indent_level: 2164 indent = genindent(indent_level) 2165 # re.subn() lacks flags support before Python 2.7, use re.compile() 2166 raw = re.subn(re.compile(r'^(?!(#|$))', re.MULTILINE), 2167 indent, raw) 2168 raw = raw[0] 2169 return re.sub(re.escape(eatspace) + r' *', '', raw) 2170 2171 2172def mcgen(code, **kwds): 2173 if code[0] == '\n': 2174 code = code[1:] 2175 return cgen(code, **kwds) 2176 2177 2178def c_fname(filename): 2179 return re.sub(r'[^A-Za-z0-9_]', '_', filename) 2180 2181 2182def guardstart(name): 2183 return mcgen(''' 2184#ifndef %(name)s 2185#define %(name)s 2186 2187''', 2188 name=c_fname(name).upper()) 2189 2190 2191def guardend(name): 2192 return mcgen(''' 2193 2194#endif /* %(name)s */ 2195''', 2196 name=c_fname(name).upper()) 2197 2198 2199def gen_if(ifcond): 2200 ret = '' 2201 for ifc in ifcond: 2202 ret += mcgen(''' 2203#if %(cond)s 2204''', cond=ifc) 2205 return ret 2206 2207 2208def gen_endif(ifcond): 2209 ret = '' 2210 for ifc in reversed(ifcond): 2211 ret += mcgen(''' 2212#endif /* %(cond)s */ 2213''', cond=ifc) 2214 return ret 2215 2216 2217def _wrap_ifcond(ifcond, before, after): 2218 if before == after: 2219 return after # suppress empty #if ... #endif 2220 2221 assert after.startswith(before) 2222 out = before 2223 added = after[len(before):] 2224 if added[0] == '\n': 2225 out += '\n' 2226 added = added[1:] 2227 out += gen_if(ifcond) 2228 out += added 2229 out += gen_endif(ifcond) 2230 return out 2231 2232 2233def gen_enum_lookup(name, members, prefix=None): 2234 ret = mcgen(''' 2235 2236const QEnumLookup %(c_name)s_lookup = { 2237 .array = (const char *const[]) { 2238''', 2239 c_name=c_name(name)) 2240 for m in members: 2241 ret += gen_if(m.ifcond) 2242 index = c_enum_const(name, m.name, prefix) 2243 ret += mcgen(''' 2244 [%(index)s] = "%(name)s", 2245''', 2246 index=index, name=m.name) 2247 ret += gen_endif(m.ifcond) 2248 2249 ret += mcgen(''' 2250 }, 2251 .size = %(max_index)s 2252}; 2253''', 2254 max_index=c_enum_const(name, '_MAX', prefix)) 2255 return ret 2256 2257 2258def gen_enum(name, members, prefix=None): 2259 # append automatically generated _MAX value 2260 enum_members = members + [QAPISchemaEnumMember('_MAX', None)] 2261 2262 ret = mcgen(''' 2263 2264typedef enum %(c_name)s { 2265''', 2266 c_name=c_name(name)) 2267 2268 for m in enum_members: 2269 ret += gen_if(m.ifcond) 2270 ret += mcgen(''' 2271 %(c_enum)s, 2272''', 2273 c_enum=c_enum_const(name, m.name, prefix)) 2274 ret += gen_endif(m.ifcond) 2275 2276 ret += mcgen(''' 2277} %(c_name)s; 2278''', 2279 c_name=c_name(name)) 2280 2281 ret += mcgen(''' 2282 2283#define %(c_name)s_str(val) \\ 2284 qapi_enum_lookup(&%(c_name)s_lookup, (val)) 2285 2286extern const QEnumLookup %(c_name)s_lookup; 2287''', 2288 c_name=c_name(name)) 2289 return ret 2290 2291 2292def build_params(arg_type, boxed, extra=None): 2293 ret = '' 2294 sep = '' 2295 if boxed: 2296 assert arg_type 2297 ret += '%s arg' % arg_type.c_param_type() 2298 sep = ', ' 2299 elif arg_type: 2300 assert not arg_type.variants 2301 for memb in arg_type.members: 2302 ret += sep 2303 sep = ', ' 2304 if memb.optional: 2305 ret += 'bool has_%s, ' % c_name(memb.name) 2306 ret += '%s %s' % (memb.type.c_param_type(), 2307 c_name(memb.name)) 2308 if extra: 2309 ret += sep + extra 2310 return ret if ret else 'void' 2311 2312 2313# 2314# Accumulate and write output 2315# 2316 2317class QAPIGen(object): 2318 2319 def __init__(self, fname): 2320 self.fname = fname 2321 self._preamble = '' 2322 self._body = '' 2323 2324 def preamble_add(self, text): 2325 self._preamble += text 2326 2327 def add(self, text): 2328 self._body += text 2329 2330 def get_content(self): 2331 return self._top() + self._preamble + self._body + self._bottom() 2332 2333 def _top(self): 2334 return '' 2335 2336 def _bottom(self): 2337 return '' 2338 2339 def write(self, output_dir): 2340 pathname = os.path.join(output_dir, self.fname) 2341 dir = os.path.dirname(pathname) 2342 if dir: 2343 try: 2344 os.makedirs(dir) 2345 except os.error as e: 2346 if e.errno != errno.EEXIST: 2347 raise 2348 fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666) 2349 if sys.version_info[0] >= 3: 2350 f = open(fd, 'r+', encoding='utf-8') 2351 else: 2352 f = os.fdopen(fd, 'r+') 2353 text = self.get_content() 2354 oldtext = f.read(len(text) + 1) 2355 if text != oldtext: 2356 f.seek(0) 2357 f.truncate(0) 2358 f.write(text) 2359 f.close() 2360 2361 2362@contextmanager 2363def ifcontext(ifcond, *args): 2364 """A 'with' statement context manager to wrap with start_if()/end_if() 2365 2366 *args: any number of QAPIGenCCode 2367 2368 Example:: 2369 2370 with ifcontext(ifcond, self._genh, self._genc): 2371 modify self._genh and self._genc ... 2372 2373 Is equivalent to calling:: 2374 2375 self._genh.start_if(ifcond) 2376 self._genc.start_if(ifcond) 2377 modify self._genh and self._genc ... 2378 self._genh.end_if() 2379 self._genc.end_if() 2380 """ 2381 for arg in args: 2382 arg.start_if(ifcond) 2383 yield 2384 for arg in args: 2385 arg.end_if() 2386 2387 2388class QAPIGenCCode(QAPIGen): 2389 2390 def __init__(self, fname): 2391 QAPIGen.__init__(self, fname) 2392 self._start_if = None 2393 2394 def start_if(self, ifcond): 2395 assert self._start_if is None 2396 self._start_if = (ifcond, self._body, self._preamble) 2397 2398 def end_if(self): 2399 assert self._start_if 2400 self._wrap_ifcond() 2401 self._start_if = None 2402 2403 def _wrap_ifcond(self): 2404 self._body = _wrap_ifcond(self._start_if[0], 2405 self._start_if[1], self._body) 2406 self._preamble = _wrap_ifcond(self._start_if[0], 2407 self._start_if[2], self._preamble) 2408 2409 def get_content(self): 2410 assert self._start_if is None 2411 return QAPIGen.get_content(self) 2412 2413 2414class QAPIGenC(QAPIGenCCode): 2415 2416 def __init__(self, fname, blurb, pydoc): 2417 QAPIGenCCode.__init__(self, fname) 2418 self._blurb = blurb 2419 self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc, 2420 re.MULTILINE)) 2421 2422 def _top(self): 2423 return mcgen(''' 2424/* AUTOMATICALLY GENERATED, DO NOT MODIFY */ 2425 2426/* 2427%(blurb)s 2428 * 2429 * %(copyright)s 2430 * 2431 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. 2432 * See the COPYING.LIB file in the top-level directory. 2433 */ 2434 2435''', 2436 blurb=self._blurb, copyright=self._copyright) 2437 2438 def _bottom(self): 2439 return mcgen(''' 2440 2441/* Dummy declaration to prevent empty .o file */ 2442char qapi_dummy_%(name)s; 2443''', 2444 name=c_fname(self.fname)) 2445 2446 2447class QAPIGenH(QAPIGenC): 2448 2449 def _top(self): 2450 return QAPIGenC._top(self) + guardstart(self.fname) 2451 2452 def _bottom(self): 2453 return guardend(self.fname) 2454 2455 2456class QAPIGenDoc(QAPIGen): 2457 2458 def _top(self): 2459 return (QAPIGen._top(self) 2460 + '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n') 2461 2462 2463class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor): 2464 2465 def __init__(self, prefix, what, blurb, pydoc): 2466 self._prefix = prefix 2467 self._what = what 2468 self._genc = QAPIGenC(self._prefix + self._what + '.c', 2469 blurb, pydoc) 2470 self._genh = QAPIGenH(self._prefix + self._what + '.h', 2471 blurb, pydoc) 2472 2473 def write(self, output_dir): 2474 self._genc.write(output_dir) 2475 self._genh.write(output_dir) 2476 2477 2478class QAPISchemaModularCVisitor(QAPISchemaVisitor): 2479 2480 def __init__(self, prefix, what, blurb, pydoc): 2481 self._prefix = prefix 2482 self._what = what 2483 self._blurb = blurb 2484 self._pydoc = pydoc 2485 self._genc = None 2486 self._genh = None 2487 self._module = {} 2488 self._main_module = None 2489 2490 @staticmethod 2491 def _is_user_module(name): 2492 return name and not name.startswith('./') 2493 2494 @staticmethod 2495 def _is_builtin_module(name): 2496 return not name 2497 2498 def _module_dirname(self, what, name): 2499 if self._is_user_module(name): 2500 return os.path.dirname(name) 2501 return '' 2502 2503 def _module_basename(self, what, name): 2504 ret = '' if self._is_builtin_module(name) else self._prefix 2505 if self._is_user_module(name): 2506 basename = os.path.basename(name) 2507 ret += what 2508 if name != self._main_module: 2509 ret += '-' + os.path.splitext(basename)[0] 2510 else: 2511 name = name[2:] if name else 'builtin' 2512 ret += re.sub(r'-', '-' + name + '-', what) 2513 return ret 2514 2515 def _module_filename(self, what, name): 2516 return os.path.join(self._module_dirname(what, name), 2517 self._module_basename(what, name)) 2518 2519 def _add_module(self, name, blurb): 2520 basename = self._module_filename(self._what, name) 2521 genc = QAPIGenC(basename + '.c', blurb, self._pydoc) 2522 genh = QAPIGenH(basename + '.h', blurb, self._pydoc) 2523 self._module[name] = (genc, genh) 2524 self._set_module(name) 2525 2526 def _add_user_module(self, name, blurb): 2527 assert self._is_user_module(name) 2528 if self._main_module is None: 2529 self._main_module = name 2530 self._add_module(name, blurb) 2531 2532 def _add_system_module(self, name, blurb): 2533 self._add_module(name and './' + name, blurb) 2534 2535 def _set_module(self, name): 2536 self._genc, self._genh = self._module[name] 2537 2538 def write(self, output_dir, opt_builtins=False): 2539 for name in self._module: 2540 if self._is_builtin_module(name) and not opt_builtins: 2541 continue 2542 (genc, genh) = self._module[name] 2543 genc.write(output_dir) 2544 genh.write(output_dir) 2545 2546 def _begin_user_module(self, name): 2547 pass 2548 2549 def visit_module(self, name): 2550 if name in self._module: 2551 self._set_module(name) 2552 elif self._is_builtin_module(name): 2553 # The built-in module has not been created. No code may 2554 # be generated. 2555 self._genc = None 2556 self._genh = None 2557 else: 2558 self._add_user_module(name, self._blurb) 2559 self._begin_user_module(name) 2560 2561 def visit_include(self, name, info): 2562 relname = os.path.relpath(self._module_filename(self._what, name), 2563 os.path.dirname(self._genh.fname)) 2564 self._genh.preamble_add(mcgen(''' 2565#include "%(relname)s.h" 2566''', 2567 relname=relname)) 2568