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