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