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