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