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