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, 20*82b52f6bSJohn Snow Sequence, 214f7f97a7SJohn Snow Tuple, 224f7f97a7SJohn Snow TypeVar, 239db27346SJohn Snow Union, 249db27346SJohn Snow) 255f50cedeSJohn Snow 265af8263dSJohn Snowfrom .common import ( 275af8263dSJohn Snow c_name, 285af8263dSJohn Snow gen_endif, 295af8263dSJohn Snow gen_if, 305af8263dSJohn Snow mcgen, 315af8263dSJohn Snow) 327137a960SJohn Snowfrom .gen import QAPISchemaMonolithicCVisitor 3367fea575SJohn Snowfrom .schema import ( 34*82b52f6bSJohn Snow QAPISchema, 3567fea575SJohn Snow QAPISchemaArrayType, 3667fea575SJohn Snow QAPISchemaBuiltinType, 37*82b52f6bSJohn Snow QAPISchemaEntity, 38*82b52f6bSJohn Snow QAPISchemaEnumMember, 39*82b52f6bSJohn Snow QAPISchemaFeature, 40*82b52f6bSJohn Snow QAPISchemaObjectType, 41*82b52f6bSJohn Snow QAPISchemaObjectTypeMember, 4267fea575SJohn Snow QAPISchemaType, 43*82b52f6bSJohn Snow QAPISchemaVariant, 44*82b52f6bSJohn Snow QAPISchemaVariants, 4567fea575SJohn Snow) 46*82b52f6bSJohn Snowfrom .source import QAPISourceInfo 47fb0bc835SMarkus Armbruster 48fb0bc835SMarkus Armbruster 499db27346SJohn Snow# This module constructs a tree data structure that is used to 509db27346SJohn Snow# generate the introspection information for QEMU. It is shaped 519db27346SJohn Snow# like a JSON value. 529db27346SJohn Snow# 539db27346SJohn Snow# A complexity over JSON is that our values may or may not be annotated. 549db27346SJohn Snow# 559db27346SJohn Snow# Un-annotated values may be: 569db27346SJohn Snow# Scalar: str, bool, None. 579db27346SJohn Snow# Non-scalar: List, Dict 589db27346SJohn Snow# _value = Union[str, bool, None, Dict[str, JSONValue], List[JSONValue]] 599db27346SJohn Snow# 609db27346SJohn Snow# With optional annotations, the type of all values is: 619db27346SJohn Snow# JSONValue = Union[_Value, Annotated[_Value]] 629db27346SJohn Snow# 639db27346SJohn Snow# Sadly, mypy does not support recursive types; so the _Stub alias is used to 649db27346SJohn Snow# mark the imprecision in the type model where we'd otherwise use JSONValue. 659db27346SJohn Snow_Stub = Any 669db27346SJohn Snow_Scalar = Union[str, bool, None] 679db27346SJohn Snow_NonScalar = Union[Dict[str, _Stub], List[_Stub]] 689db27346SJohn Snow_Value = Union[_Scalar, _NonScalar] 694f7f97a7SJohn SnowJSONValue = Union[_Value, 'Annotated[_Value]'] 709db27346SJohn Snow 71*82b52f6bSJohn Snow# These types are based on structures defined in QEMU's schema, so we 72*82b52f6bSJohn Snow# lack precise types for them here. Python 3.6 does not offer 73*82b52f6bSJohn Snow# TypedDict constructs, so they are broadly typed here as simple 74*82b52f6bSJohn Snow# Python Dicts. 75*82b52f6bSJohn SnowSchemaInfo = Dict[str, object] 76*82b52f6bSJohn SnowSchemaInfoObject = Dict[str, object] 77*82b52f6bSJohn SnowSchemaInfoObjectVariant = Dict[str, object] 78*82b52f6bSJohn SnowSchemaInfoObjectMember = Dict[str, object] 79*82b52f6bSJohn SnowSchemaInfoCommand = Dict[str, object] 80*82b52f6bSJohn Snow 819db27346SJohn Snow 824f7f97a7SJohn Snow_ValueT = TypeVar('_ValueT', bound=_Value) 834f7f97a7SJohn Snow 844f7f97a7SJohn Snow 854f7f97a7SJohn Snowclass Annotated(Generic[_ValueT]): 864f7f97a7SJohn Snow """ 874f7f97a7SJohn Snow Annotated generally contains a SchemaInfo-like type (as a dict), 884f7f97a7SJohn Snow But it also used to wrap comments/ifconds around scalar leaf values, 894f7f97a7SJohn Snow for the benefit of features and enums. 904f7f97a7SJohn Snow """ 914f7f97a7SJohn Snow # TODO: Remove after Python 3.7 adds @dataclass: 924f7f97a7SJohn Snow # pylint: disable=too-few-public-methods 934f7f97a7SJohn Snow def __init__(self, value: _ValueT, ifcond: Iterable[str], 944f7f97a7SJohn Snow comment: Optional[str] = None): 954f7f97a7SJohn Snow self.value = value 964f7f97a7SJohn Snow self.comment: Optional[str] = comment 974f7f97a7SJohn Snow self.ifcond: Tuple[str, ...] = tuple(ifcond) 9824cfd6adSMarkus Armbruster 9924cfd6adSMarkus Armbruster 100*82b52f6bSJohn Snowdef _tree_to_qlit(obj: JSONValue, 101*82b52f6bSJohn Snow level: int = 0, 102*82b52f6bSJohn Snow dict_value: bool = False) -> str: 1037d0f982bSMarc-André Lureau 104*82b52f6bSJohn Snow def indent(level: int) -> str: 1057d0f982bSMarc-André Lureau return level * 4 * ' ' 1067d0f982bSMarc-André Lureau 1074f7f97a7SJohn Snow if isinstance(obj, Annotated): 10805556960SJohn Snow # NB: _tree_to_qlit is called recursively on the values of a 10905556960SJohn Snow # key:value pair; those values can't be decorated with 11005556960SJohn Snow # comments or conditionals. 11105556960SJohn Snow msg = "dict values cannot have attached comments or if-conditionals." 11205556960SJohn Snow assert not dict_value, msg 11305556960SJohn Snow 1148c643361SEric Blake ret = '' 1154f7f97a7SJohn Snow if obj.comment: 116c0e8d9f3SJohn Snow ret += indent(level) + f"/* {obj.comment} */\n" 1174f7f97a7SJohn Snow if obj.ifcond: 1184f7f97a7SJohn Snow ret += gen_if(obj.ifcond) 1194f7f97a7SJohn Snow ret += _tree_to_qlit(obj.value, level) 1204f7f97a7SJohn Snow if obj.ifcond: 1214f7f97a7SJohn Snow ret += '\n' + gen_endif(obj.ifcond) 122d626b6c1SMarc-André Lureau return ret 123d626b6c1SMarc-André Lureau 1247d0f982bSMarc-André Lureau ret = '' 12505556960SJohn Snow if not dict_value: 1267d0f982bSMarc-André Lureau ret += indent(level) 127c0e8d9f3SJohn Snow 128c0e8d9f3SJohn Snow # Scalars: 129fb0bc835SMarkus Armbruster if obj is None: 1307d0f982bSMarc-André Lureau ret += 'QLIT_QNULL' 131fb0bc835SMarkus Armbruster elif isinstance(obj, str): 132c0e8d9f3SJohn Snow ret += f"QLIT_QSTR({to_c_string(obj)})" 133c0e8d9f3SJohn Snow elif isinstance(obj, bool): 134c0e8d9f3SJohn Snow ret += f"QLIT_QBOOL({str(obj).lower()})" 135c0e8d9f3SJohn Snow 136c0e8d9f3SJohn Snow # Non-scalars: 137fb0bc835SMarkus Armbruster elif isinstance(obj, list): 1387d0f982bSMarc-André Lureau ret += 'QLIT_QLIST(((QLitObject[]) {\n' 139c0e8d9f3SJohn Snow for value in obj: 140c0e8d9f3SJohn Snow ret += _tree_to_qlit(value, level + 1).strip('\n') + '\n' 141c0e8d9f3SJohn Snow ret += indent(level + 1) + '{}\n' 1427d0f982bSMarc-André Lureau ret += indent(level) + '}))' 143fb0bc835SMarkus Armbruster elif isinstance(obj, dict): 1447d0f982bSMarc-André Lureau ret += 'QLIT_QDICT(((QLitDictEntry[]) {\n' 145c0e8d9f3SJohn Snow for key, value in sorted(obj.items()): 146c0e8d9f3SJohn Snow ret += indent(level + 1) + "{{ {:s}, {:s} }},\n".format( 147c0e8d9f3SJohn Snow to_c_string(key), 148c0e8d9f3SJohn Snow _tree_to_qlit(value, level + 1, dict_value=True) 149c0e8d9f3SJohn Snow ) 150c0e8d9f3SJohn Snow ret += indent(level + 1) + '{}\n' 1517d0f982bSMarc-André Lureau ret += indent(level) + '}))' 152fb0bc835SMarkus Armbruster else: 1532a6c161bSJohn Snow raise NotImplementedError( 1542a6c161bSJohn Snow f"type '{type(obj).__name__}' not implemented" 1552a6c161bSJohn Snow ) 156c0e8d9f3SJohn Snow 15740bb1376SMarc-André Lureau if level > 0: 15840bb1376SMarc-André Lureau ret += ',' 159fb0bc835SMarkus Armbruster return ret 160fb0bc835SMarkus Armbruster 161fb0bc835SMarkus Armbruster 162*82b52f6bSJohn Snowdef to_c_string(string: str) -> str: 163fb0bc835SMarkus Armbruster return '"' + string.replace('\\', r'\\').replace('"', r'\"') + '"' 164fb0bc835SMarkus Armbruster 165fb0bc835SMarkus Armbruster 16671b3f045SMarkus Armbrusterclass QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor): 167fb0bc835SMarkus Armbruster 168*82b52f6bSJohn Snow def __init__(self, prefix: str, unmask: bool): 1692cae67bcSMarkus Armbruster super().__init__( 1702cae67bcSMarkus Armbruster prefix, 'qapi-introspect', 17171b3f045SMarkus Armbruster ' * QAPI/QMP schema introspection', __doc__) 17271b3f045SMarkus Armbruster self._unmask = unmask 173*82b52f6bSJohn Snow self._schema: Optional[QAPISchema] = None 174*82b52f6bSJohn Snow self._trees: List[Annotated[SchemaInfo]] = [] 175*82b52f6bSJohn Snow self._used_types: List[QAPISchemaType] = [] 176*82b52f6bSJohn Snow self._name_map: Dict[str, str] = {} 17771b3f045SMarkus Armbruster self._genc.add(mcgen(''' 17871b3f045SMarkus Armbruster#include "qemu/osdep.h" 179eb815e24SMarkus Armbruster#include "%(prefix)sqapi-introspect.h" 18071b3f045SMarkus Armbruster 18171b3f045SMarkus Armbruster''', 18271b3f045SMarkus Armbruster prefix=prefix)) 18371b3f045SMarkus Armbruster 184*82b52f6bSJohn Snow def visit_begin(self, schema: QAPISchema) -> None: 18571b3f045SMarkus Armbruster self._schema = schema 186fb0bc835SMarkus Armbruster 187*82b52f6bSJohn Snow def visit_end(self) -> None: 188fb0bc835SMarkus Armbruster # visit the types that are actually used 189fb0bc835SMarkus Armbruster for typ in self._used_types: 190fb0bc835SMarkus Armbruster typ.visit(self) 191fb0bc835SMarkus Armbruster # generate C 1927d0f982bSMarc-André Lureau name = c_name(self._prefix, protect=False) + 'qmp_schema_qlit' 19371b3f045SMarkus Armbruster self._genh.add(mcgen(''' 1947d0f982bSMarc-André Lureau#include "qapi/qmp/qlit.h" 1957d0f982bSMarc-André Lureau 1967d0f982bSMarc-André Lureauextern const QLitObject %(c_name)s; 197fb0bc835SMarkus Armbruster''', 19871b3f045SMarkus Armbruster c_name=c_name(name))) 19971b3f045SMarkus Armbruster self._genc.add(mcgen(''' 2007d0f982bSMarc-André Lureauconst QLitObject %(c_name)s = %(c_string)s; 201fb0bc835SMarkus Armbruster''', 202fb0bc835SMarkus Armbruster c_name=c_name(name), 2032e8a843dSMarkus Armbruster c_string=_tree_to_qlit(self._trees))) 204fb0bc835SMarkus Armbruster self._schema = None 2052e8a843dSMarkus Armbruster self._trees = [] 20671b3f045SMarkus Armbruster self._used_types = [] 20771b3f045SMarkus Armbruster self._name_map = {} 208fb0bc835SMarkus Armbruster 209*82b52f6bSJohn Snow def visit_needed(self, entity: QAPISchemaEntity) -> bool: 210fb0bc835SMarkus Armbruster # Ignore types on first pass; visit_end() will pick up used types 211fb0bc835SMarkus Armbruster return not isinstance(entity, QAPISchemaType) 212fb0bc835SMarkus Armbruster 213*82b52f6bSJohn Snow def _name(self, name: str) -> str: 214fb0bc835SMarkus Armbruster if self._unmask: 215fb0bc835SMarkus Armbruster return name 216fb0bc835SMarkus Armbruster if name not in self._name_map: 217fb0bc835SMarkus Armbruster self._name_map[name] = '%d' % len(self._name_map) 218fb0bc835SMarkus Armbruster return self._name_map[name] 219fb0bc835SMarkus Armbruster 220*82b52f6bSJohn Snow def _use_type(self, typ: QAPISchemaType) -> str: 2216b67bcacSJohn Snow assert self._schema is not None 2226b67bcacSJohn Snow 223fb0bc835SMarkus Armbruster # Map the various integer types to plain int 224fb0bc835SMarkus Armbruster if typ.json_type() == 'int': 225fb0bc835SMarkus Armbruster typ = self._schema.lookup_type('int') 226fb0bc835SMarkus Armbruster elif (isinstance(typ, QAPISchemaArrayType) and 227fb0bc835SMarkus Armbruster typ.element_type.json_type() == 'int'): 228fb0bc835SMarkus Armbruster typ = self._schema.lookup_type('intList') 229fb0bc835SMarkus Armbruster # Add type to work queue if new 230fb0bc835SMarkus Armbruster if typ not in self._used_types: 231fb0bc835SMarkus Armbruster self._used_types.append(typ) 232fb0bc835SMarkus Armbruster # Clients should examine commands and events, not types. Hide 2331aa806ccSEric Blake # type names as integers to reduce the temptation. Also, it 2341aa806ccSEric Blake # saves a few characters on the wire. 235fb0bc835SMarkus Armbruster if isinstance(typ, QAPISchemaBuiltinType): 236fb0bc835SMarkus Armbruster return typ.name 237fb0bc835SMarkus Armbruster if isinstance(typ, QAPISchemaArrayType): 238fb0bc835SMarkus Armbruster return '[' + self._use_type(typ.element_type) + ']' 239fb0bc835SMarkus Armbruster return self._name(typ.name) 240fb0bc835SMarkus Armbruster 24184bece7dSJohn Snow @staticmethod 242*82b52f6bSJohn Snow def _gen_features(features: List[QAPISchemaFeature] 243*82b52f6bSJohn Snow ) -> List[Annotated[str]]: 2444f7f97a7SJohn Snow return [Annotated(f.name, f.ifcond) for f in features] 24584bece7dSJohn Snow 246*82b52f6bSJohn Snow def _gen_tree(self, name: str, mtype: str, obj: Dict[str, object], 247*82b52f6bSJohn Snow ifcond: Sequence[str], 248*82b52f6bSJohn Snow features: Optional[List[QAPISchemaFeature]]) -> None: 2495f50cedeSJohn Snow comment: Optional[str] = None 250fb0bc835SMarkus Armbruster if mtype not in ('command', 'event', 'builtin', 'array'): 2518c643361SEric Blake if not self._unmask: 2528c643361SEric Blake # Output a comment to make it easy to map masked names 2538c643361SEric Blake # back to the source when reading the generated output. 2545f50cedeSJohn Snow comment = f'"{self._name(name)}" = {name}' 255fb0bc835SMarkus Armbruster name = self._name(name) 256fb0bc835SMarkus Armbruster obj['name'] = name 257fb0bc835SMarkus Armbruster obj['meta-type'] = mtype 25884bece7dSJohn Snow if features: 25984bece7dSJohn Snow obj['features'] = self._gen_features(features) 2604f7f97a7SJohn Snow self._trees.append(Annotated(obj, ifcond, comment)) 261fb0bc835SMarkus Armbruster 262*82b52f6bSJohn Snow def _gen_member(self, member: QAPISchemaObjectTypeMember 263*82b52f6bSJohn Snow ) -> Annotated[SchemaInfoObjectMember]: 264*82b52f6bSJohn Snow obj: SchemaInfoObjectMember = { 265*82b52f6bSJohn Snow 'name': member.name, 266*82b52f6bSJohn Snow 'type': self._use_type(member.type) 267*82b52f6bSJohn Snow } 268fb0bc835SMarkus Armbruster if member.optional: 26924cfd6adSMarkus Armbruster obj['default'] = None 27084bece7dSJohn Snow if member.features: 27184bece7dSJohn Snow obj['features'] = self._gen_features(member.features) 2724f7f97a7SJohn Snow return Annotated(obj, member.ifcond) 273fb0bc835SMarkus Armbruster 274*82b52f6bSJohn Snow def _gen_variant(self, variant: QAPISchemaVariant 275*82b52f6bSJohn Snow ) -> Annotated[SchemaInfoObjectVariant]: 276*82b52f6bSJohn Snow obj: SchemaInfoObjectVariant = { 277*82b52f6bSJohn Snow 'case': variant.name, 278*82b52f6bSJohn Snow 'type': self._use_type(variant.type) 279*82b52f6bSJohn Snow } 2804f7f97a7SJohn Snow return Annotated(obj, variant.ifcond) 281fb0bc835SMarkus Armbruster 282*82b52f6bSJohn Snow def visit_builtin_type(self, name: str, info: Optional[QAPISourceInfo], 283*82b52f6bSJohn Snow json_type: str) -> None: 2842e8a843dSMarkus Armbruster self._gen_tree(name, 'builtin', {'json-type': json_type}, [], None) 285fb0bc835SMarkus Armbruster 286*82b52f6bSJohn Snow def visit_enum_type(self, name: str, info: Optional[QAPISourceInfo], 287*82b52f6bSJohn Snow ifcond: Sequence[str], 288*82b52f6bSJohn Snow features: List[QAPISchemaFeature], 289*82b52f6bSJohn Snow members: List[QAPISchemaEnumMember], 290*82b52f6bSJohn Snow prefix: Optional[str]) -> None: 2914f7f97a7SJohn Snow self._gen_tree( 2924f7f97a7SJohn Snow name, 'enum', 2934f7f97a7SJohn Snow {'values': [Annotated(m.name, m.ifcond) for m in members]}, 2944f7f97a7SJohn Snow ifcond, features 2954f7f97a7SJohn Snow ) 296fb0bc835SMarkus Armbruster 297*82b52f6bSJohn Snow def visit_array_type(self, name: str, info: Optional[QAPISourceInfo], 298*82b52f6bSJohn Snow ifcond: Sequence[str], 299*82b52f6bSJohn Snow element_type: QAPISchemaType) -> None: 300fb0bc835SMarkus Armbruster element = self._use_type(element_type) 3012e8a843dSMarkus Armbruster self._gen_tree('[' + element + ']', 'array', {'element-type': element}, 302013b4efcSMarkus Armbruster ifcond, None) 303fb0bc835SMarkus Armbruster 304*82b52f6bSJohn Snow def visit_object_type_flat(self, name: str, info: Optional[QAPISourceInfo], 305*82b52f6bSJohn Snow ifcond: Sequence[str], 306*82b52f6bSJohn Snow features: List[QAPISchemaFeature], 307*82b52f6bSJohn Snow members: List[QAPISchemaObjectTypeMember], 308*82b52f6bSJohn Snow variants: Optional[QAPISchemaVariants]) -> None: 309*82b52f6bSJohn Snow obj: SchemaInfoObject = { 310*82b52f6bSJohn Snow 'members': [self._gen_member(m) for m in members] 311*82b52f6bSJohn Snow } 312fb0bc835SMarkus Armbruster if variants: 313cf5db214SJohn Snow obj['tag'] = variants.tag_member.name 314cf5db214SJohn Snow obj['variants'] = [self._gen_variant(v) for v in variants.variants] 3152e8a843dSMarkus Armbruster self._gen_tree(name, 'object', obj, ifcond, features) 316fb0bc835SMarkus Armbruster 317*82b52f6bSJohn Snow def visit_alternate_type(self, name: str, info: Optional[QAPISourceInfo], 318*82b52f6bSJohn Snow ifcond: Sequence[str], 319*82b52f6bSJohn Snow features: List[QAPISchemaFeature], 320*82b52f6bSJohn Snow variants: QAPISchemaVariants) -> None: 3214f7f97a7SJohn Snow self._gen_tree( 3224f7f97a7SJohn Snow name, 'alternate', 3234f7f97a7SJohn Snow {'members': [Annotated({'type': self._use_type(m.type)}, 3244f7f97a7SJohn Snow m.ifcond) 325013b4efcSMarkus Armbruster for m in variants.variants]}, 3264f7f97a7SJohn Snow ifcond, features 3274f7f97a7SJohn Snow ) 328fb0bc835SMarkus Armbruster 329*82b52f6bSJohn Snow def visit_command(self, name: str, info: Optional[QAPISourceInfo], 330*82b52f6bSJohn Snow ifcond: Sequence[str], 331*82b52f6bSJohn Snow features: List[QAPISchemaFeature], 332*82b52f6bSJohn Snow arg_type: Optional[QAPISchemaObjectType], 333*82b52f6bSJohn Snow ret_type: Optional[QAPISchemaType], gen: bool, 334*82b52f6bSJohn Snow success_response: bool, boxed: bool, allow_oob: bool, 335*82b52f6bSJohn Snow allow_preconfig: bool, coroutine: bool) -> None: 3366b67bcacSJohn Snow assert self._schema is not None 3376b67bcacSJohn Snow 338fb0bc835SMarkus Armbruster arg_type = arg_type or self._schema.the_empty_object_type 339fb0bc835SMarkus Armbruster ret_type = ret_type or self._schema.the_empty_object_type 340*82b52f6bSJohn Snow obj: SchemaInfoCommand = { 341*82b52f6bSJohn Snow 'arg-type': self._use_type(arg_type), 342*82b52f6bSJohn Snow 'ret-type': self._use_type(ret_type) 343*82b52f6bSJohn Snow } 34425b1ef31SMarkus Armbruster if allow_oob: 34525b1ef31SMarkus Armbruster obj['allow-oob'] = allow_oob 3462e8a843dSMarkus Armbruster self._gen_tree(name, 'command', obj, ifcond, features) 34723394b4cSPeter Krempa 348*82b52f6bSJohn Snow def visit_event(self, name: str, info: Optional[QAPISourceInfo], 349*82b52f6bSJohn Snow ifcond: Sequence[str], features: List[QAPISchemaFeature], 350*82b52f6bSJohn Snow arg_type: Optional[QAPISchemaObjectType], 351*82b52f6bSJohn Snow boxed: bool) -> None: 3526b67bcacSJohn Snow assert self._schema is not None 353*82b52f6bSJohn Snow 354fb0bc835SMarkus Armbruster arg_type = arg_type or self._schema.the_empty_object_type 3552e8a843dSMarkus Armbruster self._gen_tree(name, 'event', {'arg-type': self._use_type(arg_type)}, 356013b4efcSMarkus Armbruster ifcond, features) 357fb0bc835SMarkus Armbruster 358fb0bc835SMarkus Armbruster 359*82b52f6bSJohn Snowdef gen_introspect(schema: QAPISchema, output_dir: str, prefix: str, 360*82b52f6bSJohn Snow opt_unmask: bool) -> None: 361fb0bc835SMarkus Armbruster vis = QAPISchemaGenIntrospectVisitor(prefix, opt_unmask) 362fb0bc835SMarkus Armbruster schema.visit(vis) 36371b3f045SMarkus Armbruster vis.write(output_dir) 364