schema.py (41640B)
1 # -*- coding: utf-8 -*- 2 # 3 # QAPI schema internal representation 4 # 5 # Copyright (c) 2015-2019 Red Hat Inc. 6 # 7 # Authors: 8 # Markus Armbruster <armbru@redhat.com> 9 # Eric Blake <eblake@redhat.com> 10 # Marc-André Lureau <marcandre.lureau@redhat.com> 11 # 12 # This work is licensed under the terms of the GNU GPL, version 2. 13 # See the COPYING file in the top-level directory. 14 15 # TODO catching name collisions in generated code would be nice 16 17 from collections import OrderedDict 18 import os 19 import re 20 from typing import Optional 21 22 from .common import ( 23 POINTER_SUFFIX, 24 c_name, 25 cgen_ifcond, 26 docgen_ifcond, 27 gen_endif, 28 gen_if, 29 ) 30 from .error import QAPIError, QAPISemError, QAPISourceError 31 from .expr import check_exprs 32 from .parser import QAPISchemaParser 33 34 35 class QAPISchemaIfCond: 36 def __init__(self, ifcond=None): 37 self.ifcond = ifcond 38 39 def _cgen(self): 40 return cgen_ifcond(self.ifcond) 41 42 def gen_if(self): 43 return gen_if(self._cgen()) 44 45 def gen_endif(self): 46 return gen_endif(self._cgen()) 47 48 def docgen(self): 49 return docgen_ifcond(self.ifcond) 50 51 def is_present(self): 52 return bool(self.ifcond) 53 54 55 class QAPISchemaEntity: 56 meta: Optional[str] = None 57 58 def __init__(self, name: str, info, doc, ifcond=None, features=None): 59 assert name is None or isinstance(name, str) 60 for f in features or []: 61 assert isinstance(f, QAPISchemaFeature) 62 f.set_defined_in(name) 63 self.name = name 64 self._module = None 65 # For explicitly defined entities, info points to the (explicit) 66 # definition. For builtins (and their arrays), info is None. 67 # For implicitly defined entities, info points to a place that 68 # triggered the implicit definition (there may be more than one 69 # such place). 70 self.info = info 71 self.doc = doc 72 self._ifcond = ifcond or QAPISchemaIfCond() 73 self.features = features or [] 74 self._checked = False 75 76 def c_name(self): 77 return c_name(self.name) 78 79 def check(self, schema): 80 assert not self._checked 81 seen = {} 82 for f in self.features: 83 f.check_clash(self.info, seen) 84 self._checked = True 85 86 def connect_doc(self, doc=None): 87 doc = doc or self.doc 88 if doc: 89 for f in self.features: 90 doc.connect_feature(f) 91 92 def check_doc(self): 93 if self.doc: 94 self.doc.check() 95 96 def _set_module(self, schema, info): 97 assert self._checked 98 fname = info.fname if info else QAPISchemaModule.BUILTIN_MODULE_NAME 99 self._module = schema.module_by_fname(fname) 100 self._module.add_entity(self) 101 102 def set_module(self, schema): 103 self._set_module(schema, self.info) 104 105 @property 106 def ifcond(self): 107 assert self._checked 108 return self._ifcond 109 110 def is_implicit(self): 111 return not self.info 112 113 def visit(self, visitor): 114 assert self._checked 115 116 def describe(self): 117 assert self.meta 118 return "%s '%s'" % (self.meta, self.name) 119 120 121 class QAPISchemaVisitor: 122 def visit_begin(self, schema): 123 pass 124 125 def visit_end(self): 126 pass 127 128 def visit_module(self, name): 129 pass 130 131 def visit_needed(self, entity): 132 # Default to visiting everything 133 return True 134 135 def visit_include(self, name, info): 136 pass 137 138 def visit_builtin_type(self, name, info, json_type): 139 pass 140 141 def visit_enum_type(self, name, info, ifcond, features, members, prefix): 142 pass 143 144 def visit_array_type(self, name, info, ifcond, element_type): 145 pass 146 147 def visit_object_type(self, name, info, ifcond, features, 148 base, members, variants): 149 pass 150 151 def visit_object_type_flat(self, name, info, ifcond, features, 152 members, variants): 153 pass 154 155 def visit_alternate_type(self, name, info, ifcond, features, variants): 156 pass 157 158 def visit_command(self, name, info, ifcond, features, 159 arg_type, ret_type, gen, success_response, boxed, 160 allow_oob, allow_preconfig, coroutine): 161 pass 162 163 def visit_event(self, name, info, ifcond, features, arg_type, boxed): 164 pass 165 166 167 class QAPISchemaModule: 168 169 BUILTIN_MODULE_NAME = './builtin' 170 171 def __init__(self, name): 172 self.name = name 173 self._entity_list = [] 174 175 @staticmethod 176 def is_system_module(name: str) -> bool: 177 """ 178 System modules are internally defined modules. 179 180 Their names start with the "./" prefix. 181 """ 182 return name.startswith('./') 183 184 @classmethod 185 def is_user_module(cls, name: str) -> bool: 186 """ 187 User modules are those defined by the user in qapi JSON files. 188 189 They do not start with the "./" prefix. 190 """ 191 return not cls.is_system_module(name) 192 193 @classmethod 194 def is_builtin_module(cls, name: str) -> bool: 195 """ 196 The built-in module is a single System module for the built-in types. 197 198 It is always "./builtin". 199 """ 200 return name == cls.BUILTIN_MODULE_NAME 201 202 def add_entity(self, ent): 203 self._entity_list.append(ent) 204 205 def visit(self, visitor): 206 visitor.visit_module(self.name) 207 for entity in self._entity_list: 208 if visitor.visit_needed(entity): 209 entity.visit(visitor) 210 211 212 class QAPISchemaInclude(QAPISchemaEntity): 213 def __init__(self, sub_module, info): 214 super().__init__(None, info, None) 215 self._sub_module = sub_module 216 217 def visit(self, visitor): 218 super().visit(visitor) 219 visitor.visit_include(self._sub_module.name, self.info) 220 221 222 class QAPISchemaType(QAPISchemaEntity): 223 # Return the C type for common use. 224 # For the types we commonly box, this is a pointer type. 225 def c_type(self): 226 pass 227 228 # Return the C type to be used in a parameter list. 229 def c_param_type(self): 230 return self.c_type() 231 232 # Return the C type to be used where we suppress boxing. 233 def c_unboxed_type(self): 234 return self.c_type() 235 236 def json_type(self): 237 pass 238 239 def alternate_qtype(self): 240 json2qtype = { 241 'null': 'QTYPE_QNULL', 242 'string': 'QTYPE_QSTRING', 243 'number': 'QTYPE_QNUM', 244 'int': 'QTYPE_QNUM', 245 'boolean': 'QTYPE_QBOOL', 246 'array': 'QTYPE_QLIST', 247 'object': 'QTYPE_QDICT' 248 } 249 return json2qtype.get(self.json_type()) 250 251 def doc_type(self): 252 if self.is_implicit(): 253 return None 254 return self.name 255 256 def check(self, schema): 257 QAPISchemaEntity.check(self, schema) 258 for feat in self.features: 259 if feat.is_special(): 260 raise QAPISemError( 261 self.info, 262 f"feature '{feat.name}' is not supported for types") 263 264 def describe(self): 265 assert self.meta 266 return "%s type '%s'" % (self.meta, self.name) 267 268 269 class QAPISchemaBuiltinType(QAPISchemaType): 270 meta = 'built-in' 271 272 def __init__(self, name, json_type, c_type): 273 super().__init__(name, None, None) 274 assert not c_type or isinstance(c_type, str) 275 assert json_type in ('string', 'number', 'int', 'boolean', 'null', 276 'value') 277 self._json_type_name = json_type 278 self._c_type_name = c_type 279 280 def c_name(self): 281 return self.name 282 283 def c_type(self): 284 return self._c_type_name 285 286 def c_param_type(self): 287 if self.name == 'str': 288 return 'const ' + self._c_type_name 289 return self._c_type_name 290 291 def json_type(self): 292 return self._json_type_name 293 294 def doc_type(self): 295 return self.json_type() 296 297 def visit(self, visitor): 298 super().visit(visitor) 299 visitor.visit_builtin_type(self.name, self.info, self.json_type()) 300 301 302 class QAPISchemaEnumType(QAPISchemaType): 303 meta = 'enum' 304 305 def __init__(self, name, info, doc, ifcond, features, members, prefix): 306 super().__init__(name, info, doc, ifcond, features) 307 for m in members: 308 assert isinstance(m, QAPISchemaEnumMember) 309 m.set_defined_in(name) 310 assert prefix is None or isinstance(prefix, str) 311 self.members = members 312 self.prefix = prefix 313 314 def check(self, schema): 315 super().check(schema) 316 seen = {} 317 for m in self.members: 318 m.check_clash(self.info, seen) 319 320 def connect_doc(self, doc=None): 321 super().connect_doc(doc) 322 doc = doc or self.doc 323 for m in self.members: 324 m.connect_doc(doc) 325 326 def is_implicit(self): 327 # See QAPISchema._def_predefineds() 328 return self.name == 'QType' 329 330 def c_type(self): 331 return c_name(self.name) 332 333 def member_names(self): 334 return [m.name for m in self.members] 335 336 def json_type(self): 337 return 'string' 338 339 def visit(self, visitor): 340 super().visit(visitor) 341 visitor.visit_enum_type( 342 self.name, self.info, self.ifcond, self.features, 343 self.members, self.prefix) 344 345 346 class QAPISchemaArrayType(QAPISchemaType): 347 meta = 'array' 348 349 def __init__(self, name, info, element_type): 350 super().__init__(name, info, None) 351 assert isinstance(element_type, str) 352 self._element_type_name = element_type 353 self.element_type = None 354 355 def check(self, schema): 356 super().check(schema) 357 self.element_type = schema.resolve_type( 358 self._element_type_name, self.info, 359 self.info and self.info.defn_meta) 360 assert not isinstance(self.element_type, QAPISchemaArrayType) 361 362 def set_module(self, schema): 363 self._set_module(schema, self.element_type.info) 364 365 @property 366 def ifcond(self): 367 assert self._checked 368 return self.element_type.ifcond 369 370 def is_implicit(self): 371 return True 372 373 def c_type(self): 374 return c_name(self.name) + POINTER_SUFFIX 375 376 def json_type(self): 377 return 'array' 378 379 def doc_type(self): 380 elt_doc_type = self.element_type.doc_type() 381 if not elt_doc_type: 382 return None 383 return 'array of ' + elt_doc_type 384 385 def visit(self, visitor): 386 super().visit(visitor) 387 visitor.visit_array_type(self.name, self.info, self.ifcond, 388 self.element_type) 389 390 def describe(self): 391 assert self.meta 392 return "%s type ['%s']" % (self.meta, self._element_type_name) 393 394 395 class QAPISchemaObjectType(QAPISchemaType): 396 def __init__(self, name, info, doc, ifcond, features, 397 base, local_members, variants): 398 # struct has local_members, optional base, and no variants 399 # union has base, variants, and no local_members 400 super().__init__(name, info, doc, ifcond, features) 401 self.meta = 'union' if variants else 'struct' 402 assert base is None or isinstance(base, str) 403 for m in local_members: 404 assert isinstance(m, QAPISchemaObjectTypeMember) 405 m.set_defined_in(name) 406 if variants is not None: 407 assert isinstance(variants, QAPISchemaVariants) 408 variants.set_defined_in(name) 409 self._base_name = base 410 self.base = None 411 self.local_members = local_members 412 self.variants = variants 413 self.members = None 414 415 def check(self, schema): 416 # This calls another type T's .check() exactly when the C 417 # struct emitted by gen_object() contains that T's C struct 418 # (pointers don't count). 419 if self.members is not None: 420 # A previous .check() completed: nothing to do 421 return 422 if self._checked: 423 # Recursed: C struct contains itself 424 raise QAPISemError(self.info, 425 "object %s contains itself" % self.name) 426 427 super().check(schema) 428 assert self._checked and self.members is None 429 430 seen = OrderedDict() 431 if self._base_name: 432 self.base = schema.resolve_type(self._base_name, self.info, 433 "'base'") 434 if (not isinstance(self.base, QAPISchemaObjectType) 435 or self.base.variants): 436 raise QAPISemError( 437 self.info, 438 "'base' requires a struct type, %s isn't" 439 % self.base.describe()) 440 self.base.check(schema) 441 self.base.check_clash(self.info, seen) 442 for m in self.local_members: 443 m.check(schema) 444 m.check_clash(self.info, seen) 445 members = seen.values() 446 447 if self.variants: 448 self.variants.check(schema, seen) 449 self.variants.check_clash(self.info, seen) 450 451 self.members = members # mark completed 452 453 # Check that the members of this type do not cause duplicate JSON members, 454 # and update seen to track the members seen so far. Report any errors 455 # on behalf of info, which is not necessarily self.info 456 def check_clash(self, info, seen): 457 assert self._checked 458 assert not self.variants # not implemented 459 for m in self.members: 460 m.check_clash(info, seen) 461 462 def connect_doc(self, doc=None): 463 super().connect_doc(doc) 464 doc = doc or self.doc 465 if self.base and self.base.is_implicit(): 466 self.base.connect_doc(doc) 467 for m in self.local_members: 468 m.connect_doc(doc) 469 470 def is_implicit(self): 471 # See QAPISchema._make_implicit_object_type(), as well as 472 # _def_predefineds() 473 return self.name.startswith('q_') 474 475 def is_empty(self): 476 assert self.members is not None 477 return not self.members and not self.variants 478 479 def c_name(self): 480 assert self.name != 'q_empty' 481 return super().c_name() 482 483 def c_type(self): 484 assert not self.is_implicit() 485 return c_name(self.name) + POINTER_SUFFIX 486 487 def c_unboxed_type(self): 488 return c_name(self.name) 489 490 def json_type(self): 491 return 'object' 492 493 def visit(self, visitor): 494 super().visit(visitor) 495 visitor.visit_object_type( 496 self.name, self.info, self.ifcond, self.features, 497 self.base, self.local_members, self.variants) 498 visitor.visit_object_type_flat( 499 self.name, self.info, self.ifcond, self.features, 500 self.members, self.variants) 501 502 503 class QAPISchemaAlternateType(QAPISchemaType): 504 meta = 'alternate' 505 506 def __init__(self, name, info, doc, ifcond, features, variants): 507 super().__init__(name, info, doc, ifcond, features) 508 assert isinstance(variants, QAPISchemaVariants) 509 assert variants.tag_member 510 variants.set_defined_in(name) 511 variants.tag_member.set_defined_in(self.name) 512 self.variants = variants 513 514 def check(self, schema): 515 super().check(schema) 516 self.variants.tag_member.check(schema) 517 # Not calling self.variants.check_clash(), because there's nothing 518 # to clash with 519 self.variants.check(schema, {}) 520 # Alternate branch names have no relation to the tag enum values; 521 # so we have to check for potential name collisions ourselves. 522 seen = {} 523 types_seen = {} 524 for v in self.variants.variants: 525 v.check_clash(self.info, seen) 526 qtype = v.type.alternate_qtype() 527 if not qtype: 528 raise QAPISemError( 529 self.info, 530 "%s cannot use %s" 531 % (v.describe(self.info), v.type.describe())) 532 conflicting = set([qtype]) 533 if qtype == 'QTYPE_QSTRING': 534 if isinstance(v.type, QAPISchemaEnumType): 535 for m in v.type.members: 536 if m.name in ['on', 'off']: 537 conflicting.add('QTYPE_QBOOL') 538 if re.match(r'[-+0-9.]', m.name): 539 # lazy, could be tightened 540 conflicting.add('QTYPE_QNUM') 541 else: 542 conflicting.add('QTYPE_QNUM') 543 conflicting.add('QTYPE_QBOOL') 544 for qt in conflicting: 545 if qt in types_seen: 546 raise QAPISemError( 547 self.info, 548 "%s can't be distinguished from '%s'" 549 % (v.describe(self.info), types_seen[qt])) 550 types_seen[qt] = v.name 551 552 def connect_doc(self, doc=None): 553 super().connect_doc(doc) 554 doc = doc or self.doc 555 for v in self.variants.variants: 556 v.connect_doc(doc) 557 558 def c_type(self): 559 return c_name(self.name) + POINTER_SUFFIX 560 561 def json_type(self): 562 return 'value' 563 564 def visit(self, visitor): 565 super().visit(visitor) 566 visitor.visit_alternate_type( 567 self.name, self.info, self.ifcond, self.features, self.variants) 568 569 570 class QAPISchemaVariants: 571 def __init__(self, tag_name, info, tag_member, variants): 572 # Unions pass tag_name but not tag_member. 573 # Alternates pass tag_member but not tag_name. 574 # After check(), tag_member is always set. 575 assert bool(tag_member) != bool(tag_name) 576 assert (isinstance(tag_name, str) or 577 isinstance(tag_member, QAPISchemaObjectTypeMember)) 578 for v in variants: 579 assert isinstance(v, QAPISchemaVariant) 580 self._tag_name = tag_name 581 self.info = info 582 self.tag_member = tag_member 583 self.variants = variants 584 585 def set_defined_in(self, name): 586 for v in self.variants: 587 v.set_defined_in(name) 588 589 def check(self, schema, seen): 590 if self._tag_name: # union 591 self.tag_member = seen.get(c_name(self._tag_name)) 592 base = "'base'" 593 # Pointing to the base type when not implicit would be 594 # nice, but we don't know it here 595 if not self.tag_member or self._tag_name != self.tag_member.name: 596 raise QAPISemError( 597 self.info, 598 "discriminator '%s' is not a member of %s" 599 % (self._tag_name, base)) 600 # Here we do: 601 base_type = schema.lookup_type(self.tag_member.defined_in) 602 assert base_type 603 if not base_type.is_implicit(): 604 base = "base type '%s'" % self.tag_member.defined_in 605 if not isinstance(self.tag_member.type, QAPISchemaEnumType): 606 raise QAPISemError( 607 self.info, 608 "discriminator member '%s' of %s must be of enum type" 609 % (self._tag_name, base)) 610 if self.tag_member.optional: 611 raise QAPISemError( 612 self.info, 613 "discriminator member '%s' of %s must not be optional" 614 % (self._tag_name, base)) 615 if self.tag_member.ifcond.is_present(): 616 raise QAPISemError( 617 self.info, 618 "discriminator member '%s' of %s must not be conditional" 619 % (self._tag_name, base)) 620 else: # alternate 621 assert isinstance(self.tag_member.type, QAPISchemaEnumType) 622 assert not self.tag_member.optional 623 assert not self.tag_member.ifcond.is_present() 624 if self._tag_name: # union 625 # branches that are not explicitly covered get an empty type 626 cases = {v.name for v in self.variants} 627 for m in self.tag_member.type.members: 628 if m.name not in cases: 629 v = QAPISchemaVariant(m.name, self.info, 630 'q_empty', m.ifcond) 631 v.set_defined_in(self.tag_member.defined_in) 632 self.variants.append(v) 633 if not self.variants: 634 raise QAPISemError(self.info, "union has no branches") 635 for v in self.variants: 636 v.check(schema) 637 # Union names must match enum values; alternate names are 638 # checked separately. Use 'seen' to tell the two apart. 639 if seen: 640 if v.name not in self.tag_member.type.member_names(): 641 raise QAPISemError( 642 self.info, 643 "branch '%s' is not a value of %s" 644 % (v.name, self.tag_member.type.describe())) 645 if (not isinstance(v.type, QAPISchemaObjectType) 646 or v.type.variants): 647 raise QAPISemError( 648 self.info, 649 "%s cannot use %s" 650 % (v.describe(self.info), v.type.describe())) 651 v.type.check(schema) 652 653 def check_clash(self, info, seen): 654 for v in self.variants: 655 # Reset seen map for each variant, since qapi names from one 656 # branch do not affect another branch 657 v.type.check_clash(info, dict(seen)) 658 659 660 class QAPISchemaMember: 661 """ Represents object members, enum members and features """ 662 role = 'member' 663 664 def __init__(self, name, info, ifcond=None): 665 assert isinstance(name, str) 666 self.name = name 667 self.info = info 668 self.ifcond = ifcond or QAPISchemaIfCond() 669 self.defined_in = None 670 671 def set_defined_in(self, name): 672 assert not self.defined_in 673 self.defined_in = name 674 675 def check_clash(self, info, seen): 676 cname = c_name(self.name) 677 if cname in seen: 678 raise QAPISemError( 679 info, 680 "%s collides with %s" 681 % (self.describe(info), seen[cname].describe(info))) 682 seen[cname] = self 683 684 def connect_doc(self, doc): 685 if doc: 686 doc.connect_member(self) 687 688 def describe(self, info): 689 role = self.role 690 defined_in = self.defined_in 691 assert defined_in 692 693 if defined_in.startswith('q_obj_'): 694 # See QAPISchema._make_implicit_object_type() - reverse the 695 # mapping there to create a nice human-readable description 696 defined_in = defined_in[6:] 697 if defined_in.endswith('-arg'): 698 # Implicit type created for a command's dict 'data' 699 assert role == 'member' 700 role = 'parameter' 701 elif defined_in.endswith('-base'): 702 # Implicit type created for a union's dict 'base' 703 role = 'base ' + role 704 else: 705 assert False 706 elif defined_in != info.defn_name: 707 return "%s '%s' of type '%s'" % (role, self.name, defined_in) 708 return "%s '%s'" % (role, self.name) 709 710 711 class QAPISchemaEnumMember(QAPISchemaMember): 712 role = 'value' 713 714 def __init__(self, name, info, ifcond=None, features=None): 715 super().__init__(name, info, ifcond) 716 for f in features or []: 717 assert isinstance(f, QAPISchemaFeature) 718 f.set_defined_in(name) 719 self.features = features or [] 720 721 def connect_doc(self, doc): 722 super().connect_doc(doc) 723 if doc: 724 for f in self.features: 725 doc.connect_feature(f) 726 727 728 class QAPISchemaFeature(QAPISchemaMember): 729 role = 'feature' 730 731 def is_special(self): 732 return self.name in ('deprecated', 'unstable') 733 734 735 class QAPISchemaObjectTypeMember(QAPISchemaMember): 736 def __init__(self, name, info, typ, optional, ifcond=None, features=None): 737 super().__init__(name, info, ifcond) 738 assert isinstance(typ, str) 739 assert isinstance(optional, bool) 740 for f in features or []: 741 assert isinstance(f, QAPISchemaFeature) 742 f.set_defined_in(name) 743 self._type_name = typ 744 self.type = None 745 self.optional = optional 746 self.features = features or [] 747 748 def check(self, schema): 749 assert self.defined_in 750 self.type = schema.resolve_type(self._type_name, self.info, 751 self.describe) 752 seen = {} 753 for f in self.features: 754 f.check_clash(self.info, seen) 755 756 def connect_doc(self, doc): 757 super().connect_doc(doc) 758 if doc: 759 for f in self.features: 760 doc.connect_feature(f) 761 762 763 class QAPISchemaVariant(QAPISchemaObjectTypeMember): 764 role = 'branch' 765 766 def __init__(self, name, info, typ, ifcond=None): 767 super().__init__(name, info, typ, False, ifcond) 768 769 770 class QAPISchemaCommand(QAPISchemaEntity): 771 meta = 'command' 772 773 def __init__(self, name, info, doc, ifcond, features, 774 arg_type, ret_type, 775 gen, success_response, boxed, allow_oob, allow_preconfig, 776 coroutine): 777 super().__init__(name, info, doc, ifcond, features) 778 assert not arg_type or isinstance(arg_type, str) 779 assert not ret_type or isinstance(ret_type, str) 780 self._arg_type_name = arg_type 781 self.arg_type = None 782 self._ret_type_name = ret_type 783 self.ret_type = None 784 self.gen = gen 785 self.success_response = success_response 786 self.boxed = boxed 787 self.allow_oob = allow_oob 788 self.allow_preconfig = allow_preconfig 789 self.coroutine = coroutine 790 791 def check(self, schema): 792 super().check(schema) 793 if self._arg_type_name: 794 self.arg_type = schema.resolve_type( 795 self._arg_type_name, self.info, "command's 'data'") 796 if not isinstance(self.arg_type, QAPISchemaObjectType): 797 raise QAPISemError( 798 self.info, 799 "command's 'data' cannot take %s" 800 % self.arg_type.describe()) 801 if self.arg_type.variants and not self.boxed: 802 raise QAPISemError( 803 self.info, 804 "command's 'data' can take %s only with 'boxed': true" 805 % self.arg_type.describe()) 806 if self._ret_type_name: 807 self.ret_type = schema.resolve_type( 808 self._ret_type_name, self.info, "command's 'returns'") 809 if self.name not in self.info.pragma.command_returns_exceptions: 810 typ = self.ret_type 811 if isinstance(typ, QAPISchemaArrayType): 812 typ = self.ret_type.element_type 813 assert typ 814 if not isinstance(typ, QAPISchemaObjectType): 815 raise QAPISemError( 816 self.info, 817 "command's 'returns' cannot take %s" 818 % self.ret_type.describe()) 819 820 def connect_doc(self, doc=None): 821 super().connect_doc(doc) 822 doc = doc or self.doc 823 if doc: 824 if self.arg_type and self.arg_type.is_implicit(): 825 self.arg_type.connect_doc(doc) 826 827 def visit(self, visitor): 828 super().visit(visitor) 829 visitor.visit_command( 830 self.name, self.info, self.ifcond, self.features, 831 self.arg_type, self.ret_type, self.gen, self.success_response, 832 self.boxed, self.allow_oob, self.allow_preconfig, 833 self.coroutine) 834 835 836 class QAPISchemaEvent(QAPISchemaEntity): 837 meta = 'event' 838 839 def __init__(self, name, info, doc, ifcond, features, arg_type, boxed): 840 super().__init__(name, info, doc, ifcond, features) 841 assert not arg_type or isinstance(arg_type, str) 842 self._arg_type_name = arg_type 843 self.arg_type = None 844 self.boxed = boxed 845 846 def check(self, schema): 847 super().check(schema) 848 if self._arg_type_name: 849 self.arg_type = schema.resolve_type( 850 self._arg_type_name, self.info, "event's 'data'") 851 if not isinstance(self.arg_type, QAPISchemaObjectType): 852 raise QAPISemError( 853 self.info, 854 "event's 'data' cannot take %s" 855 % self.arg_type.describe()) 856 if self.arg_type.variants and not self.boxed: 857 raise QAPISemError( 858 self.info, 859 "event's 'data' can take %s only with 'boxed': true" 860 % self.arg_type.describe()) 861 862 def connect_doc(self, doc=None): 863 super().connect_doc(doc) 864 doc = doc or self.doc 865 if doc: 866 if self.arg_type and self.arg_type.is_implicit(): 867 self.arg_type.connect_doc(doc) 868 869 def visit(self, visitor): 870 super().visit(visitor) 871 visitor.visit_event( 872 self.name, self.info, self.ifcond, self.features, 873 self.arg_type, self.boxed) 874 875 876 class QAPISchema: 877 def __init__(self, fname): 878 self.fname = fname 879 880 try: 881 parser = QAPISchemaParser(fname) 882 except OSError as err: 883 raise QAPIError( 884 f"can't read schema file '{fname}': {err.strerror}" 885 ) from err 886 887 exprs = check_exprs(parser.exprs) 888 self.docs = parser.docs 889 self._entity_list = [] 890 self._entity_dict = {} 891 self._module_dict = OrderedDict() 892 self._schema_dir = os.path.dirname(fname) 893 self._make_module(QAPISchemaModule.BUILTIN_MODULE_NAME) 894 self._make_module(fname) 895 self._predefining = True 896 self._def_predefineds() 897 self._predefining = False 898 self._def_exprs(exprs) 899 self.check() 900 901 def _def_entity(self, ent): 902 # Only the predefined types are allowed to not have info 903 assert ent.info or self._predefining 904 self._entity_list.append(ent) 905 if ent.name is None: 906 return 907 # TODO reject names that differ only in '_' vs. '.' vs. '-', 908 # because they're liable to clash in generated C. 909 other_ent = self._entity_dict.get(ent.name) 910 if other_ent: 911 if other_ent.info: 912 where = QAPISourceError(other_ent.info, "previous definition") 913 raise QAPISemError( 914 ent.info, 915 "'%s' is already defined\n%s" % (ent.name, where)) 916 raise QAPISemError( 917 ent.info, "%s is already defined" % other_ent.describe()) 918 self._entity_dict[ent.name] = ent 919 920 def lookup_entity(self, name, typ=None): 921 ent = self._entity_dict.get(name) 922 if typ and not isinstance(ent, typ): 923 return None 924 return ent 925 926 def lookup_type(self, name): 927 return self.lookup_entity(name, QAPISchemaType) 928 929 def resolve_type(self, name, info, what): 930 typ = self.lookup_type(name) 931 if not typ: 932 if callable(what): 933 what = what(info) 934 raise QAPISemError( 935 info, "%s uses unknown type '%s'" % (what, name)) 936 return typ 937 938 def _module_name(self, fname: str) -> str: 939 if QAPISchemaModule.is_system_module(fname): 940 return fname 941 return os.path.relpath(fname, self._schema_dir) 942 943 def _make_module(self, fname): 944 name = self._module_name(fname) 945 if name not in self._module_dict: 946 self._module_dict[name] = QAPISchemaModule(name) 947 return self._module_dict[name] 948 949 def module_by_fname(self, fname): 950 name = self._module_name(fname) 951 return self._module_dict[name] 952 953 def _def_include(self, expr, info, doc): 954 include = expr['include'] 955 assert doc is None 956 self._def_entity(QAPISchemaInclude(self._make_module(include), info)) 957 958 def _def_builtin_type(self, name, json_type, c_type): 959 self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type)) 960 # Instantiating only the arrays that are actually used would 961 # be nice, but we can't as long as their generated code 962 # (qapi-builtin-types.[ch]) may be shared by some other 963 # schema. 964 self._make_array_type(name, None) 965 966 def _def_predefineds(self): 967 for t in [('str', 'string', 'char' + POINTER_SUFFIX), 968 ('number', 'number', 'double'), 969 ('int', 'int', 'int64_t'), 970 ('int8', 'int', 'int8_t'), 971 ('int16', 'int', 'int16_t'), 972 ('int32', 'int', 'int32_t'), 973 ('int64', 'int', 'int64_t'), 974 ('uint8', 'int', 'uint8_t'), 975 ('uint16', 'int', 'uint16_t'), 976 ('uint32', 'int', 'uint32_t'), 977 ('uint64', 'int', 'uint64_t'), 978 ('size', 'int', 'uint64_t'), 979 ('bool', 'boolean', 'bool'), 980 ('any', 'value', 'QObject' + POINTER_SUFFIX), 981 ('null', 'null', 'QNull' + POINTER_SUFFIX)]: 982 self._def_builtin_type(*t) 983 self.the_empty_object_type = QAPISchemaObjectType( 984 'q_empty', None, None, None, None, None, [], None) 985 self._def_entity(self.the_empty_object_type) 986 987 qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 988 'qbool'] 989 qtype_values = self._make_enum_members( 990 [{'name': n} for n in qtypes], None) 991 992 self._def_entity(QAPISchemaEnumType('QType', None, None, None, None, 993 qtype_values, 'QTYPE')) 994 995 def _make_features(self, features, info): 996 if features is None: 997 return [] 998 return [QAPISchemaFeature(f['name'], info, 999 QAPISchemaIfCond(f.get('if'))) 1000 for f in features] 1001 1002 def _make_enum_member(self, name, ifcond, features, info): 1003 return QAPISchemaEnumMember(name, info, 1004 QAPISchemaIfCond(ifcond), 1005 self._make_features(features, info)) 1006 1007 def _make_enum_members(self, values, info): 1008 return [self._make_enum_member(v['name'], v.get('if'), 1009 v.get('features'), info) 1010 for v in values] 1011 1012 def _make_array_type(self, element_type, info): 1013 name = element_type + 'List' # reserved by check_defn_name_str() 1014 if not self.lookup_type(name): 1015 self._def_entity(QAPISchemaArrayType(name, info, element_type)) 1016 return name 1017 1018 def _make_implicit_object_type(self, name, info, ifcond, role, members): 1019 if not members: 1020 return None 1021 # See also QAPISchemaObjectTypeMember.describe() 1022 name = 'q_obj_%s-%s' % (name, role) 1023 typ = self.lookup_entity(name, QAPISchemaObjectType) 1024 if typ: 1025 # The implicit object type has multiple users. This can 1026 # only be a duplicate definition, which will be flagged 1027 # later. 1028 pass 1029 else: 1030 self._def_entity(QAPISchemaObjectType( 1031 name, info, None, ifcond, None, None, members, None)) 1032 return name 1033 1034 def _def_enum_type(self, expr, info, doc): 1035 name = expr['enum'] 1036 data = expr['data'] 1037 prefix = expr.get('prefix') 1038 ifcond = QAPISchemaIfCond(expr.get('if')) 1039 features = self._make_features(expr.get('features'), info) 1040 self._def_entity(QAPISchemaEnumType( 1041 name, info, doc, ifcond, features, 1042 self._make_enum_members(data, info), prefix)) 1043 1044 def _make_member(self, name, typ, ifcond, features, info): 1045 optional = False 1046 if name.startswith('*'): 1047 name = name[1:] 1048 optional = True 1049 if isinstance(typ, list): 1050 assert len(typ) == 1 1051 typ = self._make_array_type(typ[0], info) 1052 return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond, 1053 self._make_features(features, info)) 1054 1055 def _make_members(self, data, info): 1056 return [self._make_member(key, value['type'], 1057 QAPISchemaIfCond(value.get('if')), 1058 value.get('features'), info) 1059 for (key, value) in data.items()] 1060 1061 def _def_struct_type(self, expr, info, doc): 1062 name = expr['struct'] 1063 base = expr.get('base') 1064 data = expr['data'] 1065 ifcond = QAPISchemaIfCond(expr.get('if')) 1066 features = self._make_features(expr.get('features'), info) 1067 self._def_entity(QAPISchemaObjectType( 1068 name, info, doc, ifcond, features, base, 1069 self._make_members(data, info), 1070 None)) 1071 1072 def _make_variant(self, case, typ, ifcond, info): 1073 if isinstance(typ, list): 1074 assert len(typ) == 1 1075 typ = self._make_array_type(typ[0], info) 1076 return QAPISchemaVariant(case, info, typ, ifcond) 1077 1078 def _def_union_type(self, expr, info, doc): 1079 name = expr['union'] 1080 base = expr['base'] 1081 tag_name = expr['discriminator'] 1082 data = expr['data'] 1083 ifcond = QAPISchemaIfCond(expr.get('if')) 1084 features = self._make_features(expr.get('features'), info) 1085 if isinstance(base, dict): 1086 base = self._make_implicit_object_type( 1087 name, info, ifcond, 1088 'base', self._make_members(base, info)) 1089 variants = [ 1090 self._make_variant(key, value['type'], 1091 QAPISchemaIfCond(value.get('if')), 1092 info) 1093 for (key, value) in data.items()] 1094 members = [] 1095 self._def_entity( 1096 QAPISchemaObjectType(name, info, doc, ifcond, features, 1097 base, members, 1098 QAPISchemaVariants( 1099 tag_name, info, None, variants))) 1100 1101 def _def_alternate_type(self, expr, info, doc): 1102 name = expr['alternate'] 1103 data = expr['data'] 1104 ifcond = QAPISchemaIfCond(expr.get('if')) 1105 features = self._make_features(expr.get('features'), info) 1106 variants = [ 1107 self._make_variant(key, value['type'], 1108 QAPISchemaIfCond(value.get('if')), 1109 info) 1110 for (key, value) in data.items()] 1111 tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False) 1112 self._def_entity( 1113 QAPISchemaAlternateType(name, info, doc, ifcond, features, 1114 QAPISchemaVariants( 1115 None, info, tag_member, variants))) 1116 1117 def _def_command(self, expr, info, doc): 1118 name = expr['command'] 1119 data = expr.get('data') 1120 rets = expr.get('returns') 1121 gen = expr.get('gen', True) 1122 success_response = expr.get('success-response', True) 1123 boxed = expr.get('boxed', False) 1124 allow_oob = expr.get('allow-oob', False) 1125 allow_preconfig = expr.get('allow-preconfig', False) 1126 coroutine = expr.get('coroutine', False) 1127 ifcond = QAPISchemaIfCond(expr.get('if')) 1128 features = self._make_features(expr.get('features'), info) 1129 if isinstance(data, OrderedDict): 1130 data = self._make_implicit_object_type( 1131 name, info, ifcond, 1132 'arg', self._make_members(data, info)) 1133 if isinstance(rets, list): 1134 assert len(rets) == 1 1135 rets = self._make_array_type(rets[0], info) 1136 self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, features, 1137 data, rets, 1138 gen, success_response, 1139 boxed, allow_oob, allow_preconfig, 1140 coroutine)) 1141 1142 def _def_event(self, expr, info, doc): 1143 name = expr['event'] 1144 data = expr.get('data') 1145 boxed = expr.get('boxed', False) 1146 ifcond = QAPISchemaIfCond(expr.get('if')) 1147 features = self._make_features(expr.get('features'), info) 1148 if isinstance(data, OrderedDict): 1149 data = self._make_implicit_object_type( 1150 name, info, ifcond, 1151 'arg', self._make_members(data, info)) 1152 self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, features, 1153 data, boxed)) 1154 1155 def _def_exprs(self, exprs): 1156 for expr_elem in exprs: 1157 expr = expr_elem['expr'] 1158 info = expr_elem['info'] 1159 doc = expr_elem.get('doc') 1160 if 'enum' in expr: 1161 self._def_enum_type(expr, info, doc) 1162 elif 'struct' in expr: 1163 self._def_struct_type(expr, info, doc) 1164 elif 'union' in expr: 1165 self._def_union_type(expr, info, doc) 1166 elif 'alternate' in expr: 1167 self._def_alternate_type(expr, info, doc) 1168 elif 'command' in expr: 1169 self._def_command(expr, info, doc) 1170 elif 'event' in expr: 1171 self._def_event(expr, info, doc) 1172 elif 'include' in expr: 1173 self._def_include(expr, info, doc) 1174 else: 1175 assert False 1176 1177 def check(self): 1178 for ent in self._entity_list: 1179 ent.check(self) 1180 ent.connect_doc() 1181 ent.check_doc() 1182 for ent in self._entity_list: 1183 ent.set_module(self) 1184 1185 def visit(self, visitor): 1186 visitor.visit_begin(self) 1187 for mod in self._module_dict.values(): 1188 mod.visit(visitor) 1189 visitor.visit_end()