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