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