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