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