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 783def check_alternate(expr, info): 784 name = expr['alternate'] 785 members = expr['data'] 786 types_seen = {} 787 788 # Check every branch; require at least two branches 789 if len(members) < 2: 790 raise QAPISemError(info, 791 "Alternate '%s' should have at least two branches " 792 "in 'data'" % name) 793 for (key, value) in members.items(): 794 check_name(info, "Member of alternate '%s'" % name, key) 795 796 # Ensure alternates have no type conflicts. 797 check_type(info, "Member '%s' of alternate '%s'" % (key, name), 798 value, 799 allow_metas=['built-in', 'union', 'struct', 'enum']) 800 qtype = find_alternate_member_qtype(value) 801 if not qtype: 802 raise QAPISemError(info, "Alternate '%s' member '%s' cannot use " 803 "type '%s'" % (name, key, value)) 804 conflicting = set([qtype]) 805 if qtype == 'QTYPE_QSTRING': 806 enum_expr = enum_types.get(value) 807 if enum_expr: 808 for v in enum_expr['data']: 809 if v in ['on', 'off']: 810 conflicting.add('QTYPE_QBOOL') 811 if re.match(r'[-+0-9.]', v): # lazy, could be tightened 812 conflicting.add('QTYPE_QNUM') 813 else: 814 conflicting.add('QTYPE_QNUM') 815 conflicting.add('QTYPE_QBOOL') 816 for qt in conflicting: 817 if qt in types_seen: 818 raise QAPISemError(info, "Alternate '%s' member '%s' can't " 819 "be distinguished from member '%s'" 820 % (name, key, types_seen[qt])) 821 types_seen[qt] = key 822 823 824def check_enum(expr, info): 825 name = expr['enum'] 826 members = expr.get('data') 827 prefix = expr.get('prefix') 828 829 if not isinstance(members, list): 830 raise QAPISemError(info, 831 "Enum '%s' requires an array for 'data'" % name) 832 if prefix is not None and not isinstance(prefix, str): 833 raise QAPISemError(info, 834 "Enum '%s' requires a string for 'prefix'" % name) 835 for member in members: 836 check_name(info, "Member of enum '%s'" % name, member, 837 enum_member=True) 838 839 840def check_struct(expr, info): 841 name = expr['struct'] 842 members = expr['data'] 843 844 check_type(info, "'data' for struct '%s'" % name, members, 845 allow_dict=True, allow_optional=True) 846 check_type(info, "'base' for struct '%s'" % name, expr.get('base'), 847 allow_metas=['struct']) 848 849 850def check_keys(expr_elem, meta, required, optional=[]): 851 expr = expr_elem['expr'] 852 info = expr_elem['info'] 853 name = expr[meta] 854 if not isinstance(name, str): 855 raise QAPISemError(info, "'%s' key must have a string value" % meta) 856 required = required + [meta] 857 for (key, value) in expr.items(): 858 if key not in required and key not in optional: 859 raise QAPISemError(info, "Unknown key '%s' in %s '%s'" 860 % (key, meta, name)) 861 if (key == 'gen' or key == 'success-response') and value is not False: 862 raise QAPISemError(info, 863 "'%s' of %s '%s' should only use false value" 864 % (key, meta, name)) 865 if (key == 'boxed' or key == 'allow-oob' or 866 key == 'allow-preconfig') and value is not True: 867 raise QAPISemError(info, 868 "'%s' of %s '%s' should only use true value" 869 % (key, meta, name)) 870 for key in required: 871 if key not in expr: 872 raise QAPISemError(info, "Key '%s' is missing from %s '%s'" 873 % (key, meta, name)) 874 875 876def check_exprs(exprs): 877 global all_names 878 879 # Populate name table with names of built-in types 880 for builtin in builtin_types.keys(): 881 all_names[builtin] = 'built-in' 882 883 # Learn the types and check for valid expression keys 884 for expr_elem in exprs: 885 expr = expr_elem['expr'] 886 info = expr_elem['info'] 887 doc = expr_elem.get('doc') 888 889 if 'include' in expr: 890 continue 891 892 if not doc and doc_required: 893 raise QAPISemError(info, 894 "Expression missing documentation comment") 895 896 if 'enum' in expr: 897 meta = 'enum' 898 check_keys(expr_elem, 'enum', ['data'], ['prefix']) 899 enum_types[expr[meta]] = expr 900 elif 'union' in expr: 901 meta = 'union' 902 check_keys(expr_elem, 'union', ['data'], 903 ['base', 'discriminator']) 904 union_types[expr[meta]] = expr 905 elif 'alternate' in expr: 906 meta = 'alternate' 907 check_keys(expr_elem, 'alternate', ['data']) 908 elif 'struct' in expr: 909 meta = 'struct' 910 check_keys(expr_elem, 'struct', ['data'], ['base']) 911 struct_types[expr[meta]] = expr 912 elif 'command' in expr: 913 meta = 'command' 914 check_keys(expr_elem, 'command', [], 915 ['data', 'returns', 'gen', 'success-response', 916 'boxed', 'allow-oob', 'allow-preconfig']) 917 elif 'event' in expr: 918 meta = 'event' 919 check_keys(expr_elem, 'event', [], ['data', 'boxed']) 920 else: 921 raise QAPISemError(expr_elem['info'], 922 "Expression is missing metatype") 923 name = expr[meta] 924 add_name(name, info, meta) 925 if doc and doc.symbol != name: 926 raise QAPISemError(info, "Definition of '%s' follows documentation" 927 " for '%s'" % (name, doc.symbol)) 928 929 # Try again for hidden UnionKind enum 930 for expr_elem in exprs: 931 expr = expr_elem['expr'] 932 933 if 'include' in expr: 934 continue 935 if 'union' in expr and not discriminator_find_enum_define(expr): 936 name = '%sKind' % expr['union'] 937 elif 'alternate' in expr: 938 name = '%sKind' % expr['alternate'] 939 else: 940 continue 941 enum_types[name] = {'enum': name} 942 add_name(name, info, 'enum', implicit=True) 943 944 # Validate that exprs make sense 945 for expr_elem in exprs: 946 expr = expr_elem['expr'] 947 info = expr_elem['info'] 948 doc = expr_elem.get('doc') 949 950 if 'include' in expr: 951 continue 952 if 'enum' in expr: 953 check_enum(expr, info) 954 elif 'union' in expr: 955 check_union(expr, info) 956 elif 'alternate' in expr: 957 check_alternate(expr, info) 958 elif 'struct' in expr: 959 check_struct(expr, info) 960 elif 'command' in expr: 961 check_command(expr, info) 962 elif 'event' in expr: 963 check_event(expr, info) 964 else: 965 assert False, 'unexpected meta type' 966 967 if doc: 968 doc.check_expr(expr) 969 970 return exprs 971 972 973# 974# Schema compiler frontend 975# 976 977class QAPISchemaEntity(object): 978 def __init__(self, name, info, doc): 979 assert name is None or isinstance(name, str) 980 self.name = name 981 self.module = None 982 # For explicitly defined entities, info points to the (explicit) 983 # definition. For builtins (and their arrays), info is None. 984 # For implicitly defined entities, info points to a place that 985 # triggered the implicit definition (there may be more than one 986 # such place). 987 self.info = info 988 self.doc = doc 989 990 def c_name(self): 991 return c_name(self.name) 992 993 def check(self, schema): 994 pass 995 996 def is_implicit(self): 997 return not self.info 998 999 def visit(self, visitor): 1000 pass 1001 1002 1003class QAPISchemaVisitor(object): 1004 def visit_begin(self, schema): 1005 pass 1006 1007 def visit_end(self): 1008 pass 1009 1010 def visit_module(self, fname): 1011 pass 1012 1013 def visit_needed(self, entity): 1014 # Default to visiting everything 1015 return True 1016 1017 def visit_include(self, fname, info): 1018 pass 1019 1020 def visit_builtin_type(self, name, info, json_type): 1021 pass 1022 1023 def visit_enum_type(self, name, info, values, prefix): 1024 pass 1025 1026 def visit_array_type(self, name, info, element_type): 1027 pass 1028 1029 def visit_object_type(self, name, info, base, members, variants): 1030 pass 1031 1032 def visit_object_type_flat(self, name, info, members, variants): 1033 pass 1034 1035 def visit_alternate_type(self, name, info, variants): 1036 pass 1037 1038 def visit_command(self, name, info, arg_type, ret_type, gen, 1039 success_response, boxed, allow_oob, allow_preconfig): 1040 pass 1041 1042 def visit_event(self, name, info, arg_type, boxed): 1043 pass 1044 1045 1046class QAPISchemaInclude(QAPISchemaEntity): 1047 1048 def __init__(self, fname, info): 1049 QAPISchemaEntity.__init__(self, None, info, None) 1050 self.fname = fname 1051 1052 def visit(self, visitor): 1053 visitor.visit_include(self.fname, self.info) 1054 1055 1056class QAPISchemaType(QAPISchemaEntity): 1057 # Return the C type for common use. 1058 # For the types we commonly box, this is a pointer type. 1059 def c_type(self): 1060 pass 1061 1062 # Return the C type to be used in a parameter list. 1063 def c_param_type(self): 1064 return self.c_type() 1065 1066 # Return the C type to be used where we suppress boxing. 1067 def c_unboxed_type(self): 1068 return self.c_type() 1069 1070 def json_type(self): 1071 pass 1072 1073 def alternate_qtype(self): 1074 json2qtype = { 1075 'null': 'QTYPE_QNULL', 1076 'string': 'QTYPE_QSTRING', 1077 'number': 'QTYPE_QNUM', 1078 'int': 'QTYPE_QNUM', 1079 'boolean': 'QTYPE_QBOOL', 1080 'object': 'QTYPE_QDICT' 1081 } 1082 return json2qtype.get(self.json_type()) 1083 1084 def doc_type(self): 1085 if self.is_implicit(): 1086 return None 1087 return self.name 1088 1089 1090class QAPISchemaBuiltinType(QAPISchemaType): 1091 def __init__(self, name, json_type, c_type): 1092 QAPISchemaType.__init__(self, name, None, None) 1093 assert not c_type or isinstance(c_type, str) 1094 assert json_type in ('string', 'number', 'int', 'boolean', 'null', 1095 'value') 1096 self._json_type_name = json_type 1097 self._c_type_name = c_type 1098 1099 def c_name(self): 1100 return self.name 1101 1102 def c_type(self): 1103 return self._c_type_name 1104 1105 def c_param_type(self): 1106 if self.name == 'str': 1107 return 'const ' + self._c_type_name 1108 return self._c_type_name 1109 1110 def json_type(self): 1111 return self._json_type_name 1112 1113 def doc_type(self): 1114 return self.json_type() 1115 1116 def visit(self, visitor): 1117 visitor.visit_builtin_type(self.name, self.info, self.json_type()) 1118 1119 1120class QAPISchemaEnumType(QAPISchemaType): 1121 def __init__(self, name, info, doc, values, prefix): 1122 QAPISchemaType.__init__(self, name, info, doc) 1123 for v in values: 1124 assert isinstance(v, QAPISchemaMember) 1125 v.set_owner(name) 1126 assert prefix is None or isinstance(prefix, str) 1127 self.values = values 1128 self.prefix = prefix 1129 1130 def check(self, schema): 1131 seen = {} 1132 for v in self.values: 1133 v.check_clash(self.info, seen) 1134 if self.doc: 1135 self.doc.connect_member(v) 1136 1137 def is_implicit(self): 1138 # See QAPISchema._make_implicit_enum_type() and ._def_predefineds() 1139 return self.name.endswith('Kind') or self.name == 'QType' 1140 1141 def c_type(self): 1142 return c_name(self.name) 1143 1144 def member_names(self): 1145 return [v.name for v in self.values] 1146 1147 def json_type(self): 1148 return 'string' 1149 1150 def visit(self, visitor): 1151 visitor.visit_enum_type(self.name, self.info, 1152 self.member_names(), self.prefix) 1153 1154 1155class QAPISchemaArrayType(QAPISchemaType): 1156 def __init__(self, name, info, element_type): 1157 QAPISchemaType.__init__(self, name, info, None) 1158 assert isinstance(element_type, str) 1159 self._element_type_name = element_type 1160 self.element_type = None 1161 1162 def check(self, schema): 1163 self.element_type = schema.lookup_type(self._element_type_name) 1164 assert self.element_type 1165 1166 def is_implicit(self): 1167 return True 1168 1169 def c_type(self): 1170 return c_name(self.name) + pointer_suffix 1171 1172 def json_type(self): 1173 return 'array' 1174 1175 def doc_type(self): 1176 elt_doc_type = self.element_type.doc_type() 1177 if not elt_doc_type: 1178 return None 1179 return 'array of ' + elt_doc_type 1180 1181 def visit(self, visitor): 1182 visitor.visit_array_type(self.name, self.info, self.element_type) 1183 1184 1185class QAPISchemaObjectType(QAPISchemaType): 1186 def __init__(self, name, info, doc, base, local_members, variants): 1187 # struct has local_members, optional base, and no variants 1188 # flat union has base, variants, and no local_members 1189 # simple union has local_members, variants, and no base 1190 QAPISchemaType.__init__(self, name, info, doc) 1191 assert base is None or isinstance(base, str) 1192 for m in local_members: 1193 assert isinstance(m, QAPISchemaObjectTypeMember) 1194 m.set_owner(name) 1195 if variants is not None: 1196 assert isinstance(variants, QAPISchemaObjectTypeVariants) 1197 variants.set_owner(name) 1198 self._base_name = base 1199 self.base = None 1200 self.local_members = local_members 1201 self.variants = variants 1202 self.members = None 1203 1204 def check(self, schema): 1205 if self.members is False: # check for cycles 1206 raise QAPISemError(self.info, 1207 "Object %s contains itself" % self.name) 1208 if self.members: 1209 return 1210 self.members = False # mark as being checked 1211 seen = OrderedDict() 1212 if self._base_name: 1213 self.base = schema.lookup_type(self._base_name) 1214 assert isinstance(self.base, QAPISchemaObjectType) 1215 self.base.check(schema) 1216 self.base.check_clash(self.info, seen) 1217 for m in self.local_members: 1218 m.check(schema) 1219 m.check_clash(self.info, seen) 1220 if self.doc: 1221 self.doc.connect_member(m) 1222 self.members = seen.values() 1223 if self.variants: 1224 self.variants.check(schema, seen) 1225 assert self.variants.tag_member in self.members 1226 self.variants.check_clash(self.info, seen) 1227 if self.doc: 1228 self.doc.check() 1229 1230 # Check that the members of this type do not cause duplicate JSON members, 1231 # and update seen to track the members seen so far. Report any errors 1232 # on behalf of info, which is not necessarily self.info 1233 def check_clash(self, info, seen): 1234 assert not self.variants # not implemented 1235 for m in self.members: 1236 m.check_clash(info, seen) 1237 1238 def is_implicit(self): 1239 # See QAPISchema._make_implicit_object_type(), as well as 1240 # _def_predefineds() 1241 return self.name.startswith('q_') 1242 1243 def is_empty(self): 1244 assert self.members is not None 1245 return not self.members and not self.variants 1246 1247 def c_name(self): 1248 assert self.name != 'q_empty' 1249 return QAPISchemaType.c_name(self) 1250 1251 def c_type(self): 1252 assert not self.is_implicit() 1253 return c_name(self.name) + pointer_suffix 1254 1255 def c_unboxed_type(self): 1256 return c_name(self.name) 1257 1258 def json_type(self): 1259 return 'object' 1260 1261 def visit(self, visitor): 1262 visitor.visit_object_type(self.name, self.info, 1263 self.base, self.local_members, self.variants) 1264 visitor.visit_object_type_flat(self.name, self.info, 1265 self.members, self.variants) 1266 1267 1268class QAPISchemaMember(object): 1269 role = 'member' 1270 1271 def __init__(self, name): 1272 assert isinstance(name, str) 1273 self.name = name 1274 self.owner = None 1275 1276 def set_owner(self, name): 1277 assert not self.owner 1278 self.owner = name 1279 1280 def check_clash(self, info, seen): 1281 cname = c_name(self.name) 1282 if cname.lower() != cname and self.owner not in name_case_whitelist: 1283 raise QAPISemError(info, 1284 "%s should not use uppercase" % self.describe()) 1285 if cname in seen: 1286 raise QAPISemError(info, "%s collides with %s" % 1287 (self.describe(), seen[cname].describe())) 1288 seen[cname] = self 1289 1290 def _pretty_owner(self): 1291 owner = self.owner 1292 if owner.startswith('q_obj_'): 1293 # See QAPISchema._make_implicit_object_type() - reverse the 1294 # mapping there to create a nice human-readable description 1295 owner = owner[6:] 1296 if owner.endswith('-arg'): 1297 return '(parameter of %s)' % owner[:-4] 1298 elif owner.endswith('-base'): 1299 return '(base of %s)' % owner[:-5] 1300 else: 1301 assert owner.endswith('-wrapper') 1302 # Unreachable and not implemented 1303 assert False 1304 if owner.endswith('Kind'): 1305 # See QAPISchema._make_implicit_enum_type() 1306 return '(branch of %s)' % owner[:-4] 1307 return '(%s of %s)' % (self.role, owner) 1308 1309 def describe(self): 1310 return "'%s' %s" % (self.name, self._pretty_owner()) 1311 1312 1313class QAPISchemaObjectTypeMember(QAPISchemaMember): 1314 def __init__(self, name, typ, optional): 1315 QAPISchemaMember.__init__(self, name) 1316 assert isinstance(typ, str) 1317 assert isinstance(optional, bool) 1318 self._type_name = typ 1319 self.type = None 1320 self.optional = optional 1321 1322 def check(self, schema): 1323 assert self.owner 1324 self.type = schema.lookup_type(self._type_name) 1325 assert self.type 1326 1327 1328class QAPISchemaObjectTypeVariants(object): 1329 def __init__(self, tag_name, tag_member, variants): 1330 # Flat unions pass tag_name but not tag_member. 1331 # Simple unions and alternates pass tag_member but not tag_name. 1332 # After check(), tag_member is always set, and tag_name remains 1333 # a reliable witness of being used by a flat union. 1334 assert bool(tag_member) != bool(tag_name) 1335 assert (isinstance(tag_name, str) or 1336 isinstance(tag_member, QAPISchemaObjectTypeMember)) 1337 assert len(variants) > 0 1338 for v in variants: 1339 assert isinstance(v, QAPISchemaObjectTypeVariant) 1340 self._tag_name = tag_name 1341 self.tag_member = tag_member 1342 self.variants = variants 1343 1344 def set_owner(self, name): 1345 for v in self.variants: 1346 v.set_owner(name) 1347 1348 def check(self, schema, seen): 1349 if not self.tag_member: # flat union 1350 self.tag_member = seen[c_name(self._tag_name)] 1351 assert self._tag_name == self.tag_member.name 1352 assert isinstance(self.tag_member.type, QAPISchemaEnumType) 1353 if self._tag_name: # flat union 1354 # branches that are not explicitly covered get an empty type 1355 cases = set([v.name for v in self.variants]) 1356 for val in self.tag_member.type.values: 1357 if val.name not in cases: 1358 v = QAPISchemaObjectTypeVariant(val.name, 'q_empty') 1359 v.set_owner(self.tag_member.owner) 1360 self.variants.append(v) 1361 for v in self.variants: 1362 v.check(schema) 1363 # Union names must match enum values; alternate names are 1364 # checked separately. Use 'seen' to tell the two apart. 1365 if seen: 1366 assert v.name in self.tag_member.type.member_names() 1367 assert isinstance(v.type, QAPISchemaObjectType) 1368 v.type.check(schema) 1369 1370 def check_clash(self, info, seen): 1371 for v in self.variants: 1372 # Reset seen map for each variant, since qapi names from one 1373 # branch do not affect another branch 1374 assert isinstance(v.type, QAPISchemaObjectType) 1375 v.type.check_clash(info, dict(seen)) 1376 1377 1378class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember): 1379 role = 'branch' 1380 1381 def __init__(self, name, typ): 1382 QAPISchemaObjectTypeMember.__init__(self, name, typ, False) 1383 1384 1385class QAPISchemaAlternateType(QAPISchemaType): 1386 def __init__(self, name, info, doc, variants): 1387 QAPISchemaType.__init__(self, name, info, doc) 1388 assert isinstance(variants, QAPISchemaObjectTypeVariants) 1389 assert variants.tag_member 1390 variants.set_owner(name) 1391 variants.tag_member.set_owner(self.name) 1392 self.variants = variants 1393 1394 def check(self, schema): 1395 self.variants.tag_member.check(schema) 1396 # Not calling self.variants.check_clash(), because there's nothing 1397 # to clash with 1398 self.variants.check(schema, {}) 1399 # Alternate branch names have no relation to the tag enum values; 1400 # so we have to check for potential name collisions ourselves. 1401 seen = {} 1402 for v in self.variants.variants: 1403 v.check_clash(self.info, seen) 1404 if self.doc: 1405 self.doc.connect_member(v) 1406 if self.doc: 1407 self.doc.check() 1408 1409 def c_type(self): 1410 return c_name(self.name) + pointer_suffix 1411 1412 def json_type(self): 1413 return 'value' 1414 1415 def visit(self, visitor): 1416 visitor.visit_alternate_type(self.name, self.info, self.variants) 1417 1418 def is_empty(self): 1419 return False 1420 1421 1422class QAPISchemaCommand(QAPISchemaEntity): 1423 def __init__(self, name, info, doc, arg_type, ret_type, 1424 gen, success_response, boxed, allow_oob, allow_preconfig): 1425 QAPISchemaEntity.__init__(self, name, info, doc) 1426 assert not arg_type or isinstance(arg_type, str) 1427 assert not ret_type or isinstance(ret_type, str) 1428 self._arg_type_name = arg_type 1429 self.arg_type = None 1430 self._ret_type_name = ret_type 1431 self.ret_type = None 1432 self.gen = gen 1433 self.success_response = success_response 1434 self.boxed = boxed 1435 self.allow_oob = allow_oob 1436 self.allow_preconfig = allow_preconfig 1437 1438 def check(self, schema): 1439 if self._arg_type_name: 1440 self.arg_type = schema.lookup_type(self._arg_type_name) 1441 assert (isinstance(self.arg_type, QAPISchemaObjectType) or 1442 isinstance(self.arg_type, QAPISchemaAlternateType)) 1443 self.arg_type.check(schema) 1444 if self.boxed: 1445 if self.arg_type.is_empty(): 1446 raise QAPISemError(self.info, 1447 "Cannot use 'boxed' with empty type") 1448 else: 1449 assert not isinstance(self.arg_type, QAPISchemaAlternateType) 1450 assert not self.arg_type.variants 1451 elif self.boxed: 1452 raise QAPISemError(self.info, "Use of 'boxed' requires 'data'") 1453 if self._ret_type_name: 1454 self.ret_type = schema.lookup_type(self._ret_type_name) 1455 assert isinstance(self.ret_type, QAPISchemaType) 1456 1457 def visit(self, visitor): 1458 visitor.visit_command(self.name, self.info, 1459 self.arg_type, self.ret_type, 1460 self.gen, self.success_response, 1461 self.boxed, self.allow_oob, 1462 self.allow_preconfig) 1463 1464 1465class QAPISchemaEvent(QAPISchemaEntity): 1466 def __init__(self, name, info, doc, arg_type, boxed): 1467 QAPISchemaEntity.__init__(self, name, info, doc) 1468 assert not arg_type or isinstance(arg_type, str) 1469 self._arg_type_name = arg_type 1470 self.arg_type = None 1471 self.boxed = boxed 1472 1473 def check(self, schema): 1474 if self._arg_type_name: 1475 self.arg_type = schema.lookup_type(self._arg_type_name) 1476 assert (isinstance(self.arg_type, QAPISchemaObjectType) or 1477 isinstance(self.arg_type, QAPISchemaAlternateType)) 1478 self.arg_type.check(schema) 1479 if self.boxed: 1480 if self.arg_type.is_empty(): 1481 raise QAPISemError(self.info, 1482 "Cannot use 'boxed' with empty type") 1483 else: 1484 assert not isinstance(self.arg_type, QAPISchemaAlternateType) 1485 assert not self.arg_type.variants 1486 elif self.boxed: 1487 raise QAPISemError(self.info, "Use of 'boxed' requires 'data'") 1488 1489 def visit(self, visitor): 1490 visitor.visit_event(self.name, self.info, self.arg_type, self.boxed) 1491 1492 1493class QAPISchema(object): 1494 def __init__(self, fname): 1495 self._fname = fname 1496 parser = QAPISchemaParser(open(fname, 'r')) 1497 exprs = check_exprs(parser.exprs) 1498 self.docs = parser.docs 1499 self._entity_list = [] 1500 self._entity_dict = {} 1501 self._predefining = True 1502 self._def_predefineds() 1503 self._predefining = False 1504 self._def_exprs(exprs) 1505 self.check() 1506 1507 def _def_entity(self, ent): 1508 # Only the predefined types are allowed to not have info 1509 assert ent.info or self._predefining 1510 assert ent.name is None or ent.name not in self._entity_dict 1511 self._entity_list.append(ent) 1512 if ent.name is not None: 1513 self._entity_dict[ent.name] = ent 1514 if ent.info: 1515 ent.module = os.path.relpath(ent.info['file'], 1516 os.path.dirname(self._fname)) 1517 1518 def lookup_entity(self, name, typ=None): 1519 ent = self._entity_dict.get(name) 1520 if typ and not isinstance(ent, typ): 1521 return None 1522 return ent 1523 1524 def lookup_type(self, name): 1525 return self.lookup_entity(name, QAPISchemaType) 1526 1527 def _def_include(self, expr, info, doc): 1528 include = expr['include'] 1529 assert doc is None 1530 main_info = info 1531 while main_info['parent']: 1532 main_info = main_info['parent'] 1533 fname = os.path.relpath(include, os.path.dirname(main_info['file'])) 1534 self._def_entity(QAPISchemaInclude(fname, info)) 1535 1536 def _def_builtin_type(self, name, json_type, c_type): 1537 self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type)) 1538 # Instantiating only the arrays that are actually used would 1539 # be nice, but we can't as long as their generated code 1540 # (qapi-builtin-types.[ch]) may be shared by some other 1541 # schema. 1542 self._make_array_type(name, None) 1543 1544 def _def_predefineds(self): 1545 for t in [('str', 'string', 'char' + pointer_suffix), 1546 ('number', 'number', 'double'), 1547 ('int', 'int', 'int64_t'), 1548 ('int8', 'int', 'int8_t'), 1549 ('int16', 'int', 'int16_t'), 1550 ('int32', 'int', 'int32_t'), 1551 ('int64', 'int', 'int64_t'), 1552 ('uint8', 'int', 'uint8_t'), 1553 ('uint16', 'int', 'uint16_t'), 1554 ('uint32', 'int', 'uint32_t'), 1555 ('uint64', 'int', 'uint64_t'), 1556 ('size', 'int', 'uint64_t'), 1557 ('bool', 'boolean', 'bool'), 1558 ('any', 'value', 'QObject' + pointer_suffix), 1559 ('null', 'null', 'QNull' + pointer_suffix)]: 1560 self._def_builtin_type(*t) 1561 self.the_empty_object_type = QAPISchemaObjectType( 1562 'q_empty', None, None, None, [], None) 1563 self._def_entity(self.the_empty_object_type) 1564 qtype_values = self._make_enum_members(['none', 'qnull', 'qnum', 1565 'qstring', 'qdict', 'qlist', 1566 'qbool']) 1567 self._def_entity(QAPISchemaEnumType('QType', None, None, 1568 qtype_values, 'QTYPE')) 1569 1570 def _make_enum_members(self, values): 1571 return [QAPISchemaMember(v) for v in values] 1572 1573 def _make_implicit_enum_type(self, name, info, values): 1574 # See also QAPISchemaObjectTypeMember._pretty_owner() 1575 name = name + 'Kind' # Use namespace reserved by add_name() 1576 self._def_entity(QAPISchemaEnumType( 1577 name, info, None, self._make_enum_members(values), None)) 1578 return name 1579 1580 def _make_array_type(self, element_type, info): 1581 name = element_type + 'List' # Use namespace reserved by add_name() 1582 if not self.lookup_type(name): 1583 self._def_entity(QAPISchemaArrayType(name, info, element_type)) 1584 return name 1585 1586 def _make_implicit_object_type(self, name, info, doc, role, members): 1587 if not members: 1588 return None 1589 # See also QAPISchemaObjectTypeMember._pretty_owner() 1590 name = 'q_obj_%s-%s' % (name, role) 1591 if not self.lookup_entity(name, QAPISchemaObjectType): 1592 self._def_entity(QAPISchemaObjectType(name, info, doc, None, 1593 members, None)) 1594 return name 1595 1596 def _def_enum_type(self, expr, info, doc): 1597 name = expr['enum'] 1598 data = expr['data'] 1599 prefix = expr.get('prefix') 1600 self._def_entity(QAPISchemaEnumType( 1601 name, info, doc, self._make_enum_members(data), prefix)) 1602 1603 def _make_member(self, name, typ, info): 1604 optional = False 1605 if name.startswith('*'): 1606 name = name[1:] 1607 optional = True 1608 if isinstance(typ, list): 1609 assert len(typ) == 1 1610 typ = self._make_array_type(typ[0], info) 1611 return QAPISchemaObjectTypeMember(name, typ, optional) 1612 1613 def _make_members(self, data, info): 1614 return [self._make_member(key, value, info) 1615 for (key, value) in data.items()] 1616 1617 def _def_struct_type(self, expr, info, doc): 1618 name = expr['struct'] 1619 base = expr.get('base') 1620 data = expr['data'] 1621 self._def_entity(QAPISchemaObjectType(name, info, doc, base, 1622 self._make_members(data, info), 1623 None)) 1624 1625 def _make_variant(self, case, typ): 1626 return QAPISchemaObjectTypeVariant(case, typ) 1627 1628 def _make_simple_variant(self, case, typ, info): 1629 if isinstance(typ, list): 1630 assert len(typ) == 1 1631 typ = self._make_array_type(typ[0], info) 1632 typ = self._make_implicit_object_type( 1633 typ, info, None, 'wrapper', [self._make_member('data', typ, info)]) 1634 return QAPISchemaObjectTypeVariant(case, typ) 1635 1636 def _def_union_type(self, expr, info, doc): 1637 name = expr['union'] 1638 data = expr['data'] 1639 base = expr.get('base') 1640 tag_name = expr.get('discriminator') 1641 tag_member = None 1642 if isinstance(base, dict): 1643 base = (self._make_implicit_object_type( 1644 name, info, doc, 'base', self._make_members(base, info))) 1645 if tag_name: 1646 variants = [self._make_variant(key, value) 1647 for (key, value) in data.items()] 1648 members = [] 1649 else: 1650 variants = [self._make_simple_variant(key, value, info) 1651 for (key, value) in data.items()] 1652 typ = self._make_implicit_enum_type(name, info, 1653 [v.name for v in variants]) 1654 tag_member = QAPISchemaObjectTypeMember('type', typ, False) 1655 members = [tag_member] 1656 self._def_entity( 1657 QAPISchemaObjectType(name, info, doc, base, members, 1658 QAPISchemaObjectTypeVariants(tag_name, 1659 tag_member, 1660 variants))) 1661 1662 def _def_alternate_type(self, expr, info, doc): 1663 name = expr['alternate'] 1664 data = expr['data'] 1665 variants = [self._make_variant(key, value) 1666 for (key, value) in data.items()] 1667 tag_member = QAPISchemaObjectTypeMember('type', 'QType', False) 1668 self._def_entity( 1669 QAPISchemaAlternateType(name, info, doc, 1670 QAPISchemaObjectTypeVariants(None, 1671 tag_member, 1672 variants))) 1673 1674 def _def_command(self, expr, info, doc): 1675 name = expr['command'] 1676 data = expr.get('data') 1677 rets = expr.get('returns') 1678 gen = expr.get('gen', True) 1679 success_response = expr.get('success-response', True) 1680 boxed = expr.get('boxed', False) 1681 allow_oob = expr.get('allow-oob', False) 1682 allow_preconfig = expr.get('allow-preconfig', False) 1683 if isinstance(data, OrderedDict): 1684 data = self._make_implicit_object_type( 1685 name, info, doc, 'arg', self._make_members(data, info)) 1686 if isinstance(rets, list): 1687 assert len(rets) == 1 1688 rets = self._make_array_type(rets[0], info) 1689 self._def_entity(QAPISchemaCommand(name, info, doc, data, rets, 1690 gen, success_response, 1691 boxed, allow_oob, allow_preconfig)) 1692 1693 def _def_event(self, expr, info, doc): 1694 name = expr['event'] 1695 data = expr.get('data') 1696 boxed = expr.get('boxed', False) 1697 if isinstance(data, OrderedDict): 1698 data = self._make_implicit_object_type( 1699 name, info, doc, 'arg', self._make_members(data, info)) 1700 self._def_entity(QAPISchemaEvent(name, info, doc, data, boxed)) 1701 1702 def _def_exprs(self, exprs): 1703 for expr_elem in exprs: 1704 expr = expr_elem['expr'] 1705 info = expr_elem['info'] 1706 doc = expr_elem.get('doc') 1707 if 'enum' in expr: 1708 self._def_enum_type(expr, info, doc) 1709 elif 'struct' in expr: 1710 self._def_struct_type(expr, info, doc) 1711 elif 'union' in expr: 1712 self._def_union_type(expr, info, doc) 1713 elif 'alternate' in expr: 1714 self._def_alternate_type(expr, info, doc) 1715 elif 'command' in expr: 1716 self._def_command(expr, info, doc) 1717 elif 'event' in expr: 1718 self._def_event(expr, info, doc) 1719 elif 'include' in expr: 1720 self._def_include(expr, info, doc) 1721 else: 1722 assert False 1723 1724 def check(self): 1725 for ent in self._entity_list: 1726 ent.check(self) 1727 1728 def visit(self, visitor): 1729 visitor.visit_begin(self) 1730 module = None 1731 for entity in self._entity_list: 1732 if visitor.visit_needed(entity): 1733 if entity.module != module: 1734 module = entity.module 1735 visitor.visit_module(module) 1736 entity.visit(visitor) 1737 visitor.visit_end() 1738 1739 1740# 1741# Code generation helpers 1742# 1743 1744def camel_case(name): 1745 new_name = '' 1746 first = True 1747 for ch in name: 1748 if ch in ['_', '-']: 1749 first = True 1750 elif first: 1751 new_name += ch.upper() 1752 first = False 1753 else: 1754 new_name += ch.lower() 1755 return new_name 1756 1757 1758# ENUMName -> ENUM_NAME, EnumName1 -> ENUM_NAME1 1759# ENUM_NAME -> ENUM_NAME, ENUM_NAME1 -> ENUM_NAME1, ENUM_Name2 -> ENUM_NAME2 1760# ENUM24_Name -> ENUM24_NAME 1761def camel_to_upper(value): 1762 c_fun_str = c_name(value, False) 1763 if value.isupper(): 1764 return c_fun_str 1765 1766 new_name = '' 1767 l = len(c_fun_str) 1768 for i in range(l): 1769 c = c_fun_str[i] 1770 # When c is upper and no '_' appears before, do more checks 1771 if c.isupper() and (i > 0) and c_fun_str[i - 1] != '_': 1772 if i < l - 1 and c_fun_str[i + 1].islower(): 1773 new_name += '_' 1774 elif c_fun_str[i - 1].isdigit(): 1775 new_name += '_' 1776 new_name += c 1777 return new_name.lstrip('_').upper() 1778 1779 1780def c_enum_const(type_name, const_name, prefix=None): 1781 if prefix is not None: 1782 type_name = prefix 1783 return camel_to_upper(type_name) + '_' + c_name(const_name, False).upper() 1784 1785if hasattr(str, 'maketrans'): 1786 c_name_trans = str.maketrans('.-', '__') 1787else: 1788 c_name_trans = string.maketrans('.-', '__') 1789 1790 1791# Map @name to a valid C identifier. 1792# If @protect, avoid returning certain ticklish identifiers (like 1793# C keywords) by prepending 'q_'. 1794# 1795# Used for converting 'name' from a 'name':'type' qapi definition 1796# into a generated struct member, as well as converting type names 1797# into substrings of a generated C function name. 1798# '__a.b_c' -> '__a_b_c', 'x-foo' -> 'x_foo' 1799# protect=True: 'int' -> 'q_int'; protect=False: 'int' -> 'int' 1800def c_name(name, protect=True): 1801 # ANSI X3J11/88-090, 3.1.1 1802 c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue', 1803 'default', 'do', 'double', 'else', 'enum', 'extern', 1804 'float', 'for', 'goto', 'if', 'int', 'long', 'register', 1805 'return', 'short', 'signed', 'sizeof', 'static', 1806 'struct', 'switch', 'typedef', 'union', 'unsigned', 1807 'void', 'volatile', 'while']) 1808 # ISO/IEC 9899:1999, 6.4.1 1809 c99_words = set(['inline', 'restrict', '_Bool', '_Complex', '_Imaginary']) 1810 # ISO/IEC 9899:2011, 6.4.1 1811 c11_words = set(['_Alignas', '_Alignof', '_Atomic', '_Generic', 1812 '_Noreturn', '_Static_assert', '_Thread_local']) 1813 # GCC http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/C-Extensions.html 1814 # excluding _.* 1815 gcc_words = set(['asm', 'typeof']) 1816 # C++ ISO/IEC 14882:2003 2.11 1817 cpp_words = set(['bool', 'catch', 'class', 'const_cast', 'delete', 1818 'dynamic_cast', 'explicit', 'false', 'friend', 'mutable', 1819 'namespace', 'new', 'operator', 'private', 'protected', 1820 'public', 'reinterpret_cast', 'static_cast', 'template', 1821 'this', 'throw', 'true', 'try', 'typeid', 'typename', 1822 'using', 'virtual', 'wchar_t', 1823 # alternative representations 1824 'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not', 1825 'not_eq', 'or', 'or_eq', 'xor', 'xor_eq']) 1826 # namespace pollution: 1827 polluted_words = set(['unix', 'errno', 'mips', 'sparc', 'i386']) 1828 name = name.translate(c_name_trans) 1829 if protect and (name in c89_words | c99_words | c11_words | gcc_words 1830 | cpp_words | polluted_words): 1831 return 'q_' + name 1832 return name 1833 1834eatspace = '\033EATSPACE.' 1835pointer_suffix = ' *' + eatspace 1836 1837 1838def genindent(count): 1839 ret = '' 1840 for _ in range(count): 1841 ret += ' ' 1842 return ret 1843 1844indent_level = 0 1845 1846 1847def push_indent(indent_amount=4): 1848 global indent_level 1849 indent_level += indent_amount 1850 1851 1852def pop_indent(indent_amount=4): 1853 global indent_level 1854 indent_level -= indent_amount 1855 1856 1857# Generate @code with @kwds interpolated. 1858# Obey indent_level, and strip eatspace. 1859def cgen(code, **kwds): 1860 raw = code % kwds 1861 if indent_level: 1862 indent = genindent(indent_level) 1863 # re.subn() lacks flags support before Python 2.7, use re.compile() 1864 raw = re.subn(re.compile(r'^.', re.MULTILINE), 1865 indent + r'\g<0>', raw) 1866 raw = raw[0] 1867 return re.sub(re.escape(eatspace) + r' *', '', raw) 1868 1869 1870def mcgen(code, **kwds): 1871 if code[0] == '\n': 1872 code = code[1:] 1873 return cgen(code, **kwds) 1874 1875 1876def guardname(filename): 1877 return re.sub(r'[^A-Za-z0-9_]', '_', filename).upper() 1878 1879 1880def guardstart(name): 1881 return mcgen(''' 1882#ifndef %(name)s 1883#define %(name)s 1884 1885''', 1886 name=guardname(name)) 1887 1888 1889def guardend(name): 1890 return mcgen(''' 1891 1892#endif /* %(name)s */ 1893''', 1894 name=guardname(name)) 1895 1896 1897def gen_enum_lookup(name, values, prefix=None): 1898 ret = mcgen(''' 1899 1900const QEnumLookup %(c_name)s_lookup = { 1901 .array = (const char *const[]) { 1902''', 1903 c_name=c_name(name)) 1904 for value in values: 1905 index = c_enum_const(name, value, prefix) 1906 ret += mcgen(''' 1907 [%(index)s] = "%(value)s", 1908''', 1909 index=index, value=value) 1910 1911 ret += mcgen(''' 1912 }, 1913 .size = %(max_index)s 1914}; 1915''', 1916 max_index=c_enum_const(name, '_MAX', prefix)) 1917 return ret 1918 1919 1920def gen_enum(name, values, prefix=None): 1921 # append automatically generated _MAX value 1922 enum_values = values + ['_MAX'] 1923 1924 ret = mcgen(''' 1925 1926typedef enum %(c_name)s { 1927''', 1928 c_name=c_name(name)) 1929 1930 i = 0 1931 for value in enum_values: 1932 ret += mcgen(''' 1933 %(c_enum)s = %(i)d, 1934''', 1935 c_enum=c_enum_const(name, value, prefix), 1936 i=i) 1937 i += 1 1938 1939 ret += mcgen(''' 1940} %(c_name)s; 1941''', 1942 c_name=c_name(name)) 1943 1944 ret += mcgen(''' 1945 1946#define %(c_name)s_str(val) \\ 1947 qapi_enum_lookup(&%(c_name)s_lookup, (val)) 1948 1949extern const QEnumLookup %(c_name)s_lookup; 1950''', 1951 c_name=c_name(name)) 1952 return ret 1953 1954 1955def build_params(arg_type, boxed, extra): 1956 if not arg_type: 1957 assert not boxed 1958 return extra 1959 ret = '' 1960 sep = '' 1961 if boxed: 1962 ret += '%s arg' % arg_type.c_param_type() 1963 sep = ', ' 1964 else: 1965 assert not arg_type.variants 1966 for memb in arg_type.members: 1967 ret += sep 1968 sep = ', ' 1969 if memb.optional: 1970 ret += 'bool has_%s, ' % c_name(memb.name) 1971 ret += '%s %s' % (memb.type.c_param_type(), 1972 c_name(memb.name)) 1973 if extra: 1974 ret += sep + extra 1975 return ret 1976 1977 1978# 1979# Accumulate and write output 1980# 1981 1982class QAPIGen(object): 1983 1984 def __init__(self): 1985 self._preamble = '' 1986 self._body = '' 1987 1988 def preamble_add(self, text): 1989 self._preamble += text 1990 1991 def add(self, text): 1992 self._body += text 1993 1994 def _top(self, fname): 1995 return '' 1996 1997 def _bottom(self, fname): 1998 return '' 1999 2000 def write(self, output_dir, fname): 2001 pathname = os.path.join(output_dir, fname) 2002 dir = os.path.dirname(pathname) 2003 if dir: 2004 try: 2005 os.makedirs(dir) 2006 except os.error as e: 2007 if e.errno != errno.EEXIST: 2008 raise 2009 fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666) 2010 f = os.fdopen(fd, 'r+') 2011 text = (self._top(fname) + self._preamble + self._body 2012 + self._bottom(fname)) 2013 oldtext = f.read(len(text) + 1) 2014 if text != oldtext: 2015 f.seek(0) 2016 f.truncate(0) 2017 f.write(text) 2018 f.close() 2019 2020 2021class QAPIGenC(QAPIGen): 2022 2023 def __init__(self, blurb, pydoc): 2024 QAPIGen.__init__(self) 2025 self._blurb = blurb 2026 self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc, 2027 re.MULTILINE)) 2028 2029 def _top(self, fname): 2030 return mcgen(''' 2031/* AUTOMATICALLY GENERATED, DO NOT MODIFY */ 2032 2033/* 2034%(blurb)s 2035 * 2036 * %(copyright)s 2037 * 2038 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. 2039 * See the COPYING.LIB file in the top-level directory. 2040 */ 2041 2042''', 2043 blurb=self._blurb, copyright=self._copyright) 2044 2045 def _bottom(self, fname): 2046 return mcgen(''' 2047/* Dummy declaration to prevent empty .o file */ 2048char dummy_%(name)s; 2049''', 2050 name=c_name(fname)) 2051 2052 2053class QAPIGenH(QAPIGenC): 2054 2055 def _top(self, fname): 2056 return QAPIGenC._top(self, fname) + guardstart(fname) 2057 2058 def _bottom(self, fname): 2059 return guardend(fname) 2060 2061 2062class QAPIGenDoc(QAPIGen): 2063 2064 def _top(self, fname): 2065 return (QAPIGen._top(self, fname) 2066 + '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n') 2067 2068 2069class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor): 2070 2071 def __init__(self, prefix, what, blurb, pydoc): 2072 self._prefix = prefix 2073 self._what = what 2074 self._genc = QAPIGenC(blurb, pydoc) 2075 self._genh = QAPIGenH(blurb, pydoc) 2076 2077 def write(self, output_dir): 2078 self._genc.write(output_dir, self._prefix + self._what + '.c') 2079 self._genh.write(output_dir, self._prefix + self._what + '.h') 2080 2081 2082class QAPISchemaModularCVisitor(QAPISchemaVisitor): 2083 2084 def __init__(self, prefix, what, blurb, pydoc): 2085 self._prefix = prefix 2086 self._what = what 2087 self._blurb = blurb 2088 self._pydoc = pydoc 2089 self._module = {} 2090 self._main_module = None 2091 2092 def _module_basename(self, what, name): 2093 if name is None: 2094 return re.sub(r'-', '-builtin-', what) 2095 basename = os.path.join(os.path.dirname(name), 2096 self._prefix + what) 2097 if name == self._main_module: 2098 return basename 2099 return basename + '-' + os.path.splitext(os.path.basename(name))[0] 2100 2101 def _add_module(self, name, blurb): 2102 if self._main_module is None and name is not None: 2103 self._main_module = name 2104 genc = QAPIGenC(blurb, self._pydoc) 2105 genh = QAPIGenH(blurb, self._pydoc) 2106 self._module[name] = (genc, genh) 2107 self._set_module(name) 2108 2109 def _set_module(self, name): 2110 self._genc, self._genh = self._module[name] 2111 2112 def write(self, output_dir, opt_builtins=False): 2113 for name in self._module: 2114 if name is None and not opt_builtins: 2115 continue 2116 basename = self._module_basename(self._what, name) 2117 (genc, genh) = self._module[name] 2118 genc.write(output_dir, basename + '.c') 2119 genh.write(output_dir, basename + '.h') 2120 2121 def _begin_module(self, name): 2122 pass 2123 2124 def visit_module(self, name): 2125 if name in self._module: 2126 self._set_module(name) 2127 return 2128 self._add_module(name, self._blurb) 2129 self._begin_module(name) 2130 2131 def visit_include(self, name, info): 2132 basename = self._module_basename(self._what, name) 2133 self._genh.preamble_add(mcgen(''' 2134#include "%(basename)s.h" 2135''', 2136 basename=basename)) 2137