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