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 for (key, value) in expr.items(): 919 if key in ['gen', 'success-response'] and value is not False: 920 raise QAPISemError(info, 921 "'%s' of %s '%s' should only use false value" 922 % (key, meta, name)) 923 if (key in ['boxed', 'allow-oob', 'allow-preconfig'] 924 and value is not True): 925 raise QAPISemError(info, 926 "'%s' of %s '%s' should only use true value" 927 % (key, meta, name)) 928 929 930def normalize_enum(expr): 931 if isinstance(expr['data'], list): 932 expr['data'] = [m if isinstance(m, dict) else {'name': m} 933 for m in expr['data']] 934 935 936def normalize_members(members): 937 if isinstance(members, OrderedDict): 938 for key, arg in members.items(): 939 if isinstance(arg, dict): 940 continue 941 members[key] = {'type': arg} 942 943 944def normalize_features(features): 945 if isinstance(features, list): 946 features[:] = [f if isinstance(f, dict) else {'name': f} 947 for f in features] 948 949 950def normalize_if(expr): 951 ifcond = expr.get('if') 952 if isinstance(ifcond, str): 953 expr['if'] = [ifcond] 954 955 956def check_exprs(exprs): 957 for expr_elem in exprs: 958 expr = expr_elem['expr'] 959 info = expr_elem['info'] 960 doc = expr_elem.get('doc') 961 962 if 'include' in expr: 963 continue 964 965 if not doc and doc_required: 966 raise QAPISemError(info, 967 "definition missing documentation comment") 968 969 if 'enum' in expr: 970 meta = 'enum' 971 check_keys(expr, info, 'enum', ['data'], ['if', 'prefix']) 972 normalize_enum(expr) 973 elif 'union' in expr: 974 meta = 'union' 975 check_keys(expr, info, 'union', ['data'], 976 ['base', 'discriminator', 'if']) 977 normalize_members(expr.get('base')) 978 normalize_members(expr['data']) 979 elif 'alternate' in expr: 980 meta = 'alternate' 981 check_keys(expr, info, 'alternate', ['data'], ['if']) 982 normalize_members(expr['data']) 983 elif 'struct' in expr: 984 meta = 'struct' 985 check_keys(expr, info, 'struct', ['data'], 986 ['base', 'if', 'features']) 987 normalize_members(expr['data']) 988 normalize_features(expr.get('features')) 989 elif 'command' in expr: 990 meta = 'command' 991 check_keys(expr, info, 'command', [], 992 ['data', 'returns', 'gen', 'success-response', 993 'boxed', 'allow-oob', 'allow-preconfig', 'if']) 994 normalize_members(expr.get('data')) 995 elif 'event' in expr: 996 meta = 'event' 997 check_keys(expr, info, 'event', [], ['data', 'boxed', 'if']) 998 normalize_members(expr.get('data')) 999 else: 1000 raise QAPISemError(info, "expression is missing metatype") 1001 normalize_if(expr) 1002 1003 name = expr[meta] 1004 check_name_is_str(name, info, "'%s'" % meta) 1005 info.set_defn(meta, name) 1006 check_defn_name_str(name, info, meta) 1007 1008 if doc and doc.symbol != name: 1009 raise QAPISemError( 1010 info, 1011 "definition of '%s' follows documentation for '%s'" 1012 % (name, doc.symbol)) 1013 1014 if meta == 'enum': 1015 check_enum(expr, info) 1016 elif meta == 'union': 1017 check_union(expr, info) 1018 elif meta == 'alternate': 1019 check_alternate(expr, info) 1020 elif meta == 'struct': 1021 check_struct(expr, info) 1022 elif meta == 'command': 1023 check_command(expr, info) 1024 elif meta == 'event': 1025 check_event(expr, info) 1026 else: 1027 assert False, 'unexpected meta type' 1028 1029 check_if(expr, info) 1030 1031 if doc: 1032 doc.check_expr(expr) 1033 1034 return exprs 1035 1036 1037# 1038# Schema compiler frontend 1039# TODO catching name collisions in generated code would be nice 1040# 1041 1042class QAPISchemaEntity(object): 1043 meta = None 1044 1045 def __init__(self, name, info, doc, ifcond=None): 1046 assert name is None or isinstance(name, str) 1047 self.name = name 1048 self._module = None 1049 # For explicitly defined entities, info points to the (explicit) 1050 # definition. For builtins (and their arrays), info is None. 1051 # For implicitly defined entities, info points to a place that 1052 # triggered the implicit definition (there may be more than one 1053 # such place). 1054 self.info = info 1055 self.doc = doc 1056 self._ifcond = ifcond or [] 1057 self._checked = False 1058 1059 def c_name(self): 1060 return c_name(self.name) 1061 1062 def check(self, schema): 1063 assert not self._checked 1064 if self.info: 1065 self._module = os.path.relpath(self.info.fname, 1066 os.path.dirname(schema.fname)) 1067 self._checked = True 1068 1069 @property 1070 def ifcond(self): 1071 assert self._checked 1072 return self._ifcond 1073 1074 @property 1075 def module(self): 1076 assert self._checked 1077 return self._module 1078 1079 def is_implicit(self): 1080 return not self.info 1081 1082 def visit(self, visitor): 1083 assert self._checked 1084 1085 def describe(self): 1086 assert self.meta 1087 return "%s '%s'" % (self.meta, self.name) 1088 1089 1090class QAPISchemaVisitor(object): 1091 def visit_begin(self, schema): 1092 pass 1093 1094 def visit_end(self): 1095 pass 1096 1097 def visit_module(self, fname): 1098 pass 1099 1100 def visit_needed(self, entity): 1101 # Default to visiting everything 1102 return True 1103 1104 def visit_include(self, fname, info): 1105 pass 1106 1107 def visit_builtin_type(self, name, info, json_type): 1108 pass 1109 1110 def visit_enum_type(self, name, info, ifcond, members, prefix): 1111 pass 1112 1113 def visit_array_type(self, name, info, ifcond, element_type): 1114 pass 1115 1116 def visit_object_type(self, name, info, ifcond, base, members, variants, 1117 features): 1118 pass 1119 1120 def visit_object_type_flat(self, name, info, ifcond, members, variants, 1121 features): 1122 pass 1123 1124 def visit_alternate_type(self, name, info, ifcond, variants): 1125 pass 1126 1127 def visit_command(self, name, info, ifcond, arg_type, ret_type, gen, 1128 success_response, boxed, allow_oob, allow_preconfig): 1129 pass 1130 1131 def visit_event(self, name, info, ifcond, arg_type, boxed): 1132 pass 1133 1134 1135class QAPISchemaInclude(QAPISchemaEntity): 1136 1137 def __init__(self, fname, info): 1138 QAPISchemaEntity.__init__(self, None, info, None) 1139 self.fname = fname 1140 1141 def visit(self, visitor): 1142 QAPISchemaEntity.visit(self, visitor) 1143 visitor.visit_include(self.fname, self.info) 1144 1145 1146class QAPISchemaType(QAPISchemaEntity): 1147 # Return the C type for common use. 1148 # For the types we commonly box, this is a pointer type. 1149 def c_type(self): 1150 pass 1151 1152 # Return the C type to be used in a parameter list. 1153 def c_param_type(self): 1154 return self.c_type() 1155 1156 # Return the C type to be used where we suppress boxing. 1157 def c_unboxed_type(self): 1158 return self.c_type() 1159 1160 def json_type(self): 1161 pass 1162 1163 def alternate_qtype(self): 1164 json2qtype = { 1165 'null': 'QTYPE_QNULL', 1166 'string': 'QTYPE_QSTRING', 1167 'number': 'QTYPE_QNUM', 1168 'int': 'QTYPE_QNUM', 1169 'boolean': 'QTYPE_QBOOL', 1170 'object': 'QTYPE_QDICT' 1171 } 1172 return json2qtype.get(self.json_type()) 1173 1174 def doc_type(self): 1175 if self.is_implicit(): 1176 return None 1177 return self.name 1178 1179 def describe(self): 1180 assert self.meta 1181 return "%s type '%s'" % (self.meta, self.name) 1182 1183 1184class QAPISchemaBuiltinType(QAPISchemaType): 1185 meta = 'built-in' 1186 1187 def __init__(self, name, json_type, c_type): 1188 QAPISchemaType.__init__(self, name, None, None) 1189 assert not c_type or isinstance(c_type, str) 1190 assert json_type in ('string', 'number', 'int', 'boolean', 'null', 1191 'value') 1192 self._json_type_name = json_type 1193 self._c_type_name = c_type 1194 1195 def c_name(self): 1196 return self.name 1197 1198 def c_type(self): 1199 return self._c_type_name 1200 1201 def c_param_type(self): 1202 if self.name == 'str': 1203 return 'const ' + self._c_type_name 1204 return self._c_type_name 1205 1206 def json_type(self): 1207 return self._json_type_name 1208 1209 def doc_type(self): 1210 return self.json_type() 1211 1212 def visit(self, visitor): 1213 QAPISchemaType.visit(self, visitor) 1214 visitor.visit_builtin_type(self.name, self.info, self.json_type()) 1215 1216 1217class QAPISchemaEnumType(QAPISchemaType): 1218 meta = 'enum' 1219 1220 def __init__(self, name, info, doc, ifcond, members, prefix): 1221 QAPISchemaType.__init__(self, name, info, doc, ifcond) 1222 for m in members: 1223 assert isinstance(m, QAPISchemaEnumMember) 1224 m.set_defined_in(name) 1225 assert prefix is None or isinstance(prefix, str) 1226 self.members = members 1227 self.prefix = prefix 1228 1229 def check(self, schema): 1230 QAPISchemaType.check(self, schema) 1231 seen = {} 1232 for m in self.members: 1233 m.check_clash(self.info, seen) 1234 if self.doc: 1235 self.doc.connect_member(m) 1236 1237 def is_implicit(self): 1238 # See QAPISchema._make_implicit_enum_type() and ._def_predefineds() 1239 return self.name.endswith('Kind') or self.name == 'QType' 1240 1241 def c_type(self): 1242 return c_name(self.name) 1243 1244 def member_names(self): 1245 return [m.name for m in self.members] 1246 1247 def json_type(self): 1248 return 'string' 1249 1250 def visit(self, visitor): 1251 QAPISchemaType.visit(self, visitor) 1252 visitor.visit_enum_type(self.name, self.info, self.ifcond, 1253 self.members, self.prefix) 1254 1255 1256class QAPISchemaArrayType(QAPISchemaType): 1257 meta = 'array' 1258 1259 def __init__(self, name, info, element_type): 1260 QAPISchemaType.__init__(self, name, info, None, None) 1261 assert isinstance(element_type, str) 1262 self._element_type_name = element_type 1263 self.element_type = None 1264 1265 def check(self, schema): 1266 QAPISchemaType.check(self, schema) 1267 self.element_type = schema.resolve_type( 1268 self._element_type_name, self.info, 1269 self.info and self.info.defn_meta) 1270 assert not isinstance(self.element_type, QAPISchemaArrayType) 1271 1272 @property 1273 def ifcond(self): 1274 assert self._checked 1275 return self.element_type.ifcond 1276 1277 @property 1278 def module(self): 1279 assert self._checked 1280 return self.element_type.module 1281 1282 def is_implicit(self): 1283 return True 1284 1285 def c_type(self): 1286 return c_name(self.name) + pointer_suffix 1287 1288 def json_type(self): 1289 return 'array' 1290 1291 def doc_type(self): 1292 elt_doc_type = self.element_type.doc_type() 1293 if not elt_doc_type: 1294 return None 1295 return 'array of ' + elt_doc_type 1296 1297 def visit(self, visitor): 1298 QAPISchemaType.visit(self, visitor) 1299 visitor.visit_array_type(self.name, self.info, self.ifcond, 1300 self.element_type) 1301 1302 def describe(self): 1303 assert self.meta 1304 return "%s type ['%s']" % (self.meta, self._element_type_name) 1305 1306 1307class QAPISchemaObjectType(QAPISchemaType): 1308 def __init__(self, name, info, doc, ifcond, 1309 base, local_members, variants, features): 1310 # struct has local_members, optional base, and no variants 1311 # flat union has base, variants, and no local_members 1312 # simple union has local_members, variants, and no base 1313 QAPISchemaType.__init__(self, name, info, doc, ifcond) 1314 self.meta = 'union' if variants else 'struct' 1315 assert base is None or isinstance(base, str) 1316 for m in local_members: 1317 assert isinstance(m, QAPISchemaObjectTypeMember) 1318 m.set_defined_in(name) 1319 if variants is not None: 1320 assert isinstance(variants, QAPISchemaObjectTypeVariants) 1321 variants.set_defined_in(name) 1322 for f in features: 1323 assert isinstance(f, QAPISchemaFeature) 1324 f.set_defined_in(name) 1325 self._base_name = base 1326 self.base = None 1327 self.local_members = local_members 1328 self.variants = variants 1329 self.members = None 1330 self.features = features 1331 1332 def check(self, schema): 1333 # This calls another type T's .check() exactly when the C 1334 # struct emitted by gen_object() contains that T's C struct 1335 # (pointers don't count). 1336 if self.members is not None: 1337 # A previous .check() completed: nothing to do 1338 return 1339 if self._checked: 1340 # Recursed: C struct contains itself 1341 raise QAPISemError(self.info, 1342 "object %s contains itself" % self.name) 1343 1344 QAPISchemaType.check(self, schema) 1345 assert self._checked and self.members is None 1346 1347 seen = OrderedDict() 1348 if self._base_name: 1349 self.base = schema.resolve_type(self._base_name, self.info, 1350 "'base'") 1351 if (not isinstance(self.base, QAPISchemaObjectType) 1352 or self.base.variants): 1353 raise QAPISemError( 1354 self.info, 1355 "'base' requires a struct type, %s isn't" 1356 % self.base.describe()) 1357 self.base.check(schema) 1358 self.base.check_clash(self.info, seen) 1359 for m in self.local_members: 1360 m.check(schema) 1361 m.check_clash(self.info, seen) 1362 if self.doc: 1363 self.doc.connect_member(m) 1364 members = seen.values() 1365 1366 if self.variants: 1367 self.variants.check(schema, seen) 1368 self.variants.check_clash(self.info, seen) 1369 1370 # Features are in a name space separate from members 1371 seen = {} 1372 for f in self.features: 1373 f.check_clash(self.info, seen) 1374 1375 if self.doc: 1376 self.doc.check() 1377 1378 self.members = members # mark completed 1379 1380 # Check that the members of this type do not cause duplicate JSON members, 1381 # and update seen to track the members seen so far. Report any errors 1382 # on behalf of info, which is not necessarily self.info 1383 def check_clash(self, info, seen): 1384 assert self._checked 1385 assert not self.variants # not implemented 1386 for m in self.members: 1387 m.check_clash(info, seen) 1388 1389 @property 1390 def ifcond(self): 1391 assert self._checked 1392 if isinstance(self._ifcond, QAPISchemaType): 1393 # Simple union wrapper type inherits from wrapped type; 1394 # see _make_implicit_object_type() 1395 return self._ifcond.ifcond 1396 return self._ifcond 1397 1398 def is_implicit(self): 1399 # See QAPISchema._make_implicit_object_type(), as well as 1400 # _def_predefineds() 1401 return self.name.startswith('q_') 1402 1403 def is_empty(self): 1404 assert self.members is not None 1405 return not self.members and not self.variants 1406 1407 def c_name(self): 1408 assert self.name != 'q_empty' 1409 return QAPISchemaType.c_name(self) 1410 1411 def c_type(self): 1412 assert not self.is_implicit() 1413 return c_name(self.name) + pointer_suffix 1414 1415 def c_unboxed_type(self): 1416 return c_name(self.name) 1417 1418 def json_type(self): 1419 return 'object' 1420 1421 def visit(self, visitor): 1422 QAPISchemaType.visit(self, visitor) 1423 visitor.visit_object_type(self.name, self.info, self.ifcond, 1424 self.base, self.local_members, self.variants, 1425 self.features) 1426 visitor.visit_object_type_flat(self.name, self.info, self.ifcond, 1427 self.members, self.variants, 1428 self.features) 1429 1430 1431class QAPISchemaMember(object): 1432 """ Represents object members, enum members and features """ 1433 role = 'member' 1434 1435 def __init__(self, name, info, ifcond=None): 1436 assert isinstance(name, str) 1437 self.name = name 1438 self.info = info 1439 self.ifcond = ifcond or [] 1440 self.defined_in = None 1441 1442 def set_defined_in(self, name): 1443 assert not self.defined_in 1444 self.defined_in = name 1445 1446 def check_clash(self, info, seen): 1447 cname = c_name(self.name) 1448 if cname in seen: 1449 raise QAPISemError( 1450 info, 1451 "%s collides with %s" 1452 % (self.describe(info), seen[cname].describe(info))) 1453 seen[cname] = self 1454 1455 def describe(self, info): 1456 role = self.role 1457 defined_in = self.defined_in 1458 assert defined_in 1459 1460 if defined_in.startswith('q_obj_'): 1461 # See QAPISchema._make_implicit_object_type() - reverse the 1462 # mapping there to create a nice human-readable description 1463 defined_in = defined_in[6:] 1464 if defined_in.endswith('-arg'): 1465 # Implicit type created for a command's dict 'data' 1466 assert role == 'member' 1467 role = 'parameter' 1468 elif defined_in.endswith('-base'): 1469 # Implicit type created for a flat union's dict 'base' 1470 role = 'base ' + role 1471 else: 1472 # Implicit type created for a simple union's branch 1473 assert defined_in.endswith('-wrapper') 1474 # Unreachable and not implemented 1475 assert False 1476 elif defined_in.endswith('Kind'): 1477 # See QAPISchema._make_implicit_enum_type() 1478 # Implicit enum created for simple union's branches 1479 assert role == 'value' 1480 role = 'branch' 1481 elif defined_in != info.defn_name: 1482 return "%s '%s' of type '%s'" % (role, self.name, defined_in) 1483 return "%s '%s'" % (role, self.name) 1484 1485 1486class QAPISchemaEnumMember(QAPISchemaMember): 1487 role = 'value' 1488 1489 1490class QAPISchemaFeature(QAPISchemaMember): 1491 role = 'feature' 1492 1493 1494class QAPISchemaObjectTypeMember(QAPISchemaMember): 1495 def __init__(self, name, info, typ, optional, ifcond=None): 1496 QAPISchemaMember.__init__(self, name, info, ifcond) 1497 assert isinstance(typ, str) 1498 assert isinstance(optional, bool) 1499 self._type_name = typ 1500 self.type = None 1501 self.optional = optional 1502 1503 def check(self, schema): 1504 assert self.defined_in 1505 self.type = schema.resolve_type(self._type_name, self.info, 1506 self.describe) 1507 1508 1509class QAPISchemaObjectTypeVariants(object): 1510 def __init__(self, tag_name, info, tag_member, variants): 1511 # Flat unions pass tag_name but not tag_member. 1512 # Simple unions and alternates pass tag_member but not tag_name. 1513 # After check(), tag_member is always set, and tag_name remains 1514 # a reliable witness of being used by a flat union. 1515 assert bool(tag_member) != bool(tag_name) 1516 assert (isinstance(tag_name, str) or 1517 isinstance(tag_member, QAPISchemaObjectTypeMember)) 1518 for v in variants: 1519 assert isinstance(v, QAPISchemaObjectTypeVariant) 1520 self._tag_name = tag_name 1521 self.info = info 1522 self.tag_member = tag_member 1523 self.variants = variants 1524 1525 def set_defined_in(self, name): 1526 for v in self.variants: 1527 v.set_defined_in(name) 1528 1529 def check(self, schema, seen): 1530 if not self.tag_member: # flat union 1531 self.tag_member = seen.get(c_name(self._tag_name)) 1532 base = "'base'" 1533 # Pointing to the base type when not implicit would be 1534 # nice, but we don't know it here 1535 if not self.tag_member or self._tag_name != self.tag_member.name: 1536 raise QAPISemError( 1537 self.info, 1538 "discriminator '%s' is not a member of %s" 1539 % (self._tag_name, base)) 1540 # Here we do: 1541 base_type = schema.lookup_type(self.tag_member.defined_in) 1542 assert base_type 1543 if not base_type.is_implicit(): 1544 base = "base type '%s'" % self.tag_member.defined_in 1545 if not isinstance(self.tag_member.type, QAPISchemaEnumType): 1546 raise QAPISemError( 1547 self.info, 1548 "discriminator member '%s' of %s must be of enum type" 1549 % (self._tag_name, base)) 1550 if self.tag_member.optional: 1551 raise QAPISemError( 1552 self.info, 1553 "discriminator member '%s' of %s must not be optional" 1554 % (self._tag_name, base)) 1555 if self.tag_member.ifcond: 1556 raise QAPISemError( 1557 self.info, 1558 "discriminator member '%s' of %s must not be conditional" 1559 % (self._tag_name, base)) 1560 else: # simple union 1561 assert isinstance(self.tag_member.type, QAPISchemaEnumType) 1562 assert not self.tag_member.optional 1563 assert self.tag_member.ifcond == [] 1564 if self._tag_name: # flat union 1565 # branches that are not explicitly covered get an empty type 1566 cases = set([v.name for v in self.variants]) 1567 for m in self.tag_member.type.members: 1568 if m.name not in cases: 1569 v = QAPISchemaObjectTypeVariant(m.name, self.info, 1570 'q_empty', m.ifcond) 1571 v.set_defined_in(self.tag_member.defined_in) 1572 self.variants.append(v) 1573 if not self.variants: 1574 raise QAPISemError(self.info, "union has no branches") 1575 for v in self.variants: 1576 v.check(schema) 1577 # Union names must match enum values; alternate names are 1578 # checked separately. Use 'seen' to tell the two apart. 1579 if seen: 1580 if v.name not in self.tag_member.type.member_names(): 1581 raise QAPISemError( 1582 self.info, 1583 "branch '%s' is not a value of %s" 1584 % (v.name, self.tag_member.type.describe())) 1585 if (not isinstance(v.type, QAPISchemaObjectType) 1586 or v.type.variants): 1587 raise QAPISemError( 1588 self.info, 1589 "%s cannot use %s" 1590 % (v.describe(self.info), v.type.describe())) 1591 v.type.check(schema) 1592 1593 def check_clash(self, info, seen): 1594 for v in self.variants: 1595 # Reset seen map for each variant, since qapi names from one 1596 # branch do not affect another branch 1597 v.type.check_clash(info, dict(seen)) 1598 1599 1600class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember): 1601 role = 'branch' 1602 1603 def __init__(self, name, info, typ, ifcond=None): 1604 QAPISchemaObjectTypeMember.__init__(self, name, info, typ, 1605 False, ifcond) 1606 1607 1608class QAPISchemaAlternateType(QAPISchemaType): 1609 meta = 'alternate' 1610 1611 def __init__(self, name, info, doc, ifcond, variants): 1612 QAPISchemaType.__init__(self, name, info, doc, ifcond) 1613 assert isinstance(variants, QAPISchemaObjectTypeVariants) 1614 assert variants.tag_member 1615 variants.set_defined_in(name) 1616 variants.tag_member.set_defined_in(self.name) 1617 self.variants = variants 1618 1619 def check(self, schema): 1620 QAPISchemaType.check(self, schema) 1621 self.variants.tag_member.check(schema) 1622 # Not calling self.variants.check_clash(), because there's nothing 1623 # to clash with 1624 self.variants.check(schema, {}) 1625 # Alternate branch names have no relation to the tag enum values; 1626 # so we have to check for potential name collisions ourselves. 1627 seen = {} 1628 types_seen = {} 1629 for v in self.variants.variants: 1630 v.check_clash(self.info, seen) 1631 qtype = v.type.alternate_qtype() 1632 if not qtype: 1633 raise QAPISemError( 1634 self.info, 1635 "%s cannot use %s" 1636 % (v.describe(self.info), v.type.describe())) 1637 conflicting = set([qtype]) 1638 if qtype == 'QTYPE_QSTRING': 1639 if isinstance(v.type, QAPISchemaEnumType): 1640 for m in v.type.members: 1641 if m.name in ['on', 'off']: 1642 conflicting.add('QTYPE_QBOOL') 1643 if re.match(r'[-+0-9.]', m.name): 1644 # lazy, could be tightened 1645 conflicting.add('QTYPE_QNUM') 1646 else: 1647 conflicting.add('QTYPE_QNUM') 1648 conflicting.add('QTYPE_QBOOL') 1649 for qt in conflicting: 1650 if qt in types_seen: 1651 raise QAPISemError( 1652 self.info, 1653 "%s can't be distinguished from '%s'" 1654 % (v.describe(self.info), types_seen[qt])) 1655 types_seen[qt] = v.name 1656 if self.doc: 1657 self.doc.connect_member(v) 1658 if self.doc: 1659 self.doc.check() 1660 1661 def c_type(self): 1662 return c_name(self.name) + pointer_suffix 1663 1664 def json_type(self): 1665 return 'value' 1666 1667 def visit(self, visitor): 1668 QAPISchemaType.visit(self, visitor) 1669 visitor.visit_alternate_type(self.name, self.info, self.ifcond, 1670 self.variants) 1671 1672 1673class QAPISchemaCommand(QAPISchemaEntity): 1674 meta = 'command' 1675 1676 def __init__(self, name, info, doc, ifcond, arg_type, ret_type, 1677 gen, success_response, boxed, allow_oob, allow_preconfig): 1678 QAPISchemaEntity.__init__(self, name, info, doc, ifcond) 1679 assert not arg_type or isinstance(arg_type, str) 1680 assert not ret_type or isinstance(ret_type, str) 1681 self._arg_type_name = arg_type 1682 self.arg_type = None 1683 self._ret_type_name = ret_type 1684 self.ret_type = None 1685 self.gen = gen 1686 self.success_response = success_response 1687 self.boxed = boxed 1688 self.allow_oob = allow_oob 1689 self.allow_preconfig = allow_preconfig 1690 1691 def check(self, schema): 1692 QAPISchemaEntity.check(self, schema) 1693 if self._arg_type_name: 1694 self.arg_type = schema.resolve_type( 1695 self._arg_type_name, self.info, "command's 'data'") 1696 if not isinstance(self.arg_type, QAPISchemaObjectType): 1697 raise QAPISemError( 1698 self.info, 1699 "command's 'data' cannot take %s" 1700 % self.arg_type.describe()) 1701 if self.arg_type.variants and not self.boxed: 1702 raise QAPISemError( 1703 self.info, 1704 "command's 'data' can take %s only with 'boxed': true" 1705 % self.arg_type.describe()) 1706 if self._ret_type_name: 1707 self.ret_type = schema.resolve_type( 1708 self._ret_type_name, self.info, "command's 'returns'") 1709 if self.name not in returns_whitelist: 1710 if not (isinstance(self.ret_type, QAPISchemaObjectType) 1711 or (isinstance(self.ret_type, QAPISchemaArrayType) 1712 and isinstance(self.ret_type.element_type, 1713 QAPISchemaObjectType))): 1714 raise QAPISemError( 1715 self.info, 1716 "command's 'returns' cannot take %s" 1717 % self.ret_type.describe()) 1718 1719 def visit(self, visitor): 1720 QAPISchemaEntity.visit(self, visitor) 1721 visitor.visit_command(self.name, self.info, self.ifcond, 1722 self.arg_type, self.ret_type, 1723 self.gen, self.success_response, 1724 self.boxed, self.allow_oob, 1725 self.allow_preconfig) 1726 1727 1728class QAPISchemaEvent(QAPISchemaEntity): 1729 meta = 'event' 1730 1731 def __init__(self, name, info, doc, ifcond, arg_type, boxed): 1732 QAPISchemaEntity.__init__(self, name, info, doc, ifcond) 1733 assert not arg_type or isinstance(arg_type, str) 1734 self._arg_type_name = arg_type 1735 self.arg_type = None 1736 self.boxed = boxed 1737 1738 def check(self, schema): 1739 QAPISchemaEntity.check(self, schema) 1740 if self._arg_type_name: 1741 self.arg_type = schema.resolve_type( 1742 self._arg_type_name, self.info, "event's 'data'") 1743 if not isinstance(self.arg_type, QAPISchemaObjectType): 1744 raise QAPISemError( 1745 self.info, 1746 "event's 'data' cannot take %s" 1747 % self.arg_type.describe()) 1748 if self.arg_type.variants and not self.boxed: 1749 raise QAPISemError( 1750 self.info, 1751 "event's 'data' can take %s only with 'boxed': true" 1752 % self.arg_type.describe()) 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