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