1fb0bc835SMarkus Armbruster""" 2fb0bc835SMarkus ArmbrusterQAPI introspection generator 3fb0bc835SMarkus Armbruster 4fb0bc835SMarkus ArmbrusterCopyright (C) 2015-2018 Red Hat, Inc. 5fb0bc835SMarkus Armbruster 6fb0bc835SMarkus ArmbrusterAuthors: 7fb0bc835SMarkus Armbruster Markus Armbruster <armbru@redhat.com> 8fb0bc835SMarkus Armbruster 9fb0bc835SMarkus ArmbrusterThis work is licensed under the terms of the GNU GPL, version 2. 10fb0bc835SMarkus ArmbrusterSee the COPYING file in the top-level directory. 11fb0bc835SMarkus Armbruster""" 12fb0bc835SMarkus Armbruster 139db27346SJohn Snowfrom typing import ( 149db27346SJohn Snow Any, 159db27346SJohn Snow Dict, 16*4f7f97a7SJohn Snow Generic, 17*4f7f97a7SJohn Snow Iterable, 189db27346SJohn Snow List, 199db27346SJohn Snow Optional, 20*4f7f97a7SJohn Snow Tuple, 21*4f7f97a7SJohn Snow TypeVar, 229db27346SJohn Snow Union, 239db27346SJohn Snow) 245f50cedeSJohn Snow 255af8263dSJohn Snowfrom .common import ( 265af8263dSJohn Snow c_name, 275af8263dSJohn Snow gen_endif, 285af8263dSJohn Snow gen_if, 295af8263dSJohn Snow mcgen, 305af8263dSJohn Snow) 317137a960SJohn Snowfrom .gen import QAPISchemaMonolithicCVisitor 3267fea575SJohn Snowfrom .schema import ( 3367fea575SJohn Snow QAPISchemaArrayType, 3467fea575SJohn Snow QAPISchemaBuiltinType, 3567fea575SJohn Snow QAPISchemaType, 3667fea575SJohn Snow) 37fb0bc835SMarkus Armbruster 38fb0bc835SMarkus Armbruster 399db27346SJohn Snow# This module constructs a tree data structure that is used to 409db27346SJohn Snow# generate the introspection information for QEMU. It is shaped 419db27346SJohn Snow# like a JSON value. 429db27346SJohn Snow# 439db27346SJohn Snow# A complexity over JSON is that our values may or may not be annotated. 449db27346SJohn Snow# 459db27346SJohn Snow# Un-annotated values may be: 469db27346SJohn Snow# Scalar: str, bool, None. 479db27346SJohn Snow# Non-scalar: List, Dict 489db27346SJohn Snow# _value = Union[str, bool, None, Dict[str, JSONValue], List[JSONValue]] 499db27346SJohn Snow# 509db27346SJohn Snow# With optional annotations, the type of all values is: 519db27346SJohn Snow# JSONValue = Union[_Value, Annotated[_Value]] 529db27346SJohn Snow# 539db27346SJohn Snow# Sadly, mypy does not support recursive types; so the _Stub alias is used to 549db27346SJohn Snow# mark the imprecision in the type model where we'd otherwise use JSONValue. 559db27346SJohn Snow_Stub = Any 569db27346SJohn Snow_Scalar = Union[str, bool, None] 579db27346SJohn Snow_NonScalar = Union[Dict[str, _Stub], List[_Stub]] 589db27346SJohn Snow_Value = Union[_Scalar, _NonScalar] 59*4f7f97a7SJohn SnowJSONValue = Union[_Value, 'Annotated[_Value]'] 609db27346SJohn Snow 619db27346SJohn Snow 62*4f7f97a7SJohn Snow_ValueT = TypeVar('_ValueT', bound=_Value) 63*4f7f97a7SJohn Snow 64*4f7f97a7SJohn Snow 65*4f7f97a7SJohn Snowclass Annotated(Generic[_ValueT]): 66*4f7f97a7SJohn Snow """ 67*4f7f97a7SJohn Snow Annotated generally contains a SchemaInfo-like type (as a dict), 68*4f7f97a7SJohn Snow But it also used to wrap comments/ifconds around scalar leaf values, 69*4f7f97a7SJohn Snow for the benefit of features and enums. 70*4f7f97a7SJohn Snow """ 71*4f7f97a7SJohn Snow # TODO: Remove after Python 3.7 adds @dataclass: 72*4f7f97a7SJohn Snow # pylint: disable=too-few-public-methods 73*4f7f97a7SJohn Snow def __init__(self, value: _ValueT, ifcond: Iterable[str], 74*4f7f97a7SJohn Snow comment: Optional[str] = None): 75*4f7f97a7SJohn Snow self.value = value 76*4f7f97a7SJohn Snow self.comment: Optional[str] = comment 77*4f7f97a7SJohn Snow self.ifcond: Tuple[str, ...] = tuple(ifcond) 7824cfd6adSMarkus Armbruster 7924cfd6adSMarkus Armbruster 8005556960SJohn Snowdef _tree_to_qlit(obj, level=0, dict_value=False): 817d0f982bSMarc-André Lureau 827d0f982bSMarc-André Lureau def indent(level): 837d0f982bSMarc-André Lureau return level * 4 * ' ' 847d0f982bSMarc-André Lureau 85*4f7f97a7SJohn Snow if isinstance(obj, Annotated): 8605556960SJohn Snow # NB: _tree_to_qlit is called recursively on the values of a 8705556960SJohn Snow # key:value pair; those values can't be decorated with 8805556960SJohn Snow # comments or conditionals. 8905556960SJohn Snow msg = "dict values cannot have attached comments or if-conditionals." 9005556960SJohn Snow assert not dict_value, msg 9105556960SJohn Snow 928c643361SEric Blake ret = '' 93*4f7f97a7SJohn Snow if obj.comment: 94*4f7f97a7SJohn Snow ret += indent(level) + '/* %s */\n' % obj.comment 95*4f7f97a7SJohn Snow if obj.ifcond: 96*4f7f97a7SJohn Snow ret += gen_if(obj.ifcond) 97*4f7f97a7SJohn Snow ret += _tree_to_qlit(obj.value, level) 98*4f7f97a7SJohn Snow if obj.ifcond: 99*4f7f97a7SJohn Snow ret += '\n' + gen_endif(obj.ifcond) 100d626b6c1SMarc-André Lureau return ret 101d626b6c1SMarc-André Lureau 1027d0f982bSMarc-André Lureau ret = '' 10305556960SJohn Snow if not dict_value: 1047d0f982bSMarc-André Lureau ret += indent(level) 105fb0bc835SMarkus Armbruster if obj is None: 1067d0f982bSMarc-André Lureau ret += 'QLIT_QNULL' 107fb0bc835SMarkus Armbruster elif isinstance(obj, str): 1087d0f982bSMarc-André Lureau ret += 'QLIT_QSTR(' + to_c_string(obj) + ')' 109fb0bc835SMarkus Armbruster elif isinstance(obj, list): 1102e8a843dSMarkus Armbruster elts = [_tree_to_qlit(elt, level + 1).strip('\n') 111fb0bc835SMarkus Armbruster for elt in obj] 1127d0f982bSMarc-André Lureau elts.append(indent(level + 1) + "{}") 1137d0f982bSMarc-André Lureau ret += 'QLIT_QLIST(((QLitObject[]) {\n' 11440bb1376SMarc-André Lureau ret += '\n'.join(elts) + '\n' 1157d0f982bSMarc-André Lureau ret += indent(level) + '}))' 116fb0bc835SMarkus Armbruster elif isinstance(obj, dict): 1177d0f982bSMarc-André Lureau elts = [] 1187d0f982bSMarc-André Lureau for key, value in sorted(obj.items()): 1197d0f982bSMarc-André Lureau elts.append(indent(level + 1) + '{ %s, %s }' % 1202e8a843dSMarkus Armbruster (to_c_string(key), 1212e8a843dSMarkus Armbruster _tree_to_qlit(value, level + 1, True))) 1227d0f982bSMarc-André Lureau elts.append(indent(level + 1) + '{}') 1237d0f982bSMarc-André Lureau ret += 'QLIT_QDICT(((QLitDictEntry[]) {\n' 1247d0f982bSMarc-André Lureau ret += ',\n'.join(elts) + '\n' 1257d0f982bSMarc-André Lureau ret += indent(level) + '}))' 126876c6751SPeter Xu elif isinstance(obj, bool): 127876c6751SPeter Xu ret += 'QLIT_QBOOL(%s)' % ('true' if obj else 'false') 128fb0bc835SMarkus Armbruster else: 129fb0bc835SMarkus Armbruster assert False # not implemented 13040bb1376SMarc-André Lureau if level > 0: 13140bb1376SMarc-André Lureau ret += ',' 132fb0bc835SMarkus Armbruster return ret 133fb0bc835SMarkus Armbruster 134fb0bc835SMarkus Armbruster 135fb0bc835SMarkus Armbrusterdef to_c_string(string): 136fb0bc835SMarkus Armbruster return '"' + string.replace('\\', r'\\').replace('"', r'\"') + '"' 137fb0bc835SMarkus Armbruster 138fb0bc835SMarkus Armbruster 13971b3f045SMarkus Armbrusterclass QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor): 140fb0bc835SMarkus Armbruster 14171b3f045SMarkus Armbruster def __init__(self, prefix, unmask): 1422cae67bcSMarkus Armbruster super().__init__( 1432cae67bcSMarkus Armbruster prefix, 'qapi-introspect', 14471b3f045SMarkus Armbruster ' * QAPI/QMP schema introspection', __doc__) 14571b3f045SMarkus Armbruster self._unmask = unmask 14671b3f045SMarkus Armbruster self._schema = None 1472e8a843dSMarkus Armbruster self._trees = [] 148fb0bc835SMarkus Armbruster self._used_types = [] 149fb0bc835SMarkus Armbruster self._name_map = {} 15071b3f045SMarkus Armbruster self._genc.add(mcgen(''' 15171b3f045SMarkus Armbruster#include "qemu/osdep.h" 152eb815e24SMarkus Armbruster#include "%(prefix)sqapi-introspect.h" 15371b3f045SMarkus Armbruster 15471b3f045SMarkus Armbruster''', 15571b3f045SMarkus Armbruster prefix=prefix)) 15671b3f045SMarkus Armbruster 15771b3f045SMarkus Armbruster def visit_begin(self, schema): 15871b3f045SMarkus Armbruster self._schema = schema 159fb0bc835SMarkus Armbruster 160fb0bc835SMarkus Armbruster def visit_end(self): 161fb0bc835SMarkus Armbruster # visit the types that are actually used 162fb0bc835SMarkus Armbruster for typ in self._used_types: 163fb0bc835SMarkus Armbruster typ.visit(self) 164fb0bc835SMarkus Armbruster # generate C 1657d0f982bSMarc-André Lureau name = c_name(self._prefix, protect=False) + 'qmp_schema_qlit' 16671b3f045SMarkus Armbruster self._genh.add(mcgen(''' 1677d0f982bSMarc-André Lureau#include "qapi/qmp/qlit.h" 1687d0f982bSMarc-André Lureau 1697d0f982bSMarc-André Lureauextern const QLitObject %(c_name)s; 170fb0bc835SMarkus Armbruster''', 17171b3f045SMarkus Armbruster c_name=c_name(name))) 17271b3f045SMarkus Armbruster self._genc.add(mcgen(''' 1737d0f982bSMarc-André Lureauconst QLitObject %(c_name)s = %(c_string)s; 174fb0bc835SMarkus Armbruster''', 175fb0bc835SMarkus Armbruster c_name=c_name(name), 1762e8a843dSMarkus Armbruster c_string=_tree_to_qlit(self._trees))) 177fb0bc835SMarkus Armbruster self._schema = None 1782e8a843dSMarkus Armbruster self._trees = [] 17971b3f045SMarkus Armbruster self._used_types = [] 18071b3f045SMarkus Armbruster self._name_map = {} 181fb0bc835SMarkus Armbruster 182fb0bc835SMarkus Armbruster def visit_needed(self, entity): 183fb0bc835SMarkus Armbruster # Ignore types on first pass; visit_end() will pick up used types 184fb0bc835SMarkus Armbruster return not isinstance(entity, QAPISchemaType) 185fb0bc835SMarkus Armbruster 186fb0bc835SMarkus Armbruster def _name(self, name): 187fb0bc835SMarkus Armbruster if self._unmask: 188fb0bc835SMarkus Armbruster return name 189fb0bc835SMarkus Armbruster if name not in self._name_map: 190fb0bc835SMarkus Armbruster self._name_map[name] = '%d' % len(self._name_map) 191fb0bc835SMarkus Armbruster return self._name_map[name] 192fb0bc835SMarkus Armbruster 193fb0bc835SMarkus Armbruster def _use_type(self, typ): 1946b67bcacSJohn Snow assert self._schema is not None 1956b67bcacSJohn Snow 196fb0bc835SMarkus Armbruster # Map the various integer types to plain int 197fb0bc835SMarkus Armbruster if typ.json_type() == 'int': 198fb0bc835SMarkus Armbruster typ = self._schema.lookup_type('int') 199fb0bc835SMarkus Armbruster elif (isinstance(typ, QAPISchemaArrayType) and 200fb0bc835SMarkus Armbruster typ.element_type.json_type() == 'int'): 201fb0bc835SMarkus Armbruster typ = self._schema.lookup_type('intList') 202fb0bc835SMarkus Armbruster # Add type to work queue if new 203fb0bc835SMarkus Armbruster if typ not in self._used_types: 204fb0bc835SMarkus Armbruster self._used_types.append(typ) 205fb0bc835SMarkus Armbruster # Clients should examine commands and events, not types. Hide 2061aa806ccSEric Blake # type names as integers to reduce the temptation. Also, it 2071aa806ccSEric Blake # saves a few characters on the wire. 208fb0bc835SMarkus Armbruster if isinstance(typ, QAPISchemaBuiltinType): 209fb0bc835SMarkus Armbruster return typ.name 210fb0bc835SMarkus Armbruster if isinstance(typ, QAPISchemaArrayType): 211fb0bc835SMarkus Armbruster return '[' + self._use_type(typ.element_type) + ']' 212fb0bc835SMarkus Armbruster return self._name(typ.name) 213fb0bc835SMarkus Armbruster 21484bece7dSJohn Snow @staticmethod 21584bece7dSJohn Snow def _gen_features(features): 216*4f7f97a7SJohn Snow return [Annotated(f.name, f.ifcond) for f in features] 21784bece7dSJohn Snow 2182e8a843dSMarkus Armbruster def _gen_tree(self, name, mtype, obj, ifcond, features): 2195f50cedeSJohn Snow comment: Optional[str] = None 220fb0bc835SMarkus Armbruster if mtype not in ('command', 'event', 'builtin', 'array'): 2218c643361SEric Blake if not self._unmask: 2228c643361SEric Blake # Output a comment to make it easy to map masked names 2238c643361SEric Blake # back to the source when reading the generated output. 2245f50cedeSJohn Snow comment = f'"{self._name(name)}" = {name}' 225fb0bc835SMarkus Armbruster name = self._name(name) 226fb0bc835SMarkus Armbruster obj['name'] = name 227fb0bc835SMarkus Armbruster obj['meta-type'] = mtype 22884bece7dSJohn Snow if features: 22984bece7dSJohn Snow obj['features'] = self._gen_features(features) 230*4f7f97a7SJohn Snow self._trees.append(Annotated(obj, ifcond, comment)) 231fb0bc835SMarkus Armbruster 232fb0bc835SMarkus Armbruster def _gen_member(self, member): 23324cfd6adSMarkus Armbruster obj = {'name': member.name, 'type': self._use_type(member.type)} 234fb0bc835SMarkus Armbruster if member.optional: 23524cfd6adSMarkus Armbruster obj['default'] = None 23684bece7dSJohn Snow if member.features: 23784bece7dSJohn Snow obj['features'] = self._gen_features(member.features) 238*4f7f97a7SJohn Snow return Annotated(obj, member.ifcond) 239fb0bc835SMarkus Armbruster 240fb0bc835SMarkus Armbruster def _gen_variants(self, tag_name, variants): 241fb0bc835SMarkus Armbruster return {'tag': tag_name, 242fb0bc835SMarkus Armbruster 'variants': [self._gen_variant(v) for v in variants]} 243fb0bc835SMarkus Armbruster 244fb0bc835SMarkus Armbruster def _gen_variant(self, variant): 24524cfd6adSMarkus Armbruster obj = {'case': variant.name, 'type': self._use_type(variant.type)} 246*4f7f97a7SJohn Snow return Annotated(obj, variant.ifcond) 247fb0bc835SMarkus Armbruster 248fb0bc835SMarkus Armbruster def visit_builtin_type(self, name, info, json_type): 2492e8a843dSMarkus Armbruster self._gen_tree(name, 'builtin', {'json-type': json_type}, [], None) 250fb0bc835SMarkus Armbruster 251013b4efcSMarkus Armbruster def visit_enum_type(self, name, info, ifcond, features, members, prefix): 252*4f7f97a7SJohn Snow self._gen_tree( 253*4f7f97a7SJohn Snow name, 'enum', 254*4f7f97a7SJohn Snow {'values': [Annotated(m.name, m.ifcond) for m in members]}, 255*4f7f97a7SJohn Snow ifcond, features 256*4f7f97a7SJohn Snow ) 257fb0bc835SMarkus Armbruster 258fbf09a2fSMarc-André Lureau def visit_array_type(self, name, info, ifcond, element_type): 259fb0bc835SMarkus Armbruster element = self._use_type(element_type) 2602e8a843dSMarkus Armbruster self._gen_tree('[' + element + ']', 'array', {'element-type': element}, 261013b4efcSMarkus Armbruster ifcond, None) 262fb0bc835SMarkus Armbruster 2637b3bc9e2SMarkus Armbruster def visit_object_type_flat(self, name, info, ifcond, features, 2647b3bc9e2SMarkus Armbruster members, variants): 265fb0bc835SMarkus Armbruster obj = {'members': [self._gen_member(m) for m in members]} 266fb0bc835SMarkus Armbruster if variants: 267fb0bc835SMarkus Armbruster obj.update(self._gen_variants(variants.tag_member.name, 268fb0bc835SMarkus Armbruster variants.variants)) 2696a8c0b51SKevin Wolf 2702e8a843dSMarkus Armbruster self._gen_tree(name, 'object', obj, ifcond, features) 271fb0bc835SMarkus Armbruster 272013b4efcSMarkus Armbruster def visit_alternate_type(self, name, info, ifcond, features, variants): 273*4f7f97a7SJohn Snow self._gen_tree( 274*4f7f97a7SJohn Snow name, 'alternate', 275*4f7f97a7SJohn Snow {'members': [Annotated({'type': self._use_type(m.type)}, 276*4f7f97a7SJohn Snow m.ifcond) 277013b4efcSMarkus Armbruster for m in variants.variants]}, 278*4f7f97a7SJohn Snow ifcond, features 279*4f7f97a7SJohn Snow ) 280fb0bc835SMarkus Armbruster 2817b3bc9e2SMarkus Armbruster def visit_command(self, name, info, ifcond, features, 2827b3bc9e2SMarkus Armbruster arg_type, ret_type, gen, success_response, boxed, 28304f22362SKevin Wolf allow_oob, allow_preconfig, coroutine): 2846b67bcacSJohn Snow assert self._schema is not None 2856b67bcacSJohn Snow 286fb0bc835SMarkus Armbruster arg_type = arg_type or self._schema.the_empty_object_type 287fb0bc835SMarkus Armbruster ret_type = ret_type or self._schema.the_empty_object_type 28825b1ef31SMarkus Armbruster obj = {'arg-type': self._use_type(arg_type), 28925b1ef31SMarkus Armbruster 'ret-type': self._use_type(ret_type)} 29025b1ef31SMarkus Armbruster if allow_oob: 29125b1ef31SMarkus Armbruster obj['allow-oob'] = allow_oob 2922e8a843dSMarkus Armbruster self._gen_tree(name, 'command', obj, ifcond, features) 29323394b4cSPeter Krempa 294013b4efcSMarkus Armbruster def visit_event(self, name, info, ifcond, features, arg_type, boxed): 2956b67bcacSJohn Snow assert self._schema is not None 296fb0bc835SMarkus Armbruster arg_type = arg_type or self._schema.the_empty_object_type 2972e8a843dSMarkus Armbruster self._gen_tree(name, 'event', {'arg-type': self._use_type(arg_type)}, 298013b4efcSMarkus Armbruster ifcond, features) 299fb0bc835SMarkus Armbruster 300fb0bc835SMarkus Armbruster 301fb0bc835SMarkus Armbrusterdef gen_introspect(schema, output_dir, prefix, opt_unmask): 302fb0bc835SMarkus Armbruster vis = QAPISchemaGenIntrospectVisitor(prefix, opt_unmask) 303fb0bc835SMarkus Armbruster schema.visit(vis) 30471b3f045SMarkus Armbruster vis.write(output_dir) 305