qapi: Drop simple unions
[qemu/armbru.git] / scripts / qapi / schema.py
blob004d7095ff16986b8a80538929d92e52a1dde9a6
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 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 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 c_name(self):
77 return c_name(self.name)
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
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)
92 def check_doc(self):
93 if self.doc:
94 self.doc.check()
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)
102 def set_module(self, schema):
103 self._set_module(schema, self.info)
105 @property
106 def ifcond(self):
107 assert self._checked
108 return self._ifcond
110 def is_implicit(self):
111 return not self.info
113 def visit(self, visitor):
114 assert self._checked
116 def describe(self):
117 assert self.meta
118 return "%s '%s'" % (self.meta, self.name)
121 class QAPISchemaVisitor:
122 def visit_begin(self, schema):
123 pass
125 def visit_end(self):
126 pass
128 def visit_module(self, name):
129 pass
131 def visit_needed(self, entity):
132 # Default to visiting everything
133 return True
135 def visit_include(self, name, info):
136 pass
138 def visit_builtin_type(self, name, info, json_type):
139 pass
141 def visit_enum_type(self, name, info, ifcond, features, members, prefix):
142 pass
144 def visit_array_type(self, name, info, ifcond, element_type):
145 pass
147 def visit_object_type(self, name, info, ifcond, features,
148 base, members, variants):
149 pass
151 def visit_object_type_flat(self, name, info, ifcond, features,
152 members, variants):
153 pass
155 def visit_alternate_type(self, name, info, ifcond, features, variants):
156 pass
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
163 def visit_event(self, name, info, ifcond, features, arg_type, boxed):
164 pass
167 class QAPISchemaModule:
169 BUILTIN_MODULE_NAME = './builtin'
171 def __init__(self, name):
172 self.name = name
173 self._entity_list = []
175 @staticmethod
176 def is_system_module(name: str) -> bool:
178 System modules are internally defined modules.
180 Their names start with the "./" prefix.
182 return name.startswith('./')
184 @classmethod
185 def is_user_module(cls, name: str) -> bool:
187 User modules are those defined by the user in qapi JSON files.
189 They do not start with the "./" prefix.
191 return not cls.is_system_module(name)
193 @classmethod
194 def is_builtin_module(cls, name: str) -> bool:
196 The built-in module is a single System module for the built-in types.
198 It is always "./builtin".
200 return name == cls.BUILTIN_MODULE_NAME
202 def add_entity(self, ent):
203 self._entity_list.append(ent)
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)
212 class QAPISchemaInclude(QAPISchemaEntity):
213 def __init__(self, sub_module, info):
214 super().__init__(None, info, None)
215 self._sub_module = sub_module
217 def visit(self, visitor):
218 super().visit(visitor)
219 visitor.visit_include(self._sub_module.name, self.info)
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
228 # Return the C type to be used in a parameter list.
229 def c_param_type(self):
230 return self.c_type()
232 # Return the C type to be used where we suppress boxing.
233 def c_unboxed_type(self):
234 return self.c_type()
236 def json_type(self):
237 pass
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 'object': 'QTYPE_QDICT'
248 return json2qtype.get(self.json_type())
250 def doc_type(self):
251 if self.is_implicit():
252 return None
253 return self.name
255 def check(self, schema):
256 QAPISchemaEntity.check(self, schema)
257 if 'deprecated' in [f.name for f in self.features]:
258 raise QAPISemError(
259 self.info, "feature 'deprecated' is not supported for types")
261 def describe(self):
262 assert self.meta
263 return "%s type '%s'" % (self.meta, self.name)
266 class QAPISchemaBuiltinType(QAPISchemaType):
267 meta = 'built-in'
269 def __init__(self, name, json_type, c_type):
270 super().__init__(name, None, None)
271 assert not c_type or isinstance(c_type, str)
272 assert json_type in ('string', 'number', 'int', 'boolean', 'null',
273 'value')
274 self._json_type_name = json_type
275 self._c_type_name = c_type
277 def c_name(self):
278 return self.name
280 def c_type(self):
281 return self._c_type_name
283 def c_param_type(self):
284 if self.name == 'str':
285 return 'const ' + self._c_type_name
286 return self._c_type_name
288 def json_type(self):
289 return self._json_type_name
291 def doc_type(self):
292 return self.json_type()
294 def visit(self, visitor):
295 super().visit(visitor)
296 visitor.visit_builtin_type(self.name, self.info, self.json_type())
299 class QAPISchemaEnumType(QAPISchemaType):
300 meta = 'enum'
302 def __init__(self, name, info, doc, ifcond, features, members, prefix):
303 super().__init__(name, info, doc, ifcond, features)
304 for m in members:
305 assert isinstance(m, QAPISchemaEnumMember)
306 m.set_defined_in(name)
307 assert prefix is None or isinstance(prefix, str)
308 self.members = members
309 self.prefix = prefix
311 def check(self, schema):
312 super().check(schema)
313 seen = {}
314 for m in self.members:
315 m.check_clash(self.info, seen)
317 def connect_doc(self, doc=None):
318 super().connect_doc(doc)
319 doc = doc or self.doc
320 for m in self.members:
321 m.connect_doc(doc)
323 def is_implicit(self):
324 # See QAPISchema._def_predefineds()
325 return self.name == 'QType'
327 def c_type(self):
328 return c_name(self.name)
330 def member_names(self):
331 return [m.name for m in self.members]
333 def json_type(self):
334 return 'string'
336 def visit(self, visitor):
337 super().visit(visitor)
338 visitor.visit_enum_type(
339 self.name, self.info, self.ifcond, self.features,
340 self.members, self.prefix)
343 class QAPISchemaArrayType(QAPISchemaType):
344 meta = 'array'
346 def __init__(self, name, info, element_type):
347 super().__init__(name, info, None)
348 assert isinstance(element_type, str)
349 self._element_type_name = element_type
350 self.element_type = None
352 def check(self, schema):
353 super().check(schema)
354 self.element_type = schema.resolve_type(
355 self._element_type_name, self.info,
356 self.info and self.info.defn_meta)
357 assert not isinstance(self.element_type, QAPISchemaArrayType)
359 def set_module(self, schema):
360 self._set_module(schema, self.element_type.info)
362 @property
363 def ifcond(self):
364 assert self._checked
365 return self.element_type.ifcond
367 def is_implicit(self):
368 return True
370 def c_type(self):
371 return c_name(self.name) + POINTER_SUFFIX
373 def json_type(self):
374 return 'array'
376 def doc_type(self):
377 elt_doc_type = self.element_type.doc_type()
378 if not elt_doc_type:
379 return None
380 return 'array of ' + elt_doc_type
382 def visit(self, visitor):
383 super().visit(visitor)
384 visitor.visit_array_type(self.name, self.info, self.ifcond,
385 self.element_type)
387 def describe(self):
388 assert self.meta
389 return "%s type ['%s']" % (self.meta, self._element_type_name)
392 class QAPISchemaObjectType(QAPISchemaType):
393 def __init__(self, name, info, doc, ifcond, features,
394 base, local_members, variants):
395 # struct has local_members, optional base, and no variants
396 # union has base, variants, and no local_members
397 super().__init__(name, info, doc, ifcond, features)
398 self.meta = 'union' if variants else 'struct'
399 assert base is None or isinstance(base, str)
400 for m in local_members:
401 assert isinstance(m, QAPISchemaObjectTypeMember)
402 m.set_defined_in(name)
403 if variants is not None:
404 assert isinstance(variants, QAPISchemaVariants)
405 variants.set_defined_in(name)
406 self._base_name = base
407 self.base = None
408 self.local_members = local_members
409 self.variants = variants
410 self.members = None
412 def check(self, schema):
413 # This calls another type T's .check() exactly when the C
414 # struct emitted by gen_object() contains that T's C struct
415 # (pointers don't count).
416 if self.members is not None:
417 # A previous .check() completed: nothing to do
418 return
419 if self._checked:
420 # Recursed: C struct contains itself
421 raise QAPISemError(self.info,
422 "object %s contains itself" % self.name)
424 super().check(schema)
425 assert self._checked and self.members is None
427 seen = OrderedDict()
428 if self._base_name:
429 self.base = schema.resolve_type(self._base_name, self.info,
430 "'base'")
431 if (not isinstance(self.base, QAPISchemaObjectType)
432 or self.base.variants):
433 raise QAPISemError(
434 self.info,
435 "'base' requires a struct type, %s isn't"
436 % self.base.describe())
437 self.base.check(schema)
438 self.base.check_clash(self.info, seen)
439 for m in self.local_members:
440 m.check(schema)
441 m.check_clash(self.info, seen)
442 members = seen.values()
444 if self.variants:
445 self.variants.check(schema, seen)
446 self.variants.check_clash(self.info, seen)
448 self.members = members # mark completed
450 # Check that the members of this type do not cause duplicate JSON members,
451 # and update seen to track the members seen so far. Report any errors
452 # on behalf of info, which is not necessarily self.info
453 def check_clash(self, info, seen):
454 assert self._checked
455 assert not self.variants # not implemented
456 for m in self.members:
457 m.check_clash(info, seen)
459 def connect_doc(self, doc=None):
460 super().connect_doc(doc)
461 doc = doc or self.doc
462 if self.base and self.base.is_implicit():
463 self.base.connect_doc(doc)
464 for m in self.local_members:
465 m.connect_doc(doc)
467 def is_implicit(self):
468 # See QAPISchema._make_implicit_object_type(), as well as
469 # _def_predefineds()
470 return self.name.startswith('q_')
472 def is_empty(self):
473 assert self.members is not None
474 return not self.members and not self.variants
476 def c_name(self):
477 assert self.name != 'q_empty'
478 return super().c_name()
480 def c_type(self):
481 assert not self.is_implicit()
482 return c_name(self.name) + POINTER_SUFFIX
484 def c_unboxed_type(self):
485 return c_name(self.name)
487 def json_type(self):
488 return 'object'
490 def visit(self, visitor):
491 super().visit(visitor)
492 visitor.visit_object_type(
493 self.name, self.info, self.ifcond, self.features,
494 self.base, self.local_members, self.variants)
495 visitor.visit_object_type_flat(
496 self.name, self.info, self.ifcond, self.features,
497 self.members, self.variants)
500 class QAPISchemaAlternateType(QAPISchemaType):
501 meta = 'alternate'
503 def __init__(self, name, info, doc, ifcond, features, variants):
504 super().__init__(name, info, doc, ifcond, features)
505 assert isinstance(variants, QAPISchemaVariants)
506 assert variants.tag_member
507 variants.set_defined_in(name)
508 variants.tag_member.set_defined_in(self.name)
509 self.variants = variants
511 def check(self, schema):
512 super().check(schema)
513 self.variants.tag_member.check(schema)
514 # Not calling self.variants.check_clash(), because there's nothing
515 # to clash with
516 self.variants.check(schema, {})
517 # Alternate branch names have no relation to the tag enum values;
518 # so we have to check for potential name collisions ourselves.
519 seen = {}
520 types_seen = {}
521 for v in self.variants.variants:
522 v.check_clash(self.info, seen)
523 qtype = v.type.alternate_qtype()
524 if not qtype:
525 raise QAPISemError(
526 self.info,
527 "%s cannot use %s"
528 % (v.describe(self.info), v.type.describe()))
529 conflicting = set([qtype])
530 if qtype == 'QTYPE_QSTRING':
531 if isinstance(v.type, QAPISchemaEnumType):
532 for m in v.type.members:
533 if m.name in ['on', 'off']:
534 conflicting.add('QTYPE_QBOOL')
535 if re.match(r'[-+0-9.]', m.name):
536 # lazy, could be tightened
537 conflicting.add('QTYPE_QNUM')
538 else:
539 conflicting.add('QTYPE_QNUM')
540 conflicting.add('QTYPE_QBOOL')
541 for qt in conflicting:
542 if qt in types_seen:
543 raise QAPISemError(
544 self.info,
545 "%s can't be distinguished from '%s'"
546 % (v.describe(self.info), types_seen[qt]))
547 types_seen[qt] = v.name
549 def connect_doc(self, doc=None):
550 super().connect_doc(doc)
551 doc = doc or self.doc
552 for v in self.variants.variants:
553 v.connect_doc(doc)
555 def c_type(self):
556 return c_name(self.name) + POINTER_SUFFIX
558 def json_type(self):
559 return 'value'
561 def visit(self, visitor):
562 super().visit(visitor)
563 visitor.visit_alternate_type(
564 self.name, self.info, self.ifcond, self.features, self.variants)
567 class QAPISchemaVariants:
568 def __init__(self, tag_name, info, tag_member, variants):
569 # Unions pass tag_name but not tag_member.
570 # Alternates pass tag_member but not tag_name.
571 # After check(), tag_member is always set.
572 assert bool(tag_member) != bool(tag_name)
573 assert (isinstance(tag_name, str) or
574 isinstance(tag_member, QAPISchemaObjectTypeMember))
575 for v in variants:
576 assert isinstance(v, QAPISchemaVariant)
577 self._tag_name = tag_name
578 self.info = info
579 self.tag_member = tag_member
580 self.variants = variants
582 def set_defined_in(self, name):
583 for v in self.variants:
584 v.set_defined_in(name)
586 def check(self, schema, seen):
587 if self._tag_name: # union
588 self.tag_member = seen.get(c_name(self._tag_name))
589 base = "'base'"
590 # Pointing to the base type when not implicit would be
591 # nice, but we don't know it here
592 if not self.tag_member or self._tag_name != self.tag_member.name:
593 raise QAPISemError(
594 self.info,
595 "discriminator '%s' is not a member of %s"
596 % (self._tag_name, base))
597 # Here we do:
598 base_type = schema.lookup_type(self.tag_member.defined_in)
599 assert base_type
600 if not base_type.is_implicit():
601 base = "base type '%s'" % self.tag_member.defined_in
602 if not isinstance(self.tag_member.type, QAPISchemaEnumType):
603 raise QAPISemError(
604 self.info,
605 "discriminator member '%s' of %s must be of enum type"
606 % (self._tag_name, base))
607 if self.tag_member.optional:
608 raise QAPISemError(
609 self.info,
610 "discriminator member '%s' of %s must not be optional"
611 % (self._tag_name, base))
612 if self.tag_member.ifcond.is_present():
613 raise QAPISemError(
614 self.info,
615 "discriminator member '%s' of %s must not be conditional"
616 % (self._tag_name, base))
617 else: # alternate
618 assert isinstance(self.tag_member.type, QAPISchemaEnumType)
619 assert not self.tag_member.optional
620 assert not self.tag_member.ifcond.is_present()
621 if self._tag_name: # union
622 # branches that are not explicitly covered get an empty type
623 cases = {v.name for v in self.variants}
624 for m in self.tag_member.type.members:
625 if m.name not in cases:
626 v = QAPISchemaVariant(m.name, self.info,
627 'q_empty', m.ifcond)
628 v.set_defined_in(self.tag_member.defined_in)
629 self.variants.append(v)
630 if not self.variants:
631 raise QAPISemError(self.info, "union has no branches")
632 for v in self.variants:
633 v.check(schema)
634 # Union names must match enum values; alternate names are
635 # checked separately. Use 'seen' to tell the two apart.
636 if seen:
637 if v.name not in self.tag_member.type.member_names():
638 raise QAPISemError(
639 self.info,
640 "branch '%s' is not a value of %s"
641 % (v.name, self.tag_member.type.describe()))
642 if (not isinstance(v.type, QAPISchemaObjectType)
643 or v.type.variants):
644 raise QAPISemError(
645 self.info,
646 "%s cannot use %s"
647 % (v.describe(self.info), v.type.describe()))
648 v.type.check(schema)
650 def check_clash(self, info, seen):
651 for v in self.variants:
652 # Reset seen map for each variant, since qapi names from one
653 # branch do not affect another branch
654 v.type.check_clash(info, dict(seen))
657 class QAPISchemaMember:
658 """ Represents object members, enum members and features """
659 role = 'member'
661 def __init__(self, name, info, ifcond=None):
662 assert isinstance(name, str)
663 self.name = name
664 self.info = info
665 self.ifcond = ifcond or QAPISchemaIfCond()
666 self.defined_in = None
668 def set_defined_in(self, name):
669 assert not self.defined_in
670 self.defined_in = name
672 def check_clash(self, info, seen):
673 cname = c_name(self.name)
674 if cname in seen:
675 raise QAPISemError(
676 info,
677 "%s collides with %s"
678 % (self.describe(info), seen[cname].describe(info)))
679 seen[cname] = self
681 def connect_doc(self, doc):
682 if doc:
683 doc.connect_member(self)
685 def describe(self, info):
686 role = self.role
687 defined_in = self.defined_in
688 assert defined_in
690 if defined_in.startswith('q_obj_'):
691 # See QAPISchema._make_implicit_object_type() - reverse the
692 # mapping there to create a nice human-readable description
693 defined_in = defined_in[6:]
694 if defined_in.endswith('-arg'):
695 # Implicit type created for a command's dict 'data'
696 assert role == 'member'
697 role = 'parameter'
698 elif defined_in.endswith('-base'):
699 # Implicit type created for a union's dict 'base'
700 role = 'base ' + role
701 else:
702 assert False
703 elif defined_in != info.defn_name:
704 return "%s '%s' of type '%s'" % (role, self.name, defined_in)
705 return "%s '%s'" % (role, self.name)
708 class QAPISchemaEnumMember(QAPISchemaMember):
709 role = 'value'
712 class QAPISchemaFeature(QAPISchemaMember):
713 role = 'feature'
716 class QAPISchemaObjectTypeMember(QAPISchemaMember):
717 def __init__(self, name, info, typ, optional, ifcond=None, features=None):
718 super().__init__(name, info, ifcond)
719 assert isinstance(typ, str)
720 assert isinstance(optional, bool)
721 for f in features or []:
722 assert isinstance(f, QAPISchemaFeature)
723 f.set_defined_in(name)
724 self._type_name = typ
725 self.type = None
726 self.optional = optional
727 self.features = features or []
729 def check(self, schema):
730 assert self.defined_in
731 self.type = schema.resolve_type(self._type_name, self.info,
732 self.describe)
733 seen = {}
734 for f in self.features:
735 f.check_clash(self.info, seen)
737 def connect_doc(self, doc):
738 super().connect_doc(doc)
739 if doc:
740 for f in self.features:
741 doc.connect_feature(f)
744 class QAPISchemaVariant(QAPISchemaObjectTypeMember):
745 role = 'branch'
747 def __init__(self, name, info, typ, ifcond=None):
748 super().__init__(name, info, typ, False, ifcond)
751 class QAPISchemaCommand(QAPISchemaEntity):
752 meta = 'command'
754 def __init__(self, name, info, doc, ifcond, features,
755 arg_type, ret_type,
756 gen, success_response, boxed, allow_oob, allow_preconfig,
757 coroutine):
758 super().__init__(name, info, doc, ifcond, features)
759 assert not arg_type or isinstance(arg_type, str)
760 assert not ret_type or isinstance(ret_type, str)
761 self._arg_type_name = arg_type
762 self.arg_type = None
763 self._ret_type_name = ret_type
764 self.ret_type = None
765 self.gen = gen
766 self.success_response = success_response
767 self.boxed = boxed
768 self.allow_oob = allow_oob
769 self.allow_preconfig = allow_preconfig
770 self.coroutine = coroutine
772 def check(self, schema):
773 super().check(schema)
774 if self._arg_type_name:
775 self.arg_type = schema.resolve_type(
776 self._arg_type_name, self.info, "command's 'data'")
777 if not isinstance(self.arg_type, QAPISchemaObjectType):
778 raise QAPISemError(
779 self.info,
780 "command's 'data' cannot take %s"
781 % self.arg_type.describe())
782 if self.arg_type.variants and not self.boxed:
783 raise QAPISemError(
784 self.info,
785 "command's 'data' can take %s only with 'boxed': true"
786 % self.arg_type.describe())
787 if self._ret_type_name:
788 self.ret_type = schema.resolve_type(
789 self._ret_type_name, self.info, "command's 'returns'")
790 if self.name not in self.info.pragma.command_returns_exceptions:
791 typ = self.ret_type
792 if isinstance(typ, QAPISchemaArrayType):
793 typ = self.ret_type.element_type
794 assert typ
795 if not isinstance(typ, QAPISchemaObjectType):
796 raise QAPISemError(
797 self.info,
798 "command's 'returns' cannot take %s"
799 % self.ret_type.describe())
801 def connect_doc(self, doc=None):
802 super().connect_doc(doc)
803 doc = doc or self.doc
804 if doc:
805 if self.arg_type and self.arg_type.is_implicit():
806 self.arg_type.connect_doc(doc)
808 def visit(self, visitor):
809 super().visit(visitor)
810 visitor.visit_command(
811 self.name, self.info, self.ifcond, self.features,
812 self.arg_type, self.ret_type, self.gen, self.success_response,
813 self.boxed, self.allow_oob, self.allow_preconfig,
814 self.coroutine)
817 class QAPISchemaEvent(QAPISchemaEntity):
818 meta = 'event'
820 def __init__(self, name, info, doc, ifcond, features, arg_type, boxed):
821 super().__init__(name, info, doc, ifcond, features)
822 assert not arg_type or isinstance(arg_type, str)
823 self._arg_type_name = arg_type
824 self.arg_type = None
825 self.boxed = boxed
827 def check(self, schema):
828 super().check(schema)
829 if self._arg_type_name:
830 self.arg_type = schema.resolve_type(
831 self._arg_type_name, self.info, "event's 'data'")
832 if not isinstance(self.arg_type, QAPISchemaObjectType):
833 raise QAPISemError(
834 self.info,
835 "event's 'data' cannot take %s"
836 % self.arg_type.describe())
837 if self.arg_type.variants and not self.boxed:
838 raise QAPISemError(
839 self.info,
840 "event's 'data' can take %s only with 'boxed': true"
841 % self.arg_type.describe())
843 def connect_doc(self, doc=None):
844 super().connect_doc(doc)
845 doc = doc or self.doc
846 if doc:
847 if self.arg_type and self.arg_type.is_implicit():
848 self.arg_type.connect_doc(doc)
850 def visit(self, visitor):
851 super().visit(visitor)
852 visitor.visit_event(
853 self.name, self.info, self.ifcond, self.features,
854 self.arg_type, self.boxed)
857 class QAPISchema:
858 def __init__(self, fname):
859 self.fname = fname
861 try:
862 parser = QAPISchemaParser(fname)
863 except OSError as err:
864 raise QAPIError(
865 f"can't read schema file '{fname}': {err.strerror}"
866 ) from err
868 exprs = check_exprs(parser.exprs)
869 self.docs = parser.docs
870 self._entity_list = []
871 self._entity_dict = {}
872 self._module_dict = OrderedDict()
873 self._schema_dir = os.path.dirname(fname)
874 self._make_module(QAPISchemaModule.BUILTIN_MODULE_NAME)
875 self._make_module(fname)
876 self._predefining = True
877 self._def_predefineds()
878 self._predefining = False
879 self._def_exprs(exprs)
880 self.check()
882 def _def_entity(self, ent):
883 # Only the predefined types are allowed to not have info
884 assert ent.info or self._predefining
885 self._entity_list.append(ent)
886 if ent.name is None:
887 return
888 # TODO reject names that differ only in '_' vs. '.' vs. '-',
889 # because they're liable to clash in generated C.
890 other_ent = self._entity_dict.get(ent.name)
891 if other_ent:
892 if other_ent.info:
893 where = QAPISourceError(other_ent.info, "previous definition")
894 raise QAPISemError(
895 ent.info,
896 "'%s' is already defined\n%s" % (ent.name, where))
897 raise QAPISemError(
898 ent.info, "%s is already defined" % other_ent.describe())
899 self._entity_dict[ent.name] = ent
901 def lookup_entity(self, name, typ=None):
902 ent = self._entity_dict.get(name)
903 if typ and not isinstance(ent, typ):
904 return None
905 return ent
907 def lookup_type(self, name):
908 return self.lookup_entity(name, QAPISchemaType)
910 def resolve_type(self, name, info, what):
911 typ = self.lookup_type(name)
912 if not typ:
913 if callable(what):
914 what = what(info)
915 raise QAPISemError(
916 info, "%s uses unknown type '%s'" % (what, name))
917 return typ
919 def _module_name(self, fname: str) -> str:
920 if QAPISchemaModule.is_system_module(fname):
921 return fname
922 return os.path.relpath(fname, self._schema_dir)
924 def _make_module(self, fname):
925 name = self._module_name(fname)
926 if name not in self._module_dict:
927 self._module_dict[name] = QAPISchemaModule(name)
928 return self._module_dict[name]
930 def module_by_fname(self, fname):
931 name = self._module_name(fname)
932 return self._module_dict[name]
934 def _def_include(self, expr, info, doc):
935 include = expr['include']
936 assert doc is None
937 self._def_entity(QAPISchemaInclude(self._make_module(include), info))
939 def _def_builtin_type(self, name, json_type, c_type):
940 self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
941 # Instantiating only the arrays that are actually used would
942 # be nice, but we can't as long as their generated code
943 # (qapi-builtin-types.[ch]) may be shared by some other
944 # schema.
945 self._make_array_type(name, None)
947 def _def_predefineds(self):
948 for t in [('str', 'string', 'char' + POINTER_SUFFIX),
949 ('number', 'number', 'double'),
950 ('int', 'int', 'int64_t'),
951 ('int8', 'int', 'int8_t'),
952 ('int16', 'int', 'int16_t'),
953 ('int32', 'int', 'int32_t'),
954 ('int64', 'int', 'int64_t'),
955 ('uint8', 'int', 'uint8_t'),
956 ('uint16', 'int', 'uint16_t'),
957 ('uint32', 'int', 'uint32_t'),
958 ('uint64', 'int', 'uint64_t'),
959 ('size', 'int', 'uint64_t'),
960 ('bool', 'boolean', 'bool'),
961 ('any', 'value', 'QObject' + POINTER_SUFFIX),
962 ('null', 'null', 'QNull' + POINTER_SUFFIX)]:
963 self._def_builtin_type(*t)
964 self.the_empty_object_type = QAPISchemaObjectType(
965 'q_empty', None, None, None, None, None, [], None)
966 self._def_entity(self.the_empty_object_type)
968 qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
969 'qbool']
970 qtype_values = self._make_enum_members(
971 [{'name': n} for n in qtypes], None)
973 self._def_entity(QAPISchemaEnumType('QType', None, None, None, None,
974 qtype_values, 'QTYPE'))
976 def _make_features(self, features, info):
977 if features is None:
978 return []
979 return [QAPISchemaFeature(f['name'], info,
980 QAPISchemaIfCond(f.get('if')))
981 for f in features]
983 def _make_enum_members(self, values, info):
984 return [QAPISchemaEnumMember(v['name'], info,
985 QAPISchemaIfCond(v.get('if')))
986 for v in values]
988 def _make_array_type(self, element_type, info):
989 name = element_type + 'List' # reserved by check_defn_name_str()
990 if not self.lookup_type(name):
991 self._def_entity(QAPISchemaArrayType(name, info, element_type))
992 return name
994 def _make_implicit_object_type(self, name, info, ifcond, role, members):
995 if not members:
996 return None
997 # See also QAPISchemaObjectTypeMember.describe()
998 name = 'q_obj_%s-%s' % (name, role)
999 typ = self.lookup_entity(name, QAPISchemaObjectType)
1000 if typ:
1001 # The implicit object type has multiple users. This can
1002 # only be a duplicate definition, which will be flagged
1003 # later.
1004 pass
1005 else:
1006 self._def_entity(QAPISchemaObjectType(
1007 name, info, None, ifcond, None, None, members, None))
1008 return name
1010 def _def_enum_type(self, expr, info, doc):
1011 name = expr['enum']
1012 data = expr['data']
1013 prefix = expr.get('prefix')
1014 ifcond = QAPISchemaIfCond(expr.get('if'))
1015 features = self._make_features(expr.get('features'), info)
1016 self._def_entity(QAPISchemaEnumType(
1017 name, info, doc, ifcond, features,
1018 self._make_enum_members(data, info), prefix))
1020 def _make_member(self, name, typ, ifcond, features, info):
1021 optional = False
1022 if name.startswith('*'):
1023 name = name[1:]
1024 optional = True
1025 if isinstance(typ, list):
1026 assert len(typ) == 1
1027 typ = self._make_array_type(typ[0], info)
1028 return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond,
1029 self._make_features(features, info))
1031 def _make_members(self, data, info):
1032 return [self._make_member(key, value['type'],
1033 QAPISchemaIfCond(value.get('if')),
1034 value.get('features'), info)
1035 for (key, value) in data.items()]
1037 def _def_struct_type(self, expr, info, doc):
1038 name = expr['struct']
1039 base = expr.get('base')
1040 data = expr['data']
1041 ifcond = QAPISchemaIfCond(expr.get('if'))
1042 features = self._make_features(expr.get('features'), info)
1043 self._def_entity(QAPISchemaObjectType(
1044 name, info, doc, ifcond, features, base,
1045 self._make_members(data, info),
1046 None))
1048 def _make_variant(self, case, typ, ifcond, info):
1049 return QAPISchemaVariant(case, info, typ, ifcond)
1051 def _def_union_type(self, expr, info, doc):
1052 name = expr['union']
1053 base = expr['base']
1054 tag_name = expr['discriminator']
1055 data = expr['data']
1056 ifcond = QAPISchemaIfCond(expr.get('if'))
1057 features = self._make_features(expr.get('features'), info)
1058 if isinstance(base, dict):
1059 base = self._make_implicit_object_type(
1060 name, info, ifcond,
1061 'base', self._make_members(base, info))
1062 variants = [
1063 self._make_variant(key, value['type'],
1064 QAPISchemaIfCond(value.get('if')),
1065 info)
1066 for (key, value) in data.items()]
1067 members = []
1068 self._def_entity(
1069 QAPISchemaObjectType(name, info, doc, ifcond, features,
1070 base, members,
1071 QAPISchemaVariants(
1072 tag_name, info, None, variants)))
1074 def _def_alternate_type(self, expr, info, doc):
1075 name = expr['alternate']
1076 data = expr['data']
1077 ifcond = QAPISchemaIfCond(expr.get('if'))
1078 features = self._make_features(expr.get('features'), info)
1079 variants = [
1080 self._make_variant(key, value['type'],
1081 QAPISchemaIfCond(value.get('if')),
1082 info)
1083 for (key, value) in data.items()]
1084 tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
1085 self._def_entity(
1086 QAPISchemaAlternateType(name, info, doc, ifcond, features,
1087 QAPISchemaVariants(
1088 None, info, tag_member, variants)))
1090 def _def_command(self, expr, info, doc):
1091 name = expr['command']
1092 data = expr.get('data')
1093 rets = expr.get('returns')
1094 gen = expr.get('gen', True)
1095 success_response = expr.get('success-response', True)
1096 boxed = expr.get('boxed', False)
1097 allow_oob = expr.get('allow-oob', False)
1098 allow_preconfig = expr.get('allow-preconfig', False)
1099 coroutine = expr.get('coroutine', False)
1100 ifcond = QAPISchemaIfCond(expr.get('if'))
1101 features = self._make_features(expr.get('features'), info)
1102 if isinstance(data, OrderedDict):
1103 data = self._make_implicit_object_type(
1104 name, info, ifcond,
1105 'arg', self._make_members(data, info))
1106 if isinstance(rets, list):
1107 assert len(rets) == 1
1108 rets = self._make_array_type(rets[0], info)
1109 self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, features,
1110 data, rets,
1111 gen, success_response,
1112 boxed, allow_oob, allow_preconfig,
1113 coroutine))
1115 def _def_event(self, expr, info, doc):
1116 name = expr['event']
1117 data = expr.get('data')
1118 boxed = expr.get('boxed', False)
1119 ifcond = QAPISchemaIfCond(expr.get('if'))
1120 features = self._make_features(expr.get('features'), info)
1121 if isinstance(data, OrderedDict):
1122 data = self._make_implicit_object_type(
1123 name, info, ifcond,
1124 'arg', self._make_members(data, info))
1125 self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, features,
1126 data, boxed))
1128 def _def_exprs(self, exprs):
1129 for expr_elem in exprs:
1130 expr = expr_elem['expr']
1131 info = expr_elem['info']
1132 doc = expr_elem.get('doc')
1133 if 'enum' in expr:
1134 self._def_enum_type(expr, info, doc)
1135 elif 'struct' in expr:
1136 self._def_struct_type(expr, info, doc)
1137 elif 'union' in expr:
1138 self._def_union_type(expr, info, doc)
1139 elif 'alternate' in expr:
1140 self._def_alternate_type(expr, info, doc)
1141 elif 'command' in expr:
1142 self._def_command(expr, info, doc)
1143 elif 'event' in expr:
1144 self._def_event(expr, info, doc)
1145 elif 'include' in expr:
1146 self._def_include(expr, info, doc)
1147 else:
1148 assert False
1150 def check(self):
1151 for ent in self._entity_list:
1152 ent.check(self)
1153 ent.connect_doc()
1154 ent.check_doc()
1155 for ent in self._entity_list:
1156 ent.set_module(self)
1158 def visit(self, visitor):
1159 visitor.visit_begin(self)
1160 for mod in self._module_dict.values():
1161 mod.visit(visitor)
1162 visitor.visit_end()