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