xref: /openbmc/qemu/scripts/qapi/introspect.py (revision dc5bd18f)
1"""
2QAPI introspection generator
3
4Copyright (C) 2015-2018 Red Hat, Inc.
5
6Authors:
7 Markus Armbruster <armbru@redhat.com>
8
9This work is licensed under the terms of the GNU GPL, version 2.
10See the COPYING file in the top-level directory.
11"""
12
13from qapi.common import *
14
15
16# Caveman's json.dumps() replacement (we're stuck at Python 2.4)
17# TODO try to use json.dumps() once we get unstuck
18def to_json(obj, level=0):
19    if obj is None:
20        ret = 'null'
21    elif isinstance(obj, str):
22        ret = '"' + obj.replace('"', r'\"') + '"'
23    elif isinstance(obj, list):
24        elts = [to_json(elt, level + 1)
25                for elt in obj]
26        ret = '[' + ', '.join(elts) + ']'
27    elif isinstance(obj, dict):
28        elts = ['"%s": %s' % (key.replace('"', r'\"'),
29                              to_json(obj[key], level + 1))
30                for key in sorted(obj.keys())]
31        ret = '{' + ', '.join(elts) + '}'
32    else:
33        assert False                # not implemented
34    if level == 1:
35        ret = '\n' + ret
36    return ret
37
38
39def to_c_string(string):
40    return '"' + string.replace('\\', r'\\').replace('"', r'\"') + '"'
41
42
43class QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor):
44
45    def __init__(self, prefix, unmask):
46        QAPISchemaMonolithicCVisitor.__init__(
47            self, prefix, 'qapi-introspect',
48            ' * QAPI/QMP schema introspection', __doc__)
49        self._unmask = unmask
50        self._schema = None
51        self._jsons = []
52        self._used_types = []
53        self._name_map = {}
54        self._genc.add(mcgen('''
55#include "qemu/osdep.h"
56#include "%(prefix)sqapi-introspect.h"
57
58''',
59                             prefix=prefix))
60
61    def visit_begin(self, schema):
62        self._schema = schema
63
64    def visit_end(self):
65        # visit the types that are actually used
66        jsons = self._jsons
67        self._jsons = []
68        for typ in self._used_types:
69            typ.visit(self)
70        # generate C
71        # TODO can generate awfully long lines
72        jsons.extend(self._jsons)
73        name = c_name(self._prefix, protect=False) + 'qmp_schema_json'
74        self._genh.add(mcgen('''
75extern const char %(c_name)s[];
76''',
77                             c_name=c_name(name)))
78        lines = to_json(jsons).split('\n')
79        c_string = '\n    '.join([to_c_string(line) for line in lines])
80        self._genc.add(mcgen('''
81const char %(c_name)s[] = %(c_string)s;
82''',
83                             c_name=c_name(name),
84                             c_string=c_string))
85        self._schema = None
86        self._jsons = []
87        self._used_types = []
88        self._name_map = {}
89
90    def visit_needed(self, entity):
91        # Ignore types on first pass; visit_end() will pick up used types
92        return not isinstance(entity, QAPISchemaType)
93
94    def _name(self, name):
95        if self._unmask:
96            return name
97        if name not in self._name_map:
98            self._name_map[name] = '%d' % len(self._name_map)
99        return self._name_map[name]
100
101    def _use_type(self, typ):
102        # Map the various integer types to plain int
103        if typ.json_type() == 'int':
104            typ = self._schema.lookup_type('int')
105        elif (isinstance(typ, QAPISchemaArrayType) and
106              typ.element_type.json_type() == 'int'):
107            typ = self._schema.lookup_type('intList')
108        # Add type to work queue if new
109        if typ not in self._used_types:
110            self._used_types.append(typ)
111        # Clients should examine commands and events, not types.  Hide
112        # type names to reduce the temptation.  Also saves a few
113        # characters.
114        if isinstance(typ, QAPISchemaBuiltinType):
115            return typ.name
116        if isinstance(typ, QAPISchemaArrayType):
117            return '[' + self._use_type(typ.element_type) + ']'
118        return self._name(typ.name)
119
120    def _gen_json(self, name, mtype, obj):
121        if mtype not in ('command', 'event', 'builtin', 'array'):
122            name = self._name(name)
123        obj['name'] = name
124        obj['meta-type'] = mtype
125        self._jsons.append(obj)
126
127    def _gen_member(self, member):
128        ret = {'name': member.name, 'type': self._use_type(member.type)}
129        if member.optional:
130            ret['default'] = None
131        return ret
132
133    def _gen_variants(self, tag_name, variants):
134        return {'tag': tag_name,
135                'variants': [self._gen_variant(v) for v in variants]}
136
137    def _gen_variant(self, variant):
138        return {'case': variant.name, 'type': self._use_type(variant.type)}
139
140    def visit_builtin_type(self, name, info, json_type):
141        self._gen_json(name, 'builtin', {'json-type': json_type})
142
143    def visit_enum_type(self, name, info, values, prefix):
144        self._gen_json(name, 'enum', {'values': values})
145
146    def visit_array_type(self, name, info, element_type):
147        element = self._use_type(element_type)
148        self._gen_json('[' + element + ']', 'array', {'element-type': element})
149
150    def visit_object_type_flat(self, name, info, members, variants):
151        obj = {'members': [self._gen_member(m) for m in members]}
152        if variants:
153            obj.update(self._gen_variants(variants.tag_member.name,
154                                          variants.variants))
155        self._gen_json(name, 'object', obj)
156
157    def visit_alternate_type(self, name, info, variants):
158        self._gen_json(name, 'alternate',
159                       {'members': [{'type': self._use_type(m.type)}
160                                    for m in variants.variants]})
161
162    def visit_command(self, name, info, arg_type, ret_type,
163                      gen, success_response, boxed):
164        arg_type = arg_type or self._schema.the_empty_object_type
165        ret_type = ret_type or self._schema.the_empty_object_type
166        self._gen_json(name, 'command',
167                       {'arg-type': self._use_type(arg_type),
168                        'ret-type': self._use_type(ret_type)})
169
170    def visit_event(self, name, info, arg_type, boxed):
171        arg_type = arg_type or self._schema.the_empty_object_type
172        self._gen_json(name, 'event', {'arg-type': self._use_type(arg_type)})
173
174
175def gen_introspect(schema, output_dir, prefix, opt_unmask):
176    vis = QAPISchemaGenIntrospectVisitor(prefix, opt_unmask)
177    schema.visit(vis)
178    vis.write(output_dir)
179