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