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