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