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