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