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