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