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