qapi/parser: add QAPIExpression type
[qemu/kevin.git] / scripts / qapi / schema.py
blob207e4d71f39a6d16b3a46445d2d2516bc173cfeb
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 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 'array': 'QTYPE_QLIST',
247 'object': 'QTYPE_QDICT'
249 return json2qtype.get(self.json_type())
251 def doc_type(self):
252 if self.is_implicit():
253 return None
254 return self.name
256 def need_has_if_optional(self):
257 # When FOO is a pointer, has_FOO == !!FOO, i.e. has_FOO is redundant.
258 # Except for arrays; see QAPISchemaArrayType.need_has_if_optional().
259 return not self.c_type().endswith(POINTER_SUFFIX)
261 def check(self, schema):
262 QAPISchemaEntity.check(self, schema)
263 for feat in self.features:
264 if feat.is_special():
265 raise QAPISemError(
266 self.info,
267 f"feature '{feat.name}' is not supported for types")
269 def describe(self):
270 assert self.meta
271 return "%s type '%s'" % (self.meta, self.name)
274 class QAPISchemaBuiltinType(QAPISchemaType):
275 meta = 'built-in'
277 def __init__(self, name, json_type, c_type):
278 super().__init__(name, None, None)
279 assert not c_type or isinstance(c_type, str)
280 assert json_type in ('string', 'number', 'int', 'boolean', 'null',
281 'value')
282 self._json_type_name = json_type
283 self._c_type_name = c_type
285 def c_name(self):
286 return self.name
288 def c_type(self):
289 return self._c_type_name
291 def c_param_type(self):
292 if self.name == 'str':
293 return 'const ' + self._c_type_name
294 return self._c_type_name
296 def json_type(self):
297 return self._json_type_name
299 def doc_type(self):
300 return self.json_type()
302 def visit(self, visitor):
303 super().visit(visitor)
304 visitor.visit_builtin_type(self.name, self.info, self.json_type())
307 class QAPISchemaEnumType(QAPISchemaType):
308 meta = 'enum'
310 def __init__(self, name, info, doc, ifcond, features, members, prefix):
311 super().__init__(name, info, doc, ifcond, features)
312 for m in members:
313 assert isinstance(m, QAPISchemaEnumMember)
314 m.set_defined_in(name)
315 assert prefix is None or isinstance(prefix, str)
316 self.members = members
317 self.prefix = prefix
319 def check(self, schema):
320 super().check(schema)
321 seen = {}
322 for m in self.members:
323 m.check_clash(self.info, seen)
325 def connect_doc(self, doc=None):
326 super().connect_doc(doc)
327 doc = doc or self.doc
328 for m in self.members:
329 m.connect_doc(doc)
331 def is_implicit(self):
332 # See QAPISchema._def_predefineds()
333 return self.name == 'QType'
335 def c_type(self):
336 return c_name(self.name)
338 def member_names(self):
339 return [m.name for m in self.members]
341 def json_type(self):
342 return 'string'
344 def visit(self, visitor):
345 super().visit(visitor)
346 visitor.visit_enum_type(
347 self.name, self.info, self.ifcond, self.features,
348 self.members, self.prefix)
351 class QAPISchemaArrayType(QAPISchemaType):
352 meta = 'array'
354 def __init__(self, name, info, element_type):
355 super().__init__(name, info, None)
356 assert isinstance(element_type, str)
357 self._element_type_name = element_type
358 self.element_type = None
360 def need_has_if_optional(self):
361 # When FOO is an array, we still need has_FOO to distinguish
362 # absent (!has_FOO) from present and empty (has_FOO && !FOO).
363 return True
365 def check(self, schema):
366 super().check(schema)
367 self.element_type = schema.resolve_type(
368 self._element_type_name, self.info,
369 self.info and self.info.defn_meta)
370 assert not isinstance(self.element_type, QAPISchemaArrayType)
372 def set_module(self, schema):
373 self._set_module(schema, self.element_type.info)
375 @property
376 def ifcond(self):
377 assert self._checked
378 return self.element_type.ifcond
380 def is_implicit(self):
381 return True
383 def c_type(self):
384 return c_name(self.name) + POINTER_SUFFIX
386 def json_type(self):
387 return 'array'
389 def doc_type(self):
390 elt_doc_type = self.element_type.doc_type()
391 if not elt_doc_type:
392 return None
393 return 'array of ' + elt_doc_type
395 def visit(self, visitor):
396 super().visit(visitor)
397 visitor.visit_array_type(self.name, self.info, self.ifcond,
398 self.element_type)
400 def describe(self):
401 assert self.meta
402 return "%s type ['%s']" % (self.meta, self._element_type_name)
405 class QAPISchemaObjectType(QAPISchemaType):
406 def __init__(self, name, info, doc, ifcond, features,
407 base, local_members, variants):
408 # struct has local_members, optional base, and no variants
409 # union has base, variants, and no local_members
410 super().__init__(name, info, doc, ifcond, features)
411 self.meta = 'union' if variants else 'struct'
412 assert base is None or isinstance(base, str)
413 for m in local_members:
414 assert isinstance(m, QAPISchemaObjectTypeMember)
415 m.set_defined_in(name)
416 if variants is not None:
417 assert isinstance(variants, QAPISchemaVariants)
418 variants.set_defined_in(name)
419 self._base_name = base
420 self.base = None
421 self.local_members = local_members
422 self.variants = variants
423 self.members = None
425 def check(self, schema):
426 # This calls another type T's .check() exactly when the C
427 # struct emitted by gen_object() contains that T's C struct
428 # (pointers don't count).
429 if self.members is not None:
430 # A previous .check() completed: nothing to do
431 return
432 if self._checked:
433 # Recursed: C struct contains itself
434 raise QAPISemError(self.info,
435 "object %s contains itself" % self.name)
437 super().check(schema)
438 assert self._checked and self.members is None
440 seen = OrderedDict()
441 if self._base_name:
442 self.base = schema.resolve_type(self._base_name, self.info,
443 "'base'")
444 if (not isinstance(self.base, QAPISchemaObjectType)
445 or self.base.variants):
446 raise QAPISemError(
447 self.info,
448 "'base' requires a struct type, %s isn't"
449 % self.base.describe())
450 self.base.check(schema)
451 self.base.check_clash(self.info, seen)
452 for m in self.local_members:
453 m.check(schema)
454 m.check_clash(self.info, seen)
455 members = seen.values()
457 if self.variants:
458 self.variants.check(schema, seen)
459 self.variants.check_clash(self.info, seen)
461 self.members = members # mark completed
463 # Check that the members of this type do not cause duplicate JSON members,
464 # and update seen to track the members seen so far. Report any errors
465 # on behalf of info, which is not necessarily self.info
466 def check_clash(self, info, seen):
467 assert self._checked
468 assert not self.variants # not implemented
469 for m in self.members:
470 m.check_clash(info, seen)
472 def connect_doc(self, doc=None):
473 super().connect_doc(doc)
474 doc = doc or self.doc
475 if self.base and self.base.is_implicit():
476 self.base.connect_doc(doc)
477 for m in self.local_members:
478 m.connect_doc(doc)
480 def is_implicit(self):
481 # See QAPISchema._make_implicit_object_type(), as well as
482 # _def_predefineds()
483 return self.name.startswith('q_')
485 def is_empty(self):
486 assert self.members is not None
487 return not self.members and not self.variants
489 def c_name(self):
490 assert self.name != 'q_empty'
491 return super().c_name()
493 def c_type(self):
494 assert not self.is_implicit()
495 return c_name(self.name) + POINTER_SUFFIX
497 def c_unboxed_type(self):
498 return c_name(self.name)
500 def json_type(self):
501 return 'object'
503 def visit(self, visitor):
504 super().visit(visitor)
505 visitor.visit_object_type(
506 self.name, self.info, self.ifcond, self.features,
507 self.base, self.local_members, self.variants)
508 visitor.visit_object_type_flat(
509 self.name, self.info, self.ifcond, self.features,
510 self.members, self.variants)
513 class QAPISchemaAlternateType(QAPISchemaType):
514 meta = 'alternate'
516 def __init__(self, name, info, doc, ifcond, features, variants):
517 super().__init__(name, info, doc, ifcond, features)
518 assert isinstance(variants, QAPISchemaVariants)
519 assert variants.tag_member
520 variants.set_defined_in(name)
521 variants.tag_member.set_defined_in(self.name)
522 self.variants = variants
524 def check(self, schema):
525 super().check(schema)
526 self.variants.tag_member.check(schema)
527 # Not calling self.variants.check_clash(), because there's nothing
528 # to clash with
529 self.variants.check(schema, {})
530 # Alternate branch names have no relation to the tag enum values;
531 # so we have to check for potential name collisions ourselves.
532 seen = {}
533 types_seen = {}
534 for v in self.variants.variants:
535 v.check_clash(self.info, seen)
536 qtype = v.type.alternate_qtype()
537 if not qtype:
538 raise QAPISemError(
539 self.info,
540 "%s cannot use %s"
541 % (v.describe(self.info), v.type.describe()))
542 conflicting = set([qtype])
543 if qtype == 'QTYPE_QSTRING':
544 if isinstance(v.type, QAPISchemaEnumType):
545 for m in v.type.members:
546 if m.name in ['on', 'off']:
547 conflicting.add('QTYPE_QBOOL')
548 if re.match(r'[-+0-9.]', m.name):
549 # lazy, could be tightened
550 conflicting.add('QTYPE_QNUM')
551 else:
552 conflicting.add('QTYPE_QNUM')
553 conflicting.add('QTYPE_QBOOL')
554 for qt in conflicting:
555 if qt in types_seen:
556 raise QAPISemError(
557 self.info,
558 "%s can't be distinguished from '%s'"
559 % (v.describe(self.info), types_seen[qt]))
560 types_seen[qt] = v.name
562 def connect_doc(self, doc=None):
563 super().connect_doc(doc)
564 doc = doc or self.doc
565 for v in self.variants.variants:
566 v.connect_doc(doc)
568 def c_type(self):
569 return c_name(self.name) + POINTER_SUFFIX
571 def json_type(self):
572 return 'value'
574 def visit(self, visitor):
575 super().visit(visitor)
576 visitor.visit_alternate_type(
577 self.name, self.info, self.ifcond, self.features, self.variants)
580 class QAPISchemaVariants:
581 def __init__(self, tag_name, info, tag_member, variants):
582 # Unions pass tag_name but not tag_member.
583 # Alternates pass tag_member but not tag_name.
584 # After check(), tag_member is always set.
585 assert bool(tag_member) != bool(tag_name)
586 assert (isinstance(tag_name, str) or
587 isinstance(tag_member, QAPISchemaObjectTypeMember))
588 for v in variants:
589 assert isinstance(v, QAPISchemaVariant)
590 self._tag_name = tag_name
591 self.info = info
592 self.tag_member = tag_member
593 self.variants = variants
595 def set_defined_in(self, name):
596 for v in self.variants:
597 v.set_defined_in(name)
599 def check(self, schema, seen):
600 if self._tag_name: # union
601 self.tag_member = seen.get(c_name(self._tag_name))
602 base = "'base'"
603 # Pointing to the base type when not implicit would be
604 # nice, but we don't know it here
605 if not self.tag_member or self._tag_name != self.tag_member.name:
606 raise QAPISemError(
607 self.info,
608 "discriminator '%s' is not a member of %s"
609 % (self._tag_name, base))
610 # Here we do:
611 base_type = schema.lookup_type(self.tag_member.defined_in)
612 assert base_type
613 if not base_type.is_implicit():
614 base = "base type '%s'" % self.tag_member.defined_in
615 if not isinstance(self.tag_member.type, QAPISchemaEnumType):
616 raise QAPISemError(
617 self.info,
618 "discriminator member '%s' of %s must be of enum type"
619 % (self._tag_name, base))
620 if self.tag_member.optional:
621 raise QAPISemError(
622 self.info,
623 "discriminator member '%s' of %s must not be optional"
624 % (self._tag_name, base))
625 if self.tag_member.ifcond.is_present():
626 raise QAPISemError(
627 self.info,
628 "discriminator member '%s' of %s must not be conditional"
629 % (self._tag_name, base))
630 else: # alternate
631 assert isinstance(self.tag_member.type, QAPISchemaEnumType)
632 assert not self.tag_member.optional
633 assert not self.tag_member.ifcond.is_present()
634 if self._tag_name: # union
635 # branches that are not explicitly covered get an empty type
636 cases = {v.name for v in self.variants}
637 for m in self.tag_member.type.members:
638 if m.name not in cases:
639 v = QAPISchemaVariant(m.name, self.info,
640 'q_empty', m.ifcond)
641 v.set_defined_in(self.tag_member.defined_in)
642 self.variants.append(v)
643 if not self.variants:
644 raise QAPISemError(self.info, "union has no branches")
645 for v in self.variants:
646 v.check(schema)
647 # Union names must match enum values; alternate names are
648 # checked separately. Use 'seen' to tell the two apart.
649 if seen:
650 if v.name not in self.tag_member.type.member_names():
651 raise QAPISemError(
652 self.info,
653 "branch '%s' is not a value of %s"
654 % (v.name, self.tag_member.type.describe()))
655 if (not isinstance(v.type, QAPISchemaObjectType)
656 or v.type.variants):
657 raise QAPISemError(
658 self.info,
659 "%s cannot use %s"
660 % (v.describe(self.info), v.type.describe()))
661 v.type.check(schema)
663 def check_clash(self, info, seen):
664 for v in self.variants:
665 # Reset seen map for each variant, since qapi names from one
666 # branch do not affect another branch
667 v.type.check_clash(info, dict(seen))
670 class QAPISchemaMember:
671 """ Represents object members, enum members and features """
672 role = 'member'
674 def __init__(self, name, info, ifcond=None):
675 assert isinstance(name, str)
676 self.name = name
677 self.info = info
678 self.ifcond = ifcond or QAPISchemaIfCond()
679 self.defined_in = None
681 def set_defined_in(self, name):
682 assert not self.defined_in
683 self.defined_in = name
685 def check_clash(self, info, seen):
686 cname = c_name(self.name)
687 if cname in seen:
688 raise QAPISemError(
689 info,
690 "%s collides with %s"
691 % (self.describe(info), seen[cname].describe(info)))
692 seen[cname] = self
694 def connect_doc(self, doc):
695 if doc:
696 doc.connect_member(self)
698 def describe(self, info):
699 role = self.role
700 defined_in = self.defined_in
701 assert defined_in
703 if defined_in.startswith('q_obj_'):
704 # See QAPISchema._make_implicit_object_type() - reverse the
705 # mapping there to create a nice human-readable description
706 defined_in = defined_in[6:]
707 if defined_in.endswith('-arg'):
708 # Implicit type created for a command's dict 'data'
709 assert role == 'member'
710 role = 'parameter'
711 elif defined_in.endswith('-base'):
712 # Implicit type created for a union's dict 'base'
713 role = 'base ' + role
714 else:
715 assert False
716 elif defined_in != info.defn_name:
717 return "%s '%s' of type '%s'" % (role, self.name, defined_in)
718 return "%s '%s'" % (role, self.name)
721 class QAPISchemaEnumMember(QAPISchemaMember):
722 role = 'value'
724 def __init__(self, name, info, ifcond=None, features=None):
725 super().__init__(name, info, ifcond)
726 for f in features or []:
727 assert isinstance(f, QAPISchemaFeature)
728 f.set_defined_in(name)
729 self.features = features or []
731 def connect_doc(self, doc):
732 super().connect_doc(doc)
733 if doc:
734 for f in self.features:
735 doc.connect_feature(f)
738 class QAPISchemaFeature(QAPISchemaMember):
739 role = 'feature'
741 def is_special(self):
742 return self.name in ('deprecated', 'unstable')
745 class QAPISchemaObjectTypeMember(QAPISchemaMember):
746 def __init__(self, name, info, typ, optional, ifcond=None, features=None):
747 super().__init__(name, info, ifcond)
748 assert isinstance(typ, str)
749 assert isinstance(optional, bool)
750 for f in features or []:
751 assert isinstance(f, QAPISchemaFeature)
752 f.set_defined_in(name)
753 self._type_name = typ
754 self.type = None
755 self.optional = optional
756 self.features = features or []
758 def need_has(self):
759 assert self.type
760 return self.optional and self.type.need_has_if_optional()
762 def check(self, schema):
763 assert self.defined_in
764 self.type = schema.resolve_type(self._type_name, self.info,
765 self.describe)
766 seen = {}
767 for f in self.features:
768 f.check_clash(self.info, seen)
770 def connect_doc(self, doc):
771 super().connect_doc(doc)
772 if doc:
773 for f in self.features:
774 doc.connect_feature(f)
777 class QAPISchemaVariant(QAPISchemaObjectTypeMember):
778 role = 'branch'
780 def __init__(self, name, info, typ, ifcond=None):
781 super().__init__(name, info, typ, False, ifcond)
784 class QAPISchemaCommand(QAPISchemaEntity):
785 meta = 'command'
787 def __init__(self, name, info, doc, ifcond, features,
788 arg_type, ret_type,
789 gen, success_response, boxed, allow_oob, allow_preconfig,
790 coroutine):
791 super().__init__(name, info, doc, ifcond, features)
792 assert not arg_type or isinstance(arg_type, str)
793 assert not ret_type or isinstance(ret_type, str)
794 self._arg_type_name = arg_type
795 self.arg_type = None
796 self._ret_type_name = ret_type
797 self.ret_type = None
798 self.gen = gen
799 self.success_response = success_response
800 self.boxed = boxed
801 self.allow_oob = allow_oob
802 self.allow_preconfig = allow_preconfig
803 self.coroutine = coroutine
805 def check(self, schema):
806 super().check(schema)
807 if self._arg_type_name:
808 self.arg_type = schema.resolve_type(
809 self._arg_type_name, self.info, "command's 'data'")
810 if not isinstance(self.arg_type, QAPISchemaObjectType):
811 raise QAPISemError(
812 self.info,
813 "command's 'data' cannot take %s"
814 % self.arg_type.describe())
815 if self.arg_type.variants and not self.boxed:
816 raise QAPISemError(
817 self.info,
818 "command's 'data' can take %s only with 'boxed': true"
819 % self.arg_type.describe())
820 if self._ret_type_name:
821 self.ret_type = schema.resolve_type(
822 self._ret_type_name, self.info, "command's 'returns'")
823 if self.name not in self.info.pragma.command_returns_exceptions:
824 typ = self.ret_type
825 if isinstance(typ, QAPISchemaArrayType):
826 typ = self.ret_type.element_type
827 assert typ
828 if not isinstance(typ, QAPISchemaObjectType):
829 raise QAPISemError(
830 self.info,
831 "command's 'returns' cannot take %s"
832 % self.ret_type.describe())
834 def connect_doc(self, doc=None):
835 super().connect_doc(doc)
836 doc = doc or self.doc
837 if doc:
838 if self.arg_type and self.arg_type.is_implicit():
839 self.arg_type.connect_doc(doc)
841 def visit(self, visitor):
842 super().visit(visitor)
843 visitor.visit_command(
844 self.name, self.info, self.ifcond, self.features,
845 self.arg_type, self.ret_type, self.gen, self.success_response,
846 self.boxed, self.allow_oob, self.allow_preconfig,
847 self.coroutine)
850 class QAPISchemaEvent(QAPISchemaEntity):
851 meta = 'event'
853 def __init__(self, name, info, doc, ifcond, features, arg_type, boxed):
854 super().__init__(name, info, doc, ifcond, features)
855 assert not arg_type or isinstance(arg_type, str)
856 self._arg_type_name = arg_type
857 self.arg_type = None
858 self.boxed = boxed
860 def check(self, schema):
861 super().check(schema)
862 if self._arg_type_name:
863 self.arg_type = schema.resolve_type(
864 self._arg_type_name, self.info, "event's 'data'")
865 if not isinstance(self.arg_type, QAPISchemaObjectType):
866 raise QAPISemError(
867 self.info,
868 "event's 'data' cannot take %s"
869 % self.arg_type.describe())
870 if self.arg_type.variants and not self.boxed:
871 raise QAPISemError(
872 self.info,
873 "event's 'data' can take %s only with 'boxed': true"
874 % self.arg_type.describe())
876 def connect_doc(self, doc=None):
877 super().connect_doc(doc)
878 doc = doc or self.doc
879 if doc:
880 if self.arg_type and self.arg_type.is_implicit():
881 self.arg_type.connect_doc(doc)
883 def visit(self, visitor):
884 super().visit(visitor)
885 visitor.visit_event(
886 self.name, self.info, self.ifcond, self.features,
887 self.arg_type, self.boxed)
890 class QAPISchema:
891 def __init__(self, fname):
892 self.fname = fname
894 try:
895 parser = QAPISchemaParser(fname)
896 except OSError as err:
897 raise QAPIError(
898 f"can't read schema file '{fname}': {err.strerror}"
899 ) from err
901 exprs = check_exprs(parser.exprs)
902 self.docs = parser.docs
903 self._entity_list = []
904 self._entity_dict = {}
905 self._module_dict = OrderedDict()
906 self._schema_dir = os.path.dirname(fname)
907 self._make_module(QAPISchemaModule.BUILTIN_MODULE_NAME)
908 self._make_module(fname)
909 self._predefining = True
910 self._def_predefineds()
911 self._predefining = False
912 self._def_exprs(exprs)
913 self.check()
915 def _def_entity(self, ent):
916 # Only the predefined types are allowed to not have info
917 assert ent.info or self._predefining
918 self._entity_list.append(ent)
919 if ent.name is None:
920 return
921 # TODO reject names that differ only in '_' vs. '.' vs. '-',
922 # because they're liable to clash in generated C.
923 other_ent = self._entity_dict.get(ent.name)
924 if other_ent:
925 if other_ent.info:
926 where = QAPISourceError(other_ent.info, "previous definition")
927 raise QAPISemError(
928 ent.info,
929 "'%s' is already defined\n%s" % (ent.name, where))
930 raise QAPISemError(
931 ent.info, "%s is already defined" % other_ent.describe())
932 self._entity_dict[ent.name] = ent
934 def lookup_entity(self, name, typ=None):
935 ent = self._entity_dict.get(name)
936 if typ and not isinstance(ent, typ):
937 return None
938 return ent
940 def lookup_type(self, name):
941 return self.lookup_entity(name, QAPISchemaType)
943 def resolve_type(self, name, info, what):
944 typ = self.lookup_type(name)
945 if not typ:
946 if callable(what):
947 what = what(info)
948 raise QAPISemError(
949 info, "%s uses unknown type '%s'" % (what, name))
950 return typ
952 def _module_name(self, fname: str) -> str:
953 if QAPISchemaModule.is_system_module(fname):
954 return fname
955 return os.path.relpath(fname, self._schema_dir)
957 def _make_module(self, fname):
958 name = self._module_name(fname)
959 if name not in self._module_dict:
960 self._module_dict[name] = QAPISchemaModule(name)
961 return self._module_dict[name]
963 def module_by_fname(self, fname):
964 name = self._module_name(fname)
965 return self._module_dict[name]
967 def _def_include(self, expr: QAPIExpression):
968 include = expr['include']
969 assert expr.doc is None
970 self._def_entity(
971 QAPISchemaInclude(self._make_module(include), expr.info))
973 def _def_builtin_type(self, name, json_type, c_type):
974 self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
975 # Instantiating only the arrays that are actually used would
976 # be nice, but we can't as long as their generated code
977 # (qapi-builtin-types.[ch]) may be shared by some other
978 # schema.
979 self._make_array_type(name, None)
981 def _def_predefineds(self):
982 for t in [('str', 'string', 'char' + POINTER_SUFFIX),
983 ('number', 'number', 'double'),
984 ('int', 'int', 'int64_t'),
985 ('int8', 'int', 'int8_t'),
986 ('int16', 'int', 'int16_t'),
987 ('int32', 'int', 'int32_t'),
988 ('int64', 'int', 'int64_t'),
989 ('uint8', 'int', 'uint8_t'),
990 ('uint16', 'int', 'uint16_t'),
991 ('uint32', 'int', 'uint32_t'),
992 ('uint64', 'int', 'uint64_t'),
993 ('size', 'int', 'uint64_t'),
994 ('bool', 'boolean', 'bool'),
995 ('any', 'value', 'QObject' + POINTER_SUFFIX),
996 ('null', 'null', 'QNull' + POINTER_SUFFIX)]:
997 self._def_builtin_type(*t)
998 self.the_empty_object_type = QAPISchemaObjectType(
999 'q_empty', None, None, None, None, None, [], None)
1000 self._def_entity(self.the_empty_object_type)
1002 qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
1003 'qbool']
1004 qtype_values = self._make_enum_members(
1005 [{'name': n} for n in qtypes], None)
1007 self._def_entity(QAPISchemaEnumType('QType', None, None, None, None,
1008 qtype_values, 'QTYPE'))
1010 def _make_features(self, features, info):
1011 if features is None:
1012 return []
1013 return [QAPISchemaFeature(f['name'], info,
1014 QAPISchemaIfCond(f.get('if')))
1015 for f in features]
1017 def _make_enum_member(self, name, ifcond, features, info):
1018 return QAPISchemaEnumMember(name, info,
1019 QAPISchemaIfCond(ifcond),
1020 self._make_features(features, info))
1022 def _make_enum_members(self, values, info):
1023 return [self._make_enum_member(v['name'], v.get('if'),
1024 v.get('features'), info)
1025 for v in values]
1027 def _make_array_type(self, element_type, info):
1028 name = element_type + 'List' # reserved by check_defn_name_str()
1029 if not self.lookup_type(name):
1030 self._def_entity(QAPISchemaArrayType(name, info, element_type))
1031 return name
1033 def _make_implicit_object_type(self, name, info, ifcond, role, members):
1034 if not members:
1035 return None
1036 # See also QAPISchemaObjectTypeMember.describe()
1037 name = 'q_obj_%s-%s' % (name, role)
1038 typ = self.lookup_entity(name, QAPISchemaObjectType)
1039 if typ:
1040 # The implicit object type has multiple users. This can
1041 # only be a duplicate definition, which will be flagged
1042 # later.
1043 pass
1044 else:
1045 self._def_entity(QAPISchemaObjectType(
1046 name, info, None, ifcond, None, None, members, None))
1047 return name
1049 def _def_enum_type(self, expr: QAPIExpression):
1050 name = expr['enum']
1051 data = expr['data']
1052 prefix = expr.get('prefix')
1053 ifcond = QAPISchemaIfCond(expr.get('if'))
1054 info = expr.info
1055 features = self._make_features(expr.get('features'), info)
1056 self._def_entity(QAPISchemaEnumType(
1057 name, info, expr.doc, ifcond, features,
1058 self._make_enum_members(data, info), prefix))
1060 def _make_member(self, name, typ, ifcond, features, info):
1061 optional = False
1062 if name.startswith('*'):
1063 name = name[1:]
1064 optional = True
1065 if isinstance(typ, list):
1066 assert len(typ) == 1
1067 typ = self._make_array_type(typ[0], info)
1068 return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond,
1069 self._make_features(features, info))
1071 def _make_members(self, data, info):
1072 return [self._make_member(key, value['type'],
1073 QAPISchemaIfCond(value.get('if')),
1074 value.get('features'), info)
1075 for (key, value) in data.items()]
1077 def _def_struct_type(self, expr: QAPIExpression):
1078 name = expr['struct']
1079 base = expr.get('base')
1080 data = expr['data']
1081 info = expr.info
1082 ifcond = QAPISchemaIfCond(expr.get('if'))
1083 features = self._make_features(expr.get('features'), info)
1084 self._def_entity(QAPISchemaObjectType(
1085 name, info, expr.doc, ifcond, features, base,
1086 self._make_members(data, info),
1087 None))
1089 def _make_variant(self, case, typ, ifcond, info):
1090 if isinstance(typ, list):
1091 assert len(typ) == 1
1092 typ = self._make_array_type(typ[0], info)
1093 return QAPISchemaVariant(case, info, typ, ifcond)
1095 def _def_union_type(self, expr: QAPIExpression):
1096 name = expr['union']
1097 base = expr['base']
1098 tag_name = expr['discriminator']
1099 data = expr['data']
1100 assert isinstance(data, dict)
1101 info = expr.info
1102 ifcond = QAPISchemaIfCond(expr.get('if'))
1103 features = self._make_features(expr.get('features'), info)
1104 if isinstance(base, dict):
1105 base = self._make_implicit_object_type(
1106 name, info, ifcond,
1107 'base', self._make_members(base, info))
1108 variants = [
1109 self._make_variant(key, value['type'],
1110 QAPISchemaIfCond(value.get('if')),
1111 info)
1112 for (key, value) in data.items()]
1113 members: List[QAPISchemaObjectTypeMember] = []
1114 self._def_entity(
1115 QAPISchemaObjectType(name, info, expr.doc, ifcond, features,
1116 base, members,
1117 QAPISchemaVariants(
1118 tag_name, info, None, variants)))
1120 def _def_alternate_type(self, expr: QAPIExpression):
1121 name = expr['alternate']
1122 data = expr['data']
1123 assert isinstance(data, dict)
1124 ifcond = QAPISchemaIfCond(expr.get('if'))
1125 info = expr.info
1126 features = self._make_features(expr.get('features'), info)
1127 variants = [
1128 self._make_variant(key, value['type'],
1129 QAPISchemaIfCond(value.get('if')),
1130 info)
1131 for (key, value) in data.items()]
1132 tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
1133 self._def_entity(
1134 QAPISchemaAlternateType(
1135 name, info, expr.doc, ifcond, features,
1136 QAPISchemaVariants(None, info, tag_member, variants)))
1138 def _def_command(self, expr: QAPIExpression):
1139 name = expr['command']
1140 data = expr.get('data')
1141 rets = expr.get('returns')
1142 gen = expr.get('gen', True)
1143 success_response = expr.get('success-response', True)
1144 boxed = expr.get('boxed', False)
1145 allow_oob = expr.get('allow-oob', False)
1146 allow_preconfig = expr.get('allow-preconfig', False)
1147 coroutine = expr.get('coroutine', False)
1148 ifcond = QAPISchemaIfCond(expr.get('if'))
1149 info = expr.info
1150 features = self._make_features(expr.get('features'), info)
1151 if isinstance(data, OrderedDict):
1152 data = self._make_implicit_object_type(
1153 name, info, ifcond,
1154 'arg', self._make_members(data, info))
1155 if isinstance(rets, list):
1156 assert len(rets) == 1
1157 rets = self._make_array_type(rets[0], info)
1158 self._def_entity(QAPISchemaCommand(name, info, expr.doc, ifcond,
1159 features, data, rets,
1160 gen, success_response,
1161 boxed, allow_oob, allow_preconfig,
1162 coroutine))
1164 def _def_event(self, expr: QAPIExpression):
1165 name = expr['event']
1166 data = expr.get('data')
1167 boxed = expr.get('boxed', False)
1168 ifcond = QAPISchemaIfCond(expr.get('if'))
1169 info = expr.info
1170 features = self._make_features(expr.get('features'), info)
1171 if isinstance(data, OrderedDict):
1172 data = self._make_implicit_object_type(
1173 name, info, ifcond,
1174 'arg', self._make_members(data, info))
1175 self._def_entity(QAPISchemaEvent(name, info, expr.doc, ifcond,
1176 features, data, boxed))
1178 def _def_exprs(self, exprs):
1179 for expr in exprs:
1180 if 'enum' in expr:
1181 self._def_enum_type(expr)
1182 elif 'struct' in expr:
1183 self._def_struct_type(expr)
1184 elif 'union' in expr:
1185 self._def_union_type(expr)
1186 elif 'alternate' in expr:
1187 self._def_alternate_type(expr)
1188 elif 'command' in expr:
1189 self._def_command(expr)
1190 elif 'event' in expr:
1191 self._def_event(expr)
1192 elif 'include' in expr:
1193 self._def_include(expr)
1194 else:
1195 assert False
1197 def check(self):
1198 for ent in self._entity_list:
1199 ent.check(self)
1200 ent.connect_doc()
1201 ent.check_doc()
1202 for ent in self._entity_list:
1203 ent.set_module(self)
1205 def visit(self, visitor):
1206 visitor.visit_begin(self)
1207 for mod in self._module_dict.values():
1208 mod.visit(visitor)
1209 visitor.visit_end()