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