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