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 typing import Optional 14 15from .common import ( 16 c_name, 17 gen_endif, 18 gen_if, 19 mcgen, 20) 21from .gen import QAPISchemaMonolithicCVisitor 22from .schema import ( 23 QAPISchemaArrayType, 24 QAPISchemaBuiltinType, 25 QAPISchemaType, 26) 27 28 29def _make_tree(obj, ifcond, comment=None): 30 extra = {} 31 if ifcond: 32 extra['if'] = ifcond 33 if comment: 34 extra['comment'] = comment 35 return (obj, extra) 36 37 38def _tree_to_qlit(obj, level=0, dict_value=False): 39 40 def indent(level): 41 return level * 4 * ' ' 42 43 if isinstance(obj, tuple): 44 ifobj, extra = obj 45 ifcond = extra.get('if') 46 comment = extra.get('comment') 47 48 # NB: _tree_to_qlit is called recursively on the values of a 49 # key:value pair; those values can't be decorated with 50 # comments or conditionals. 51 msg = "dict values cannot have attached comments or if-conditionals." 52 assert not dict_value, msg 53 54 ret = '' 55 if comment: 56 ret += indent(level) + '/* %s */\n' % comment 57 if ifcond: 58 ret += gen_if(ifcond) 59 ret += _tree_to_qlit(ifobj, level) 60 if ifcond: 61 ret += '\n' + gen_endif(ifcond) 62 return ret 63 64 ret = '' 65 if not dict_value: 66 ret += indent(level) 67 if obj is None: 68 ret += 'QLIT_QNULL' 69 elif isinstance(obj, str): 70 ret += 'QLIT_QSTR(' + to_c_string(obj) + ')' 71 elif isinstance(obj, list): 72 elts = [_tree_to_qlit(elt, level + 1).strip('\n') 73 for elt in obj] 74 elts.append(indent(level + 1) + "{}") 75 ret += 'QLIT_QLIST(((QLitObject[]) {\n' 76 ret += '\n'.join(elts) + '\n' 77 ret += indent(level) + '}))' 78 elif isinstance(obj, dict): 79 elts = [] 80 for key, value in sorted(obj.items()): 81 elts.append(indent(level + 1) + '{ %s, %s }' % 82 (to_c_string(key), 83 _tree_to_qlit(value, level + 1, True))) 84 elts.append(indent(level + 1) + '{}') 85 ret += 'QLIT_QDICT(((QLitDictEntry[]) {\n' 86 ret += ',\n'.join(elts) + '\n' 87 ret += indent(level) + '}))' 88 elif isinstance(obj, bool): 89 ret += 'QLIT_QBOOL(%s)' % ('true' if obj else 'false') 90 else: 91 assert False # not implemented 92 if level > 0: 93 ret += ',' 94 return ret 95 96 97def to_c_string(string): 98 return '"' + string.replace('\\', r'\\').replace('"', r'\"') + '"' 99 100 101class QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor): 102 103 def __init__(self, prefix, unmask): 104 super().__init__( 105 prefix, 'qapi-introspect', 106 ' * QAPI/QMP schema introspection', __doc__) 107 self._unmask = unmask 108 self._schema = None 109 self._trees = [] 110 self._used_types = [] 111 self._name_map = {} 112 self._genc.add(mcgen(''' 113#include "qemu/osdep.h" 114#include "%(prefix)sqapi-introspect.h" 115 116''', 117 prefix=prefix)) 118 119 def visit_begin(self, schema): 120 self._schema = schema 121 122 def visit_end(self): 123 # visit the types that are actually used 124 for typ in self._used_types: 125 typ.visit(self) 126 # generate C 127 name = c_name(self._prefix, protect=False) + 'qmp_schema_qlit' 128 self._genh.add(mcgen(''' 129#include "qapi/qmp/qlit.h" 130 131extern const QLitObject %(c_name)s; 132''', 133 c_name=c_name(name))) 134 self._genc.add(mcgen(''' 135const QLitObject %(c_name)s = %(c_string)s; 136''', 137 c_name=c_name(name), 138 c_string=_tree_to_qlit(self._trees))) 139 self._schema = None 140 self._trees = [] 141 self._used_types = [] 142 self._name_map = {} 143 144 def visit_needed(self, entity): 145 # Ignore types on first pass; visit_end() will pick up used types 146 return not isinstance(entity, QAPISchemaType) 147 148 def _name(self, name): 149 if self._unmask: 150 return name 151 if name not in self._name_map: 152 self._name_map[name] = '%d' % len(self._name_map) 153 return self._name_map[name] 154 155 def _use_type(self, typ): 156 assert self._schema is not None 157 158 # Map the various integer types to plain int 159 if typ.json_type() == 'int': 160 typ = self._schema.lookup_type('int') 161 elif (isinstance(typ, QAPISchemaArrayType) and 162 typ.element_type.json_type() == 'int'): 163 typ = self._schema.lookup_type('intList') 164 # Add type to work queue if new 165 if typ not in self._used_types: 166 self._used_types.append(typ) 167 # Clients should examine commands and events, not types. Hide 168 # type names as integers to reduce the temptation. Also, it 169 # saves a few characters on the wire. 170 if isinstance(typ, QAPISchemaBuiltinType): 171 return typ.name 172 if isinstance(typ, QAPISchemaArrayType): 173 return '[' + self._use_type(typ.element_type) + ']' 174 return self._name(typ.name) 175 176 @staticmethod 177 def _gen_features(features): 178 return [_make_tree(f.name, f.ifcond) for f in features] 179 180 def _gen_tree(self, name, mtype, obj, ifcond, features): 181 comment: Optional[str] = None 182 if mtype not in ('command', 'event', 'builtin', 'array'): 183 if not self._unmask: 184 # Output a comment to make it easy to map masked names 185 # back to the source when reading the generated output. 186 comment = f'"{self._name(name)}" = {name}' 187 name = self._name(name) 188 obj['name'] = name 189 obj['meta-type'] = mtype 190 if features: 191 obj['features'] = self._gen_features(features) 192 self._trees.append(_make_tree(obj, ifcond, comment)) 193 194 def _gen_member(self, member): 195 obj = {'name': member.name, 'type': self._use_type(member.type)} 196 if member.optional: 197 obj['default'] = None 198 if member.features: 199 obj['features'] = self._gen_features(member.features) 200 return _make_tree(obj, member.ifcond) 201 202 def _gen_variants(self, tag_name, variants): 203 return {'tag': tag_name, 204 'variants': [self._gen_variant(v) for v in variants]} 205 206 def _gen_variant(self, variant): 207 obj = {'case': variant.name, 'type': self._use_type(variant.type)} 208 return _make_tree(obj, variant.ifcond) 209 210 def visit_builtin_type(self, name, info, json_type): 211 self._gen_tree(name, 'builtin', {'json-type': json_type}, [], None) 212 213 def visit_enum_type(self, name, info, ifcond, features, members, prefix): 214 self._gen_tree(name, 'enum', 215 {'values': [_make_tree(m.name, m.ifcond, None) 216 for m in members]}, 217 ifcond, features) 218 219 def visit_array_type(self, name, info, ifcond, element_type): 220 element = self._use_type(element_type) 221 self._gen_tree('[' + element + ']', 'array', {'element-type': element}, 222 ifcond, None) 223 224 def visit_object_type_flat(self, name, info, ifcond, features, 225 members, variants): 226 obj = {'members': [self._gen_member(m) for m in members]} 227 if variants: 228 obj.update(self._gen_variants(variants.tag_member.name, 229 variants.variants)) 230 231 self._gen_tree(name, 'object', obj, ifcond, features) 232 233 def visit_alternate_type(self, name, info, ifcond, features, variants): 234 self._gen_tree(name, 'alternate', 235 {'members': [ 236 _make_tree({'type': self._use_type(m.type)}, 237 m.ifcond, None) 238 for m in variants.variants]}, 239 ifcond, features) 240 241 def visit_command(self, name, info, ifcond, features, 242 arg_type, ret_type, gen, success_response, boxed, 243 allow_oob, allow_preconfig, coroutine): 244 assert self._schema is not None 245 246 arg_type = arg_type or self._schema.the_empty_object_type 247 ret_type = ret_type or self._schema.the_empty_object_type 248 obj = {'arg-type': self._use_type(arg_type), 249 'ret-type': self._use_type(ret_type)} 250 if allow_oob: 251 obj['allow-oob'] = allow_oob 252 self._gen_tree(name, 'command', obj, ifcond, features) 253 254 def visit_event(self, name, info, ifcond, features, arg_type, boxed): 255 assert self._schema is not None 256 arg_type = arg_type or self._schema.the_empty_object_type 257 self._gen_tree(name, 'event', {'arg-type': self._use_type(arg_type)}, 258 ifcond, features) 259 260 261def gen_introspect(schema, output_dir, prefix, opt_unmask): 262 vis = QAPISchemaGenIntrospectVisitor(prefix, opt_unmask) 263 schema.visit(vis) 264 vis.write(output_dir) 265