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