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, 164f7f97a7SJohn Snow Generic, 174f7f97a7SJohn Snow Iterable, 189db27346SJohn Snow List, 199db27346SJohn Snow Optional, 204f7f97a7SJohn Snow Tuple, 214f7f97a7SJohn 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] 594f7f97a7SJohn SnowJSONValue = Union[_Value, 'Annotated[_Value]'] 609db27346SJohn Snow 619db27346SJohn Snow 624f7f97a7SJohn Snow_ValueT = TypeVar('_ValueT', bound=_Value) 634f7f97a7SJohn Snow 644f7f97a7SJohn Snow 654f7f97a7SJohn Snowclass Annotated(Generic[_ValueT]): 664f7f97a7SJohn Snow """ 674f7f97a7SJohn Snow Annotated generally contains a SchemaInfo-like type (as a dict), 684f7f97a7SJohn Snow But it also used to wrap comments/ifconds around scalar leaf values, 694f7f97a7SJohn Snow for the benefit of features and enums. 704f7f97a7SJohn Snow """ 714f7f97a7SJohn Snow # TODO: Remove after Python 3.7 adds @dataclass: 724f7f97a7SJohn Snow # pylint: disable=too-few-public-methods 734f7f97a7SJohn Snow def __init__(self, value: _ValueT, ifcond: Iterable[str], 744f7f97a7SJohn Snow comment: Optional[str] = None): 754f7f97a7SJohn Snow self.value = value 764f7f97a7SJohn Snow self.comment: Optional[str] = comment 774f7f97a7SJohn 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 854f7f97a7SJohn 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 = '' 934f7f97a7SJohn Snow if obj.comment: 94*c0e8d9f3SJohn Snow ret += indent(level) + f"/* {obj.comment} */\n" 954f7f97a7SJohn Snow if obj.ifcond: 964f7f97a7SJohn Snow ret += gen_if(obj.ifcond) 974f7f97a7SJohn Snow ret += _tree_to_qlit(obj.value, level) 984f7f97a7SJohn Snow if obj.ifcond: 994f7f97a7SJohn 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) 105*c0e8d9f3SJohn Snow 106*c0e8d9f3SJohn Snow # Scalars: 107fb0bc835SMarkus Armbruster if obj is None: 1087d0f982bSMarc-André Lureau ret += 'QLIT_QNULL' 109fb0bc835SMarkus Armbruster elif isinstance(obj, str): 110*c0e8d9f3SJohn Snow ret += f"QLIT_QSTR({to_c_string(obj)})" 111*c0e8d9f3SJohn Snow elif isinstance(obj, bool): 112*c0e8d9f3SJohn Snow ret += f"QLIT_QBOOL({str(obj).lower()})" 113*c0e8d9f3SJohn Snow 114*c0e8d9f3SJohn Snow # Non-scalars: 115fb0bc835SMarkus Armbruster elif isinstance(obj, list): 1167d0f982bSMarc-André Lureau ret += 'QLIT_QLIST(((QLitObject[]) {\n' 117*c0e8d9f3SJohn Snow for value in obj: 118*c0e8d9f3SJohn Snow ret += _tree_to_qlit(value, level + 1).strip('\n') + '\n' 119*c0e8d9f3SJohn Snow ret += indent(level + 1) + '{}\n' 1207d0f982bSMarc-André Lureau ret += indent(level) + '}))' 121fb0bc835SMarkus Armbruster elif isinstance(obj, dict): 1227d0f982bSMarc-André Lureau ret += 'QLIT_QDICT(((QLitDictEntry[]) {\n' 123*c0e8d9f3SJohn Snow for key, value in sorted(obj.items()): 124*c0e8d9f3SJohn Snow ret += indent(level + 1) + "{{ {:s}, {:s} }},\n".format( 125*c0e8d9f3SJohn Snow to_c_string(key), 126*c0e8d9f3SJohn Snow _tree_to_qlit(value, level + 1, dict_value=True) 127*c0e8d9f3SJohn Snow ) 128*c0e8d9f3SJohn Snow ret += indent(level + 1) + '{}\n' 1297d0f982bSMarc-André Lureau ret += indent(level) + '}))' 130fb0bc835SMarkus Armbruster else: 1312a6c161bSJohn Snow raise NotImplementedError( 1322a6c161bSJohn Snow f"type '{type(obj).__name__}' not implemented" 1332a6c161bSJohn Snow ) 134*c0e8d9f3SJohn Snow 13540bb1376SMarc-André Lureau if level > 0: 13640bb1376SMarc-André Lureau ret += ',' 137fb0bc835SMarkus Armbruster return ret 138fb0bc835SMarkus Armbruster 139fb0bc835SMarkus Armbruster 140fb0bc835SMarkus Armbrusterdef to_c_string(string): 141fb0bc835SMarkus Armbruster return '"' + string.replace('\\', r'\\').replace('"', r'\"') + '"' 142fb0bc835SMarkus Armbruster 143fb0bc835SMarkus Armbruster 14471b3f045SMarkus Armbrusterclass QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor): 145fb0bc835SMarkus Armbruster 14671b3f045SMarkus Armbruster def __init__(self, prefix, unmask): 1472cae67bcSMarkus Armbruster super().__init__( 1482cae67bcSMarkus Armbruster prefix, 'qapi-introspect', 14971b3f045SMarkus Armbruster ' * QAPI/QMP schema introspection', __doc__) 15071b3f045SMarkus Armbruster self._unmask = unmask 15171b3f045SMarkus Armbruster self._schema = None 1522e8a843dSMarkus Armbruster self._trees = [] 153fb0bc835SMarkus Armbruster self._used_types = [] 154fb0bc835SMarkus Armbruster self._name_map = {} 15571b3f045SMarkus Armbruster self._genc.add(mcgen(''' 15671b3f045SMarkus Armbruster#include "qemu/osdep.h" 157eb815e24SMarkus Armbruster#include "%(prefix)sqapi-introspect.h" 15871b3f045SMarkus Armbruster 15971b3f045SMarkus Armbruster''', 16071b3f045SMarkus Armbruster prefix=prefix)) 16171b3f045SMarkus Armbruster 16271b3f045SMarkus Armbruster def visit_begin(self, schema): 16371b3f045SMarkus Armbruster self._schema = schema 164fb0bc835SMarkus Armbruster 165fb0bc835SMarkus Armbruster def visit_end(self): 166fb0bc835SMarkus Armbruster # visit the types that are actually used 167fb0bc835SMarkus Armbruster for typ in self._used_types: 168fb0bc835SMarkus Armbruster typ.visit(self) 169fb0bc835SMarkus Armbruster # generate C 1707d0f982bSMarc-André Lureau name = c_name(self._prefix, protect=False) + 'qmp_schema_qlit' 17171b3f045SMarkus Armbruster self._genh.add(mcgen(''' 1727d0f982bSMarc-André Lureau#include "qapi/qmp/qlit.h" 1737d0f982bSMarc-André Lureau 1747d0f982bSMarc-André Lureauextern const QLitObject %(c_name)s; 175fb0bc835SMarkus Armbruster''', 17671b3f045SMarkus Armbruster c_name=c_name(name))) 17771b3f045SMarkus Armbruster self._genc.add(mcgen(''' 1787d0f982bSMarc-André Lureauconst QLitObject %(c_name)s = %(c_string)s; 179fb0bc835SMarkus Armbruster''', 180fb0bc835SMarkus Armbruster c_name=c_name(name), 1812e8a843dSMarkus Armbruster c_string=_tree_to_qlit(self._trees))) 182fb0bc835SMarkus Armbruster self._schema = None 1832e8a843dSMarkus Armbruster self._trees = [] 18471b3f045SMarkus Armbruster self._used_types = [] 18571b3f045SMarkus Armbruster self._name_map = {} 186fb0bc835SMarkus Armbruster 187fb0bc835SMarkus Armbruster def visit_needed(self, entity): 188fb0bc835SMarkus Armbruster # Ignore types on first pass; visit_end() will pick up used types 189fb0bc835SMarkus Armbruster return not isinstance(entity, QAPISchemaType) 190fb0bc835SMarkus Armbruster 191fb0bc835SMarkus Armbruster def _name(self, name): 192fb0bc835SMarkus Armbruster if self._unmask: 193fb0bc835SMarkus Armbruster return name 194fb0bc835SMarkus Armbruster if name not in self._name_map: 195fb0bc835SMarkus Armbruster self._name_map[name] = '%d' % len(self._name_map) 196fb0bc835SMarkus Armbruster return self._name_map[name] 197fb0bc835SMarkus Armbruster 198fb0bc835SMarkus Armbruster def _use_type(self, typ): 1996b67bcacSJohn Snow assert self._schema is not None 2006b67bcacSJohn Snow 201fb0bc835SMarkus Armbruster # Map the various integer types to plain int 202fb0bc835SMarkus Armbruster if typ.json_type() == 'int': 203fb0bc835SMarkus Armbruster typ = self._schema.lookup_type('int') 204fb0bc835SMarkus Armbruster elif (isinstance(typ, QAPISchemaArrayType) and 205fb0bc835SMarkus Armbruster typ.element_type.json_type() == 'int'): 206fb0bc835SMarkus Armbruster typ = self._schema.lookup_type('intList') 207fb0bc835SMarkus Armbruster # Add type to work queue if new 208fb0bc835SMarkus Armbruster if typ not in self._used_types: 209fb0bc835SMarkus Armbruster self._used_types.append(typ) 210fb0bc835SMarkus Armbruster # Clients should examine commands and events, not types. Hide 2111aa806ccSEric Blake # type names as integers to reduce the temptation. Also, it 2121aa806ccSEric Blake # saves a few characters on the wire. 213fb0bc835SMarkus Armbruster if isinstance(typ, QAPISchemaBuiltinType): 214fb0bc835SMarkus Armbruster return typ.name 215fb0bc835SMarkus Armbruster if isinstance(typ, QAPISchemaArrayType): 216fb0bc835SMarkus Armbruster return '[' + self._use_type(typ.element_type) + ']' 217fb0bc835SMarkus Armbruster return self._name(typ.name) 218fb0bc835SMarkus Armbruster 21984bece7dSJohn Snow @staticmethod 22084bece7dSJohn Snow def _gen_features(features): 2214f7f97a7SJohn Snow return [Annotated(f.name, f.ifcond) for f in features] 22284bece7dSJohn Snow 2232e8a843dSMarkus Armbruster def _gen_tree(self, name, mtype, obj, ifcond, features): 2245f50cedeSJohn Snow comment: Optional[str] = None 225fb0bc835SMarkus Armbruster if mtype not in ('command', 'event', 'builtin', 'array'): 2268c643361SEric Blake if not self._unmask: 2278c643361SEric Blake # Output a comment to make it easy to map masked names 2288c643361SEric Blake # back to the source when reading the generated output. 2295f50cedeSJohn Snow comment = f'"{self._name(name)}" = {name}' 230fb0bc835SMarkus Armbruster name = self._name(name) 231fb0bc835SMarkus Armbruster obj['name'] = name 232fb0bc835SMarkus Armbruster obj['meta-type'] = mtype 23384bece7dSJohn Snow if features: 23484bece7dSJohn Snow obj['features'] = self._gen_features(features) 2354f7f97a7SJohn Snow self._trees.append(Annotated(obj, ifcond, comment)) 236fb0bc835SMarkus Armbruster 237fb0bc835SMarkus Armbruster def _gen_member(self, member): 23824cfd6adSMarkus Armbruster obj = {'name': member.name, 'type': self._use_type(member.type)} 239fb0bc835SMarkus Armbruster if member.optional: 24024cfd6adSMarkus Armbruster obj['default'] = None 24184bece7dSJohn Snow if member.features: 24284bece7dSJohn Snow obj['features'] = self._gen_features(member.features) 2434f7f97a7SJohn Snow return Annotated(obj, member.ifcond) 244fb0bc835SMarkus Armbruster 245fb0bc835SMarkus Armbruster def _gen_variants(self, tag_name, variants): 246fb0bc835SMarkus Armbruster return {'tag': tag_name, 247fb0bc835SMarkus Armbruster 'variants': [self._gen_variant(v) for v in variants]} 248fb0bc835SMarkus Armbruster 249fb0bc835SMarkus Armbruster def _gen_variant(self, variant): 25024cfd6adSMarkus Armbruster obj = {'case': variant.name, 'type': self._use_type(variant.type)} 2514f7f97a7SJohn Snow return Annotated(obj, variant.ifcond) 252fb0bc835SMarkus Armbruster 253fb0bc835SMarkus Armbruster def visit_builtin_type(self, name, info, json_type): 2542e8a843dSMarkus Armbruster self._gen_tree(name, 'builtin', {'json-type': json_type}, [], None) 255fb0bc835SMarkus Armbruster 256013b4efcSMarkus Armbruster def visit_enum_type(self, name, info, ifcond, features, members, prefix): 2574f7f97a7SJohn Snow self._gen_tree( 2584f7f97a7SJohn Snow name, 'enum', 2594f7f97a7SJohn Snow {'values': [Annotated(m.name, m.ifcond) for m in members]}, 2604f7f97a7SJohn Snow ifcond, features 2614f7f97a7SJohn Snow ) 262fb0bc835SMarkus Armbruster 263fbf09a2fSMarc-André Lureau def visit_array_type(self, name, info, ifcond, element_type): 264fb0bc835SMarkus Armbruster element = self._use_type(element_type) 2652e8a843dSMarkus Armbruster self._gen_tree('[' + element + ']', 'array', {'element-type': element}, 266013b4efcSMarkus Armbruster ifcond, None) 267fb0bc835SMarkus Armbruster 2687b3bc9e2SMarkus Armbruster def visit_object_type_flat(self, name, info, ifcond, features, 2697b3bc9e2SMarkus Armbruster members, variants): 270fb0bc835SMarkus Armbruster obj = {'members': [self._gen_member(m) for m in members]} 271fb0bc835SMarkus Armbruster if variants: 272fb0bc835SMarkus Armbruster obj.update(self._gen_variants(variants.tag_member.name, 273fb0bc835SMarkus Armbruster variants.variants)) 2746a8c0b51SKevin Wolf 2752e8a843dSMarkus Armbruster self._gen_tree(name, 'object', obj, ifcond, features) 276fb0bc835SMarkus Armbruster 277013b4efcSMarkus Armbruster def visit_alternate_type(self, name, info, ifcond, features, variants): 2784f7f97a7SJohn Snow self._gen_tree( 2794f7f97a7SJohn Snow name, 'alternate', 2804f7f97a7SJohn Snow {'members': [Annotated({'type': self._use_type(m.type)}, 2814f7f97a7SJohn Snow m.ifcond) 282013b4efcSMarkus Armbruster for m in variants.variants]}, 2834f7f97a7SJohn Snow ifcond, features 2844f7f97a7SJohn Snow ) 285fb0bc835SMarkus Armbruster 2867b3bc9e2SMarkus Armbruster def visit_command(self, name, info, ifcond, features, 2877b3bc9e2SMarkus Armbruster arg_type, ret_type, gen, success_response, boxed, 28804f22362SKevin Wolf allow_oob, allow_preconfig, coroutine): 2896b67bcacSJohn Snow assert self._schema is not None 2906b67bcacSJohn Snow 291fb0bc835SMarkus Armbruster arg_type = arg_type or self._schema.the_empty_object_type 292fb0bc835SMarkus Armbruster ret_type = ret_type or self._schema.the_empty_object_type 29325b1ef31SMarkus Armbruster obj = {'arg-type': self._use_type(arg_type), 29425b1ef31SMarkus Armbruster 'ret-type': self._use_type(ret_type)} 29525b1ef31SMarkus Armbruster if allow_oob: 29625b1ef31SMarkus Armbruster obj['allow-oob'] = allow_oob 2972e8a843dSMarkus Armbruster self._gen_tree(name, 'command', obj, ifcond, features) 29823394b4cSPeter Krempa 299013b4efcSMarkus Armbruster def visit_event(self, name, info, ifcond, features, arg_type, boxed): 3006b67bcacSJohn Snow assert self._schema is not None 301fb0bc835SMarkus Armbruster arg_type = arg_type or self._schema.the_empty_object_type 3022e8a843dSMarkus Armbruster self._gen_tree(name, 'event', {'arg-type': self._use_type(arg_type)}, 303013b4efcSMarkus Armbruster ifcond, features) 304fb0bc835SMarkus Armbruster 305fb0bc835SMarkus Armbruster 306fb0bc835SMarkus Armbrusterdef gen_introspect(schema, output_dir, prefix, opt_unmask): 307fb0bc835SMarkus Armbruster vis = QAPISchemaGenIntrospectVisitor(prefix, opt_unmask) 308fb0bc835SMarkus Armbruster schema.visit(vis) 30971b3f045SMarkus Armbruster vis.write(output_dir) 310