From a4bcb2080d5c1d08bab512d76fb260296e2cae74 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 16 Sep 2015 13:06:04 +0200 Subject: qapi: Rename class QAPISchema to QAPISchemaParser I want to name a new class QAPISchema. While there, make it a new-style class. Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Reviewed-by: Daniel P. Berrange Message-Id: <1442401589-24189-2-git-send-email-armbru@redhat.com> --- scripts/qapi.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'scripts/qapi.py') diff --git a/scripts/qapi.py b/scripts/qapi.py index c4423b7..2a0f465 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -103,7 +103,7 @@ class QAPIExprError(Exception): return error_path(self.info['parent']) + \ "%s:%d: %s" % (self.info['file'], self.info['line'], self.msg) -class QAPISchema: +class QAPISchemaParser(object): def __init__(self, fp, previously_included = [], incl_info = None): abs_fname = os.path.abspath(fp.name) @@ -149,8 +149,8 @@ class QAPISchema: except IOError, e: raise QAPIExprError(expr_info, '%s: %s' % (e.strerror, include)) - exprs_include = QAPISchema(fobj, previously_included, - expr_info) + exprs_include = QAPISchemaParser(fobj, previously_included, + expr_info) self.exprs.extend(exprs_include.exprs) else: expr_elem = {'expr': expr, @@ -755,7 +755,7 @@ def check_exprs(exprs): def parse_schema(fname): try: - schema = QAPISchema(open(fname, "r")) + schema = QAPISchemaParser(open(fname, "r")) return check_exprs(schema.exprs) except (QAPISchemaError, QAPIExprError), e: print >>sys.stderr, e -- cgit v1.1 From ac88219a6c78302c693fb60fe6cf04358540fbce Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 16 Sep 2015 13:06:05 +0200 Subject: qapi: New QAPISchema intermediate reperesentation The QAPI code generators work with a syntax tree (nested dictionaries) plus a few symbol tables (also dictionaries) on the side. They have clearly outgrown these simple data structures. There's lots of rummaging around in dictionaries, and information is recomputed on the fly. For the work I'm going to do, I want more clearly defined and more convenient interfaces. Going forward, I also want less coupling between the back-ends and the syntax tree, to make messing with the syntax easier. Create a bunch of classes to represent QAPI schemata. Have the QAPISchema initializer call the parser, then walk the syntax tree to create the new internal representation, and finally perform semantic analysis. Shortcut: the semantic analysis still relies on existing check_exprs() to do the actual semantic checking. All this code needs to move into the classes. Mark as TODO. Simple unions are lowered to flat unions. Flat unions and structs are represented as a more general object type. Catching name collisions in generated code would be nice. Mark as TODO. We generate array types eagerly, even though most of them aren't used. Mark as TODO. Nothing uses the new intermediate representation just yet, thus no change to generated files. Signed-off-by: Markus Armbruster Reviewed-by: Daniel P. Berrange Reviewed-by: Eric Blake --- scripts/qapi.py | 380 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 372 insertions(+), 8 deletions(-) (limited to 'scripts/qapi.py') diff --git a/scripts/qapi.py b/scripts/qapi.py index 2a0f465..d0dfabe 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -302,6 +302,8 @@ class QAPISchemaParser(object): # # Semantic analysis of schema expressions +# TODO fold into QAPISchema +# TODO catching name collisions in generated code would be nice # def find_base_fields(base): @@ -751,15 +753,377 @@ def check_exprs(exprs): else: assert False, 'unexpected meta type' - return map(lambda expr_elem: expr_elem['expr'], exprs) + return exprs + + +# +# Schema compiler frontend +# + +class QAPISchemaEntity(object): + def __init__(self, name, info): + assert isinstance(name, str) + self.name = name + self.info = info + + def check(self, schema): + pass + + +class QAPISchemaType(QAPISchemaEntity): + pass + + +class QAPISchemaBuiltinType(QAPISchemaType): + def __init__(self, name): + QAPISchemaType.__init__(self, name, None) + + +class QAPISchemaEnumType(QAPISchemaType): + def __init__(self, name, info, values, prefix): + QAPISchemaType.__init__(self, name, info) + for v in values: + assert isinstance(v, str) + assert prefix is None or isinstance(prefix, str) + self.values = values + self.prefix = prefix + + def check(self, schema): + assert len(set(self.values)) == len(self.values) + + +class QAPISchemaArrayType(QAPISchemaType): + def __init__(self, name, info, element_type): + QAPISchemaType.__init__(self, name, info) + assert isinstance(element_type, str) + self._element_type_name = element_type + self.element_type = None + + def check(self, schema): + self.element_type = schema.lookup_type(self._element_type_name) + assert self.element_type + + +class QAPISchemaObjectType(QAPISchemaType): + def __init__(self, name, info, base, local_members, variants): + QAPISchemaType.__init__(self, name, info) + assert base is None or isinstance(base, str) + for m in local_members: + assert isinstance(m, QAPISchemaObjectTypeMember) + assert (variants is None or + isinstance(variants, QAPISchemaObjectTypeVariants)) + self._base_name = base + self.base = None + self.local_members = local_members + self.variants = variants + self.members = None + + def check(self, schema): + assert self.members is not False # not running in cycles + if self.members: + return + self.members = False # mark as being checked + if self._base_name: + self.base = schema.lookup_type(self._base_name) + assert isinstance(self.base, QAPISchemaObjectType) + assert not self.base.variants # not implemented + self.base.check(schema) + members = list(self.base.members) + else: + members = [] + seen = {} + for m in members: + seen[m.name] = m + for m in self.local_members: + m.check(schema, members, seen) + if self.variants: + self.variants.check(schema, members, seen) + self.members = members + + +class QAPISchemaObjectTypeMember(object): + def __init__(self, name, typ, optional): + assert isinstance(name, str) + assert isinstance(typ, str) + assert isinstance(optional, bool) + self.name = name + self._type_name = typ + self.type = None + self.optional = optional + + def check(self, schema, all_members, seen): + assert self.name not in seen + self.type = schema.lookup_type(self._type_name) + assert self.type + all_members.append(self) + seen[self.name] = self + + +class QAPISchemaObjectTypeVariants(object): + def __init__(self, tag_name, tag_enum, variants): + assert tag_name is None or isinstance(tag_name, str) + assert tag_enum is None or isinstance(tag_enum, str) + for v in variants: + assert isinstance(v, QAPISchemaObjectTypeVariant) + self.tag_name = tag_name + if tag_name: + assert not tag_enum + self.tag_member = None + else: + self.tag_member = QAPISchemaObjectTypeMember('type', tag_enum, + False) + self.variants = variants + + def check(self, schema, members, seen): + if self.tag_name: + self.tag_member = seen[self.tag_name] + else: + self.tag_member.check(schema, members, seen) + assert isinstance(self.tag_member.type, QAPISchemaEnumType) + for v in self.variants: + vseen = dict(seen) + v.check(schema, self.tag_member.type, vseen) + + +class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember): + def __init__(self, name, typ): + QAPISchemaObjectTypeMember.__init__(self, name, typ, False) + + def check(self, schema, tag_type, seen): + QAPISchemaObjectTypeMember.check(self, schema, [], seen) + assert self.name in tag_type.values + + +class QAPISchemaAlternateType(QAPISchemaType): + def __init__(self, name, info, variants): + QAPISchemaType.__init__(self, name, info) + assert isinstance(variants, QAPISchemaObjectTypeVariants) + assert not variants.tag_name + self.variants = variants + + def check(self, schema): + self.variants.check(schema, [], {}) + + +class QAPISchemaCommand(QAPISchemaEntity): + def __init__(self, name, info, arg_type, ret_type, gen, success_response): + QAPISchemaEntity.__init__(self, name, info) + assert not arg_type or isinstance(arg_type, str) + assert not ret_type or isinstance(ret_type, str) + self._arg_type_name = arg_type + self.arg_type = None + self._ret_type_name = ret_type + self.ret_type = None + self.gen = gen + self.success_response = success_response + + def check(self, schema): + if self._arg_type_name: + self.arg_type = schema.lookup_type(self._arg_type_name) + assert isinstance(self.arg_type, QAPISchemaObjectType) + assert not self.arg_type.variants # not implemented + if self._ret_type_name: + self.ret_type = schema.lookup_type(self._ret_type_name) + assert isinstance(self.ret_type, QAPISchemaType) + + +class QAPISchemaEvent(QAPISchemaEntity): + def __init__(self, name, info, arg_type): + QAPISchemaEntity.__init__(self, name, info) + assert not arg_type or isinstance(arg_type, str) + self._arg_type_name = arg_type + self.arg_type = None + + def check(self, schema): + if self._arg_type_name: + self.arg_type = schema.lookup_type(self._arg_type_name) + assert isinstance(self.arg_type, QAPISchemaObjectType) + assert not self.arg_type.variants # not implemented + + +class QAPISchema(object): + def __init__(self, fname): + try: + self.exprs = check_exprs(QAPISchemaParser(open(fname, "r")).exprs) + except (QAPISchemaError, QAPIExprError), err: + print >>sys.stderr, err + exit(1) + self._entity_dict = {} + self._def_predefineds() + self._def_exprs() + self.check() + + def get_exprs(self): + return [expr_elem['expr'] for expr_elem in self.exprs] + + def _def_entity(self, ent): + assert ent.name not in self._entity_dict + self._entity_dict[ent.name] = ent + + def lookup_entity(self, name, typ=None): + ent = self._entity_dict.get(name) + if typ and not isinstance(ent, typ): + return None + return ent + + def lookup_type(self, name): + return self.lookup_entity(name, QAPISchemaType) + + def _def_builtin_type(self, name): + self._def_entity(QAPISchemaBuiltinType(name)) + if name != '**': + self._make_array_type(name) # TODO really needed? + + def _def_predefineds(self): + for t in ['str', 'number', 'int', 'int8', 'int16', 'int32', 'int64', + 'uint8', 'uint16', 'uint32', 'uint64', 'size', 'bool', '**']: + self._def_builtin_type(t) + + def _make_implicit_enum_type(self, name, values): + name = name + 'Kind' + self._def_entity(QAPISchemaEnumType(name, None, values, None)) + return name + + def _make_array_type(self, element_type): + name = element_type + 'List' + if not self.lookup_type(name): + self._def_entity(QAPISchemaArrayType(name, None, element_type)) + return name + + def _make_implicit_object_type(self, name, role, members): + if not members: + return None + name = ':obj-%s-%s' % (name, role) + if not self.lookup_entity(name, QAPISchemaObjectType): + self._def_entity(QAPISchemaObjectType(name, None, None, + members, None)) + return name + + def _def_enum_type(self, expr, info): + name = expr['enum'] + data = expr['data'] + prefix = expr.get('prefix') + self._def_entity(QAPISchemaEnumType(name, info, data, prefix)) + self._make_array_type(name) # TODO really needed? + + def _make_member(self, name, typ): + optional = False + if name.startswith('*'): + name = name[1:] + optional = True + if isinstance(typ, list): + assert len(typ) == 1 + typ = self._make_array_type(typ[0]) + return QAPISchemaObjectTypeMember(name, typ, optional) + + def _make_members(self, data): + return [self._make_member(key, value) + for (key, value) in data.iteritems()] + + def _def_struct_type(self, expr, info): + name = expr['struct'] + base = expr.get('base') + data = expr['data'] + self._def_entity(QAPISchemaObjectType(name, info, base, + self._make_members(data), + None)) + self._make_array_type(name) # TODO really needed? + + def _make_variant(self, case, typ): + return QAPISchemaObjectTypeVariant(case, typ) + + def _make_simple_variant(self, case, typ): + if isinstance(typ, list): + assert len(typ) == 1 + typ = self._make_array_type(typ[0]) + typ = self._make_implicit_object_type(typ, 'wrapper', + [self._make_member('data', typ)]) + return QAPISchemaObjectTypeVariant(case, typ) + + def _make_tag_enum(self, type_name, variants): + return self._make_implicit_enum_type(type_name, + [v.name for v in variants]) + + def _def_union_type(self, expr, info): + name = expr['union'] + data = expr['data'] + base = expr.get('base') + tag_name = expr.get('discriminator') + tag_enum = None + if tag_name: + variants = [self._make_variant(key, value) + for (key, value) in data.iteritems()] + else: + variants = [self._make_simple_variant(key, value) + for (key, value) in data.iteritems()] + tag_enum = self._make_tag_enum(name, variants) + self._def_entity( + QAPISchemaObjectType(name, info, base, + self._make_members(OrderedDict()), + QAPISchemaObjectTypeVariants(tag_name, + tag_enum, + variants))) + self._make_array_type(name) # TODO really needed? + + def _def_alternate_type(self, expr, info): + name = expr['alternate'] + data = expr['data'] + variants = [self._make_variant(key, value) + for (key, value) in data.iteritems()] + tag_enum = self._make_tag_enum(name, variants) + self._def_entity( + QAPISchemaAlternateType(name, info, + QAPISchemaObjectTypeVariants(None, + tag_enum, + variants))) + self._make_array_type(name) # TODO really needed? + + def _def_command(self, expr, info): + name = expr['command'] + data = expr.get('data') + rets = expr.get('returns') + gen = expr.get('gen', True) + success_response = expr.get('success-response', True) + if isinstance(data, OrderedDict): + data = self._make_implicit_object_type(name, 'arg', + self._make_members(data)) + if isinstance(rets, list): + assert len(rets) == 1 + rets = self._make_array_type(rets[0]) + self._def_entity(QAPISchemaCommand(name, info, data, rets, gen, + success_response)) + + def _def_event(self, expr, info): + name = expr['event'] + data = expr.get('data') + if isinstance(data, OrderedDict): + data = self._make_implicit_object_type(name, 'arg', + self._make_members(data)) + self._def_entity(QAPISchemaEvent(name, info, data)) + + def _def_exprs(self): + for expr_elem in self.exprs: + expr = expr_elem['expr'] + info = expr_elem['info'] + if 'enum' in expr: + self._def_enum_type(expr, info) + elif 'struct' in expr: + self._def_struct_type(expr, info) + elif 'union' in expr: + self._def_union_type(expr, info) + elif 'alternate' in expr: + self._def_alternate_type(expr, info) + elif 'command' in expr: + self._def_command(expr, info) + elif 'event' in expr: + self._def_event(expr, info) + else: + assert False + + def check(self): + for ent in self._entity_dict.values(): + ent.check(self) -def parse_schema(fname): - try: - schema = QAPISchemaParser(open(fname, "r")) - return check_exprs(schema.exprs) - except (QAPISchemaError, QAPIExprError), e: - print >>sys.stderr, e - exit(1) # # Code generation helpers -- cgit v1.1 From f51d8c3db11b0f3052b3bb4b8b0c7f0bc76f7136 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 16 Sep 2015 13:06:06 +0200 Subject: qapi: QAPISchema code generation helper methods New methods c_name(), c_type(), c_null(), json_type(), alternate_qtype(). Signed-off-by: Markus Armbruster Message-Id: <1442401589-24189-4-git-send-email-armbru@redhat.com> Reviewed-by: Daniel P. Berrange Reviewed-by: Eric Blake --- scripts/qapi.py | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 7 deletions(-) (limited to 'scripts/qapi.py') diff --git a/scripts/qapi.py b/scripts/qapi.py index d0dfabe..401b87d 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -766,17 +766,57 @@ class QAPISchemaEntity(object): self.name = name self.info = info + def c_name(self): + return c_name(self.name) + def check(self, schema): pass class QAPISchemaType(QAPISchemaEntity): - pass + def c_type(self, is_param=False): + return c_name(self.name) + pointer_suffix + + def c_null(self): + return 'NULL' + + def json_type(self): + pass + + def alternate_qtype(self): + json2qtype = { + 'string': 'QTYPE_QSTRING', + 'number': 'QTYPE_QFLOAT', + 'int': 'QTYPE_QINT', + 'boolean': 'QTYPE_QBOOL', + 'object': 'QTYPE_QDICT' + } + return json2qtype.get(self.json_type()) class QAPISchemaBuiltinType(QAPISchemaType): - def __init__(self, name): + def __init__(self, name, json_type, c_type, c_null): QAPISchemaType.__init__(self, name, None) + assert not c_type or isinstance(c_type, str) + assert json_type in ('string', 'number', 'int', 'boolean', 'null', + 'value') + self._json_type_name = json_type + self._c_type_name = c_type + self._c_null_val = c_null + + def c_name(self): + return self.name + + def c_type(self, is_param=False): + if is_param and self.name == 'str': + return 'const ' + self._c_type_name + return self._c_type_name + + def c_null(self): + return self._c_null_val + + def json_type(self): + return self._json_type_name class QAPISchemaEnumType(QAPISchemaType): @@ -791,6 +831,16 @@ class QAPISchemaEnumType(QAPISchemaType): def check(self, schema): assert len(set(self.values)) == len(self.values) + def c_type(self, is_param=False): + return c_name(self.name) + + def c_null(self): + return c_enum_const(self.name, (self.values + ['MAX'])[0], + self.prefix) + + def json_type(self): + return 'string' + class QAPISchemaArrayType(QAPISchemaType): def __init__(self, name, info, element_type): @@ -803,6 +853,9 @@ class QAPISchemaArrayType(QAPISchemaType): self.element_type = schema.lookup_type(self._element_type_name) assert self.element_type + def json_type(self): + return 'array' + class QAPISchemaObjectType(QAPISchemaType): def __init__(self, name, info, base, local_members, variants): @@ -840,6 +893,17 @@ class QAPISchemaObjectType(QAPISchemaType): self.variants.check(schema, members, seen) self.members = members + def c_name(self): + assert self.info + return QAPISchemaType.c_name(self) + + def c_type(self, is_param=False): + assert self.info + return QAPISchemaType.c_type(self) + + def json_type(self): + return 'object' + class QAPISchemaObjectTypeMember(object): def __init__(self, name, typ, optional): @@ -904,6 +968,9 @@ class QAPISchemaAlternateType(QAPISchemaType): def check(self, schema): self.variants.check(schema, [], {}) + def json_type(self): + return 'value' + class QAPISchemaCommand(QAPISchemaEntity): def __init__(self, name, info, arg_type, ret_type, gen, success_response): @@ -969,15 +1036,28 @@ class QAPISchema(object): def lookup_type(self, name): return self.lookup_entity(name, QAPISchemaType) - def _def_builtin_type(self, name): - self._def_entity(QAPISchemaBuiltinType(name)) + def _def_builtin_type(self, name, json_type, c_type, c_null): + self._def_entity(QAPISchemaBuiltinType(name, json_type, + c_type, c_null)) if name != '**': self._make_array_type(name) # TODO really needed? def _def_predefineds(self): - for t in ['str', 'number', 'int', 'int8', 'int16', 'int32', 'int64', - 'uint8', 'uint16', 'uint32', 'uint64', 'size', 'bool', '**']: - self._def_builtin_type(t) + for t in [('str', 'string', 'char' + pointer_suffix, 'NULL'), + ('number', 'number', 'double', '0'), + ('int', 'int', 'int64_t', '0'), + ('int8', 'int', 'int8_t', '0'), + ('int16', 'int', 'int16_t', '0'), + ('int32', 'int', 'int32_t', '0'), + ('int64', 'int', 'int64_t', '0'), + ('uint8', 'int', 'uint8_t', '0'), + ('uint16', 'int', 'uint16_t', '0'), + ('uint32', 'int', 'uint32_t', '0'), + ('uint64', 'int', 'uint64_t', '0'), + ('size', 'int', 'uint64_t', '0'), + ('bool', 'boolean', 'bool', 'false'), + ('**', 'value', None, None)]: + self._def_builtin_type(*t) def _make_implicit_enum_type(self, name, values): name = name + 'Kind' -- cgit v1.1 From 3f7dc21bee1e930d5cccf607b8f83831c3bbdb09 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 16 Sep 2015 13:06:07 +0200 Subject: qapi: New QAPISchemaVisitor The visitor will help keeping the code generation code simple and reasonably separated from QAPISchema details. Signed-off-by: Markus Armbruster Message-Id: <1442401589-24189-5-git-send-email-armbru@redhat.com> Reviewed-by: Daniel P. Berrange Reviewed-by: Eric Blake --- scripts/qapi.py | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) (limited to 'scripts/qapi.py') diff --git a/scripts/qapi.py b/scripts/qapi.py index 401b87d..36e0702 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -772,6 +772,39 @@ class QAPISchemaEntity(object): def check(self, schema): pass + def visit(self, visitor): + pass + + +class QAPISchemaVisitor(object): + def visit_begin(self, schema): + pass + + def visit_end(self): + pass + + def visit_builtin_type(self, name, info, json_type): + pass + + def visit_enum_type(self, name, info, values, prefix): + pass + + def visit_array_type(self, name, info, element_type): + pass + + def visit_object_type(self, name, info, base, members, variants): + pass + + def visit_alternate_type(self, name, info, variants): + pass + + def visit_command(self, name, info, arg_type, ret_type, + gen, success_response): + pass + + def visit_event(self, name, info, arg_type): + pass + class QAPISchemaType(QAPISchemaEntity): def c_type(self, is_param=False): @@ -818,6 +851,9 @@ class QAPISchemaBuiltinType(QAPISchemaType): def json_type(self): return self._json_type_name + def visit(self, visitor): + visitor.visit_builtin_type(self.name, self.info, self.json_type()) + class QAPISchemaEnumType(QAPISchemaType): def __init__(self, name, info, values, prefix): @@ -841,6 +877,10 @@ class QAPISchemaEnumType(QAPISchemaType): def json_type(self): return 'string' + def visit(self, visitor): + visitor.visit_enum_type(self.name, self.info, + self.values, self.prefix) + class QAPISchemaArrayType(QAPISchemaType): def __init__(self, name, info, element_type): @@ -856,6 +896,9 @@ class QAPISchemaArrayType(QAPISchemaType): def json_type(self): return 'array' + def visit(self, visitor): + visitor.visit_array_type(self.name, self.info, self.element_type) + class QAPISchemaObjectType(QAPISchemaType): def __init__(self, name, info, base, local_members, variants): @@ -904,6 +947,10 @@ class QAPISchemaObjectType(QAPISchemaType): def json_type(self): return 'object' + def visit(self, visitor): + visitor.visit_object_type(self.name, self.info, + self.base, self.local_members, self.variants) + class QAPISchemaObjectTypeMember(object): def __init__(self, name, typ, optional): @@ -971,6 +1018,9 @@ class QAPISchemaAlternateType(QAPISchemaType): def json_type(self): return 'value' + def visit(self, visitor): + visitor.visit_alternate_type(self.name, self.info, self.variants) + class QAPISchemaCommand(QAPISchemaEntity): def __init__(self, name, info, arg_type, ret_type, gen, success_response): @@ -993,6 +1043,11 @@ class QAPISchemaCommand(QAPISchemaEntity): self.ret_type = schema.lookup_type(self._ret_type_name) assert isinstance(self.ret_type, QAPISchemaType) + def visit(self, visitor): + visitor.visit_command(self.name, self.info, + self.arg_type, self.ret_type, + self.gen, self.success_response) + class QAPISchemaEvent(QAPISchemaEntity): def __init__(self, name, info, arg_type): @@ -1007,6 +1062,9 @@ class QAPISchemaEvent(QAPISchemaEntity): assert isinstance(self.arg_type, QAPISchemaObjectType) assert not self.arg_type.variants # not implemented + def visit(self, visitor): + visitor.visit_event(self.name, self.info, self.arg_type) + class QAPISchema(object): def __init__(self, fname): @@ -1204,6 +1262,12 @@ class QAPISchema(object): for ent in self._entity_dict.values(): ent.check(self) + def visit(self, visitor): + visitor.visit_begin(self) + for name in sorted(self._entity_dict.keys()): + self._entity_dict[name].visit(visitor) + visitor.visit_end() + # # Code generation helpers -- cgit v1.1 From 2b162ccbe875e5323fc04c1009addbdea4d35220 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 16 Sep 2015 13:06:09 +0200 Subject: qapi-types: Convert to QAPISchemaVisitor, fixing flat unions Fixes flat unions to get the base's base members. Test case is from commit 2fc0043, in qapi-schema-test.json: { 'union': 'UserDefFlatUnion', 'base': 'UserDefUnionBase', 'discriminator': 'enum1', 'data': { 'value1' : 'UserDefA', 'value2' : 'UserDefB', 'value3' : 'UserDefB' } } { 'struct': 'UserDefUnionBase', 'base': 'UserDefZero', 'data': { 'string': 'str', 'enum1': 'EnumOne' } } { 'struct': 'UserDefZero', 'data': { 'integer': 'int' } } Patch's effect on UserDefFlatUnion: struct UserDefFlatUnion { /* Members inherited from UserDefUnionBase: */ + int64_t integer; char *string; EnumOne enum1; /* Own members: */ union { /* union tag is @enum1 */ void *data; UserDefA *value1; UserDefB *value2; UserDefB *value3; }; }; Flat union visitors remain broken. They'll be fixed next. Code is generated in a different order now, but that doesn't matter. The two guards QAPI_TYPES_BUILTIN_STRUCT_DECL and QAPI_TYPES_BUILTIN_CLEANUP_DECL are replaced by just QAPI_TYPES_BUILTIN. Two ugly special cases for simple unions now stand out like sore thumbs: 1. The type tag is named 'type' everywhere, except in generated C, where it's 'kind'. 2. QAPISchema lowers simple unions to semantically equivalent flat unions. However, the C generated for a simple unions differs from the C generated for its equivalent flat union, and we therefore need special code to preserve that pointless difference for now. Mark both TODO. Signed-off-by: Markus Armbruster Reviewed-by: Daniel P. Berrange Reviewed-by: Eric Blake --- scripts/qapi.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'scripts/qapi.py') diff --git a/scripts/qapi.py b/scripts/qapi.py index 36e0702..c18400d 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -995,7 +995,6 @@ class QAPISchemaObjectTypeVariants(object): vseen = dict(seen) v.check(schema, self.tag_member.type, vseen) - class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember): def __init__(self, name, typ): QAPISchemaObjectTypeMember.__init__(self, name, typ, False) @@ -1004,6 +1003,15 @@ class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember): QAPISchemaObjectTypeMember.check(self, schema, [], seen) assert self.name in tag_type.values + # This function exists to support ugly simple union special cases + # TODO get rid of them, and drop the function + def simple_union_type(self): + if isinstance(self.type, QAPISchemaObjectType) and not self.type.info: + assert len(self.type.members) == 1 + assert not self.type.variants + return self.type.members[0].type + return None + class QAPISchemaAlternateType(QAPISchemaType): def __init__(self, name, info, variants): -- cgit v1.1 From ee44602857660e79f46547de02e26d65bcaf1519 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 16 Sep 2015 13:06:11 +0200 Subject: qapi-commands: Convert to QAPISchemaVisitor Output unchanged apart from reordering and white-space. Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Message-Id: <1442401589-24189-9-git-send-email-armbru@redhat.com> Reviewed-by: Daniel P. Berrange --- scripts/qapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scripts/qapi.py') diff --git a/scripts/qapi.py b/scripts/qapi.py index c18400d..db5bc85 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -1442,7 +1442,7 @@ def c_type(value, is_param=False): return c_name(value) + pointer_suffix def is_c_ptr(value): - return c_type(value).endswith(pointer_suffix) + return value.endswith(pointer_suffix) def genindent(count): ret = "" -- cgit v1.1 From efd2eaa6c2992c214a13f102b6ddd4dca4697fb3 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 16 Sep 2015 13:06:12 +0200 Subject: qapi: De-duplicate enum code generation Duplicated in commit 21cd70d. Yes, we can't import qapi-types, but that's no excuse. Move the helpers from qapi-types.py to qapi.py, and replace the duplicates in qapi-event.py. The generated event enumeration type's lookup table becomes const-correct (see commit 2e4450f), and uses explicit indexes instead of relying on order (see commit 912ae9c). Signed-off-by: Markus Armbruster Message-Id: <1442401589-24189-10-git-send-email-armbru@redhat.com> Reviewed-by: Daniel P. Berrange Reviewed-by: Eric Blake --- scripts/qapi.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) (limited to 'scripts/qapi.py') diff --git a/scripts/qapi.py b/scripts/qapi.py index db5bc85..ba32aac 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -1498,6 +1498,61 @@ def guardend(name): ''', name=guardname(name)) +def generate_enum_lookup(name, values, prefix=None): + ret = mcgen(''' + +const char *const %(name)s_lookup[] = { +''', + name=c_name(name)) + for value in values: + index = c_enum_const(name, value, prefix) + ret += mcgen(''' + [%(index)s] = "%(value)s", +''', + index = index, value = value) + + max_index = c_enum_const(name, 'MAX', prefix) + ret += mcgen(''' + [%(max_index)s] = NULL, +}; +''', + max_index=max_index) + return ret + +def generate_enum(name, values, prefix=None): + name = c_name(name) + lookup_decl = mcgen(''' + +extern const char *const %(name)s_lookup[]; +''', + name=name) + + enum_decl = mcgen(''' + +typedef enum %(name)s { +''', + name=name) + + # append automatically generated _MAX value + enum_values = values + [ 'MAX' ] + + i = 0 + for value in enum_values: + enum_full_value = c_enum_const(name, value, prefix) + enum_decl += mcgen(''' + %(enum_full_value)s = %(i)d, +''', + enum_full_value = enum_full_value, + i=i) + i += 1 + + enum_decl += mcgen(''' +} %(name)s; +''', + name=name) + + return enum_decl + lookup_decl + # # Common command line parsing # -- cgit v1.1 From 5710153e7310995b5d4127af267e36d8529b3b30 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 16 Sep 2015 13:06:15 +0200 Subject: qapi: Replace dirty is_c_ptr() by method c_null() is_c_ptr() looks whether the end of the C text for the type looks like a pointer. Works, but is fragile. We now have a better tool: use QAPISchemaType method c_null(). The initializers for non-pointers become prettier: 0, false or the enumeration constant with the value 0 instead of {0}. Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Reviewed-by: Daniel P. Berrange Message-Id: <1442401589-24189-13-git-send-email-armbru@redhat.com> --- scripts/qapi.py | 3 --- 1 file changed, 3 deletions(-) (limited to 'scripts/qapi.py') diff --git a/scripts/qapi.py b/scripts/qapi.py index ba32aac..0ffd02d 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -1441,9 +1441,6 @@ def c_type(value, is_param=False): assert isinstance(value, str) and value != "" return c_name(value) + pointer_suffix -def is_c_ptr(value): - return value.endswith(pointer_suffix) - def genindent(count): ret = "" for i in range(count): -- cgit v1.1 From e98859a9b96d71dea8f9af43325edd43c7effe66 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 16 Sep 2015 13:06:16 +0200 Subject: qapi: Clean up after recent conversions to QAPISchemaVisitor Generate just 'FOO' instead of 'struct FOO' when possible. Drop helper functions that are now unused. Make pep8 and pylint reasonably happy. Rename generate_FOO() functions to gen_FOO() for consistency. Use more consistent and sensible variable names. Consistently use c_ for mapping keys when their value is a C identifier or type. Simplify gen_enum() and gen_visit_union() Consistently use single quotes for C text string literals. Signed-off-by: Markus Armbruster Message-Id: <1442401589-24189-14-git-send-email-armbru@redhat.com> Reviewed-by: Daniel P. Berrange Reviewed-by: Eric Blake --- scripts/qapi.py | 127 ++++++++++---------------------------------------------- 1 file changed, 23 insertions(+), 104 deletions(-) (limited to 'scripts/qapi.py') diff --git a/scripts/qapi.py b/scripts/qapi.py index 0ffd02d..7ac72f6 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -1086,9 +1086,6 @@ class QAPISchema(object): self._def_exprs() self.check() - def get_exprs(self): - return [expr_elem['expr'] for expr_elem in self.exprs] - def _def_entity(self, ent): assert ent.name not in self._entity_dict self._entity_dict[ent.name] = ent @@ -1281,23 +1278,6 @@ class QAPISchema(object): # Code generation helpers # -def parse_args(typeinfo): - if isinstance(typeinfo, str): - struct = find_struct(typeinfo) - assert struct != None - typeinfo = struct['data'] - - for member in typeinfo: - argname = member - argentry = typeinfo[member] - optional = False - if member.startswith('*'): - argname = member[1:] - optional = True - # Todo: allow argentry to be OrderedDict, for providing the - # value of an optional argument. - yield (argname, argentry, optional) - def camel_case(name): new_name = '' first = True @@ -1380,67 +1360,9 @@ def c_name(name, protect=True): return "q_" + name return name.translate(c_name_trans) -# Map type @name to the C typedef name for the list form. -# -# ['Name'] -> 'NameList', ['x-Foo'] -> 'x_FooList', ['int'] -> 'intList' -def c_list_type(name): - return type_name(name) + 'List' - -# Map type @value to the C typedef form. -# -# Used for converting 'type' from a 'member':'type' qapi definition -# into the alphanumeric portion of the type for a generated C parameter, -# as well as generated C function names. See c_type() for the rest of -# the conversion such as adding '*' on pointer types. -# 'int' -> 'int', '[x-Foo]' -> 'x_FooList', '__a.b_c' -> '__a_b_c' -def type_name(value): - if type(value) == list: - return c_list_type(value[0]) - if value in builtin_types.keys(): - return value - return c_name(value) - eatspace = '\033EATSPACE.' pointer_suffix = ' *' + eatspace -# Map type @name to its C type expression. -# If @is_param, const-qualify the string type. -# -# This function is used for computing the full C type of 'member':'name'. -# A special suffix is added in c_type() for pointer types, and it's -# stripped in mcgen(). So please notice this when you check the return -# value of c_type() outside mcgen(). -def c_type(value, is_param=False): - if value == 'str': - if is_param: - return 'const char' + pointer_suffix - return 'char' + pointer_suffix - - elif value == 'int': - return 'int64_t' - elif (value == 'int8' or value == 'int16' or value == 'int32' or - value == 'int64' or value == 'uint8' or value == 'uint16' or - value == 'uint32' or value == 'uint64'): - return value + '_t' - elif value == 'size': - return 'uint64_t' - elif value == 'bool': - return 'bool' - elif value == 'number': - return 'double' - elif type(value) == list: - return c_list_type(value[0]) + pointer_suffix - elif is_enum(value): - return c_name(value) - elif value == None: - return 'void' - elif value in events: - return camel_case(value) + 'Event' + pointer_suffix - else: - # complex type name - assert isinstance(value, str) and value != "" - return c_name(value) + pointer_suffix - def genindent(count): ret = "" for i in range(count): @@ -1495,60 +1417,57 @@ def guardend(name): ''', name=guardname(name)) -def generate_enum_lookup(name, values, prefix=None): +def gen_enum_lookup(name, values, prefix=None): ret = mcgen(''' -const char *const %(name)s_lookup[] = { +const char *const %(c_name)s_lookup[] = { ''', - name=c_name(name)) + c_name=c_name(name)) for value in values: index = c_enum_const(name, value, prefix) ret += mcgen(''' [%(index)s] = "%(value)s", ''', - index = index, value = value) + index=index, value=value) max_index = c_enum_const(name, 'MAX', prefix) ret += mcgen(''' [%(max_index)s] = NULL, }; ''', - max_index=max_index) + max_index=max_index) return ret -def generate_enum(name, values, prefix=None): - name = c_name(name) - lookup_decl = mcgen(''' - -extern const char *const %(name)s_lookup[]; -''', - name=name) +def gen_enum(name, values, prefix=None): + # append automatically generated _MAX value + enum_values = values + ['MAX'] - enum_decl = mcgen(''' + ret = mcgen(''' -typedef enum %(name)s { +typedef enum %(c_name)s { ''', - name=name) - - # append automatically generated _MAX value - enum_values = values + [ 'MAX' ] + c_name=c_name(name)) i = 0 for value in enum_values: - enum_full_value = c_enum_const(name, value, prefix) - enum_decl += mcgen(''' - %(enum_full_value)s = %(i)d, + ret += mcgen(''' + %(c_enum)s = %(i)d, ''', - enum_full_value = enum_full_value, + c_enum=c_enum_const(name, value, prefix), i=i) i += 1 - enum_decl += mcgen(''' -} %(name)s; + ret += mcgen(''' +} %(c_name)s; ''', - name=name) + c_name=c_name(name)) + + ret += mcgen(''' - return enum_decl + lookup_decl +extern const char *const %(c_name)s_lookup[]; +''', + c_name=c_name(name)) + return ret # # Common command line parsing -- cgit v1.1 From 03b4367a556179e3e59affa535493427bd009e9d Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 16 Sep 2015 13:06:20 +0200 Subject: qapi: De-duplicate parameter list generation Generated qapi-event.[ch] lose line breaks. No change otherwise. Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Reviewed-by: Daniel P. Berrange Message-Id: <1442401589-24189-18-git-send-email-armbru@redhat.com> --- scripts/qapi.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'scripts/qapi.py') diff --git a/scripts/qapi.py b/scripts/qapi.py index 7ac72f6..6b6f1ae 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -1469,6 +1469,22 @@ extern const char *const %(c_name)s_lookup[]; c_name=c_name(name)) return ret +def gen_params(arg_type, extra): + if not arg_type: + return extra + assert not arg_type.variants + ret = '' + sep = '' + for memb in arg_type.members: + ret += sep + sep = ', ' + if memb.optional: + ret += 'bool has_%s, ' % c_name(memb.name) + ret += '%s %s' % (memb.type.c_type(is_param=True), c_name(memb.name)) + if extra: + ret += sep + extra + return ret + # # Common command line parsing # -- cgit v1.1 From 28770e057f265a4e70bcbdfc2447cce7b5f2dc19 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 16 Sep 2015 13:06:24 +0200 Subject: qapi: Introduce a first class 'any' type It's first class, because unlike '**', it actually works, i.e. doesn't require 'gen': false. '**' will go away next. Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Reviewed-by: Daniel P. Berrange --- scripts/qapi.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'scripts/qapi.py') diff --git a/scripts/qapi.py b/scripts/qapi.py index 6b6f1ae..6a21bd6 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -33,6 +33,7 @@ builtin_types = { 'uint32': 'QTYPE_QINT', 'uint64': 'QTYPE_QINT', 'size': 'QTYPE_QINT', + 'any': None, # any qtype_code possible, actually } # Whitelist of commands allowed to return a non-dictionary @@ -1102,8 +1103,7 @@ class QAPISchema(object): def _def_builtin_type(self, name, json_type, c_type, c_null): self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type, c_null)) - if name != '**': - self._make_array_type(name) # TODO really needed? + self._make_array_type(name) # TODO really needed? def _def_predefineds(self): for t in [('str', 'string', 'char' + pointer_suffix, 'NULL'), @@ -1119,8 +1119,9 @@ class QAPISchema(object): ('uint64', 'int', 'uint64_t', '0'), ('size', 'int', 'uint64_t', '0'), ('bool', 'boolean', 'bool', 'false'), - ('**', 'value', None, None)]: + ('any', 'value', 'QObject' + pointer_suffix, 'NULL')]: self._def_builtin_type(*t) + self._entity_dict['**'] = self.lookup_type('any') # TODO drop this alias def _make_implicit_enum_type(self, name, values): name = name + 'Kind' @@ -1270,6 +1271,8 @@ class QAPISchema(object): def visit(self, visitor): visitor.visit_begin(self) for name in sorted(self._entity_dict.keys()): + if self._entity_dict[name].name != name: + continue # ignore alias TODO drop alias and remove self._entity_dict[name].visit(visitor) visitor.visit_end() -- cgit v1.1 From 6eb3937e9b20319e1c4f4d53e906fda8f5ccda10 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 16 Sep 2015 13:06:25 +0200 Subject: qom: Don't use 'gen': false for qom-get, qom-set, object-add With the previous commit, the generated marshalers just work, and save us a bit of handwritten code. Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Reviewed-by: Daniel P. Berrange Message-Id: <1442401589-24189-23-git-send-email-armbru@redhat.com> --- scripts/qapi.py | 1 + 1 file changed, 1 insertion(+) (limited to 'scripts/qapi.py') diff --git a/scripts/qapi.py b/scripts/qapi.py index 6a21bd6..8808ae6 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -40,6 +40,7 @@ builtin_types = { returns_whitelist = [ # From QMP: 'human-monitor-command', + 'qom-get', 'query-migrate-cache-size', 'query-tpm-models', 'query-tpm-types', -- cgit v1.1 From 2d21291ae645955fcc4652ebfec81ad338169ac6 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 16 Sep 2015 13:06:27 +0200 Subject: qapi: Pseudo-type '**' is now unused, drop it 'gen': false needs to stay for now, because netdev_add is still using it. Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Reviewed-by: Daniel P. Berrange Message-Id: <1442401589-24189-25-git-send-email-armbru@redhat.com> --- scripts/qapi.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) (limited to 'scripts/qapi.py') diff --git a/scripts/qapi.py b/scripts/qapi.py index 8808ae6..f407297 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -428,15 +428,12 @@ def is_enum(name): def check_type(expr_info, source, value, allow_array = False, allow_dict = False, allow_optional = False, - allow_star = False, allow_metas = []): + allow_metas = []): global all_names if value is None: return - if allow_star and value == '**': - return - # Check if array type for value is okay if isinstance(value, list): if not allow_array: @@ -450,10 +447,6 @@ def check_type(expr_info, source, value, allow_array = False, # Check if type name for value is okay if isinstance(value, str): - if value == '**': - raise QAPIExprError(expr_info, - "%s uses '**' but did not request 'gen':false" - % source) if not value in all_names: raise QAPIExprError(expr_info, "%s uses unknown type '%s'" @@ -479,7 +472,7 @@ def check_type(expr_info, source, value, allow_array = False, # Todo: allow dictionaries to represent default values of # an optional argument. check_type(expr_info, "Member '%s' of %s" % (key, source), arg, - allow_array=True, allow_star=allow_star, + allow_array=True, allow_metas=['built-in', 'union', 'alternate', 'struct', 'enum']) @@ -499,18 +492,16 @@ def check_member_clash(expr_info, base_name, data, source = ""): def check_command(expr, expr_info): name = expr['command'] - allow_star = expr.has_key('gen') check_type(expr_info, "'data' for command '%s'" % name, expr.get('data'), allow_dict=True, allow_optional=True, - allow_metas=['struct'], allow_star=allow_star) + allow_metas=['struct']) returns_meta = ['union', 'struct'] if name in returns_whitelist: returns_meta += ['built-in', 'alternate', 'enum'] check_type(expr_info, "'returns' for command '%s'" % name, expr.get('returns'), allow_array=True, - allow_optional=True, allow_metas=returns_meta, - allow_star=allow_star) + allow_optional=True, allow_metas=returns_meta) def check_event(expr, expr_info): global events @@ -1122,7 +1113,6 @@ class QAPISchema(object): ('bool', 'boolean', 'bool', 'false'), ('any', 'value', 'QObject' + pointer_suffix, 'NULL')]: self._def_builtin_type(*t) - self._entity_dict['**'] = self.lookup_type('any') # TODO drop this alias def _make_implicit_enum_type(self, name, values): name = name + 'Kind' @@ -1272,8 +1262,6 @@ class QAPISchema(object): def visit(self, visitor): visitor.visit_begin(self) for name in sorted(self._entity_dict.keys()): - if self._entity_dict[name].name != name: - continue # ignore alias TODO drop alias and remove self._entity_dict[name].visit(visitor) visitor.visit_end() -- cgit v1.1 From 39a181581650f4d50f4445bc6276d9716cece050 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 16 Sep 2015 13:06:28 +0200 Subject: qapi: New QMP command query-qmp-schema for QMP introspection qapi/introspect.json defines the introspection schema. It's designed for QMP introspection, but should do for similar uses, such as QGA. The introspection schema does not reflect all the rules and restrictions that apply to QAPI schemata. A valid QAPI schema has an introspection value conforming to the introspection schema, but the converse is not true. Introspection lowers away a number of schema details, and makes implicit things explicit: * The built-in types are declared with their JSON type. All integer types are mapped to 'int', because how many bits we use internally is an implementation detail. It could be pressed into external interface service as very approximate range information, but that's a bad idea. If we need range information, we better do it properly. * Implicit type definitions are made explicit, and given auto-generated names: - Array types, named by appending "List" to the name of their element type, like in generated C. - The enumeration types implicitly defined by simple union types, named by appending "Kind" to the name of their simple union type, like in generated C. - Types that don't occur in generated C. Their names start with ':' so they don't clash with the user's names. * All type references are by name. * The struct and union types are generalized into an object type. * Base types are flattened. * Commands take a single argument and return a single result. Dictionary argument or list result is an implicit type definition. The empty object type is used when a command takes no arguments or produces no results. The argument is always of object type, but the introspection schema doesn't reflect that. The 'gen': false directive is omitted as implementation detail. The 'success-response' directive is omitted as well for now, even though it's not an implementation detail, because it's not used by QMP. * Events carry a single data value. Implicit type definition and empty object type use, just like for commands. The value is of object type, but the introspection schema doesn't reflect that. * Types not used by commands or events are omitted. Indirect use counts as use. * Optional members have a default, which can only be null right now Instead of a mandatory "optional" flag, we have an optional default. No default means mandatory, default null means optional without default value. Non-null is available for optional with default (possible future extension). * Clients should *not* look up types by name, because type names are not ABI. Look up the command or event you're interested in, then follow the references. TODO Should we hide the type names to eliminate the temptation? New generator scripts/qapi-introspect.py computes an introspection value for its input, and generates a C variable holding it. It can generate awfully long lines. Marked TODO. A new test-qmp-input-visitor test case feeds its result for both tests/qapi-schema/qapi-schema-test.json and qapi-schema.json to a QmpInputVisitor to verify it actually conforms to the schema. New QMP command query-qmp-schema takes its return value from that variable. Its reply is some 85KiBytes for me right now. If this turns out to be too much, we have a couple of options: * We can use shorter names in the JSON. Not the QMP style. * Optionally return the sub-schema for commands and events given as arguments. Right now qmp_query_schema() sends the string literal computed by qmp-introspect.py. To compute sub-schema at run time, we'd have to duplicate parts of qapi-introspect.py in C. Unattractive. * Let clients cache the output of query-qmp-schema. It changes only on QEMU upgrades, i.e. rarely. Provide a command query-qmp-schema-hash. Clients can have a cache indexed by hash, and re-query the schema only when they don't have it cached. Even simpler: put the hash in the QMP greeting. Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake --- scripts/qapi.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'scripts/qapi.py') diff --git a/scripts/qapi.py b/scripts/qapi.py index f407297..06478bb 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -788,6 +788,9 @@ class QAPISchemaVisitor(object): def visit_object_type(self, name, info, base, members, variants): pass + def visit_object_type_flat(self, name, info, members, variants): + pass + def visit_alternate_type(self, name, info, variants): pass @@ -943,6 +946,8 @@ class QAPISchemaObjectType(QAPISchemaType): def visit(self, visitor): visitor.visit_object_type(self.name, self.info, self.base, self.local_members, self.variants) + visitor.visit_object_type_flat(self.name, self.info, + self.members, self.variants) class QAPISchemaObjectTypeMember(object): @@ -1113,6 +1118,9 @@ class QAPISchema(object): ('bool', 'boolean', 'bool', 'false'), ('any', 'value', 'QObject' + pointer_suffix, 'NULL')]: self._def_builtin_type(*t) + self.the_empty_object_type = QAPISchemaObjectType(':empty', None, None, + [], None) + self._def_entity(self.the_empty_object_type) def _make_implicit_enum_type(self, name, values): name = name + 'Kind' @@ -1260,9 +1268,10 @@ class QAPISchema(object): ent.check(self) def visit(self, visitor): - visitor.visit_begin(self) + ignore = visitor.visit_begin(self) for name in sorted(self._entity_dict.keys()): - self._entity_dict[name].visit(visitor) + if not ignore or not isinstance(self._entity_dict[name], ignore): + self._entity_dict[name].visit(visitor) visitor.visit_end() -- cgit v1.1