From 967c885108f18e5065744719f7959ba5ea0a5b0d Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Tue, 3 Jul 2018 17:56:35 +0200 Subject: qapi: add 'if' to top-level expressions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Accept 'if' key in top-level elements, accepted as string or list of string type. The following patches will modify the test visitor to check the value is correctly saved, and generate #if/#endif code (as a single #if/endif line or a series for a list). Example of 'if' key: { 'struct': 'TestIfStruct', 'data': { 'foo': 'int' }, 'if': 'defined(TEST_IF_STRUCT)' } The generated code is for now *unconditional*. Later patches generate the conditionals. Signed-off-by: Marc-André Lureau Reviewed-by: Markus Armbruster Message-Id: <20180703155648.11933-2-marcandre.lureau@redhat.com> [Commit message and Documentation improved] Signed-off-by: Markus Armbruster --- scripts/qapi/common.py | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) (limited to 'scripts/qapi/common.py') diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index 8b6708dbf1..991045a478 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -638,6 +638,27 @@ def add_name(name, info, meta, implicit=False): all_names[name] = meta +def check_if(expr, info): + + def check_if_str(ifcond, info): + if not isinstance(ifcond, str): + raise QAPISemError( + info, "'if' condition must be a string or a list of strings") + if ifcond == '': + raise QAPISemError(info, "'if' condition '' makes no sense") + + ifcond = expr.get('if') + if ifcond is None: + return + if isinstance(ifcond, list): + if ifcond == []: + raise QAPISemError(info, "'if' condition [] is useless") + for elt in ifcond: + check_if_str(elt, info) + else: + check_if_str(ifcond, info) + + def check_type(info, source, value, allow_array=False, allow_dict=False, allow_optional=False, allow_metas=[]): @@ -871,6 +892,8 @@ def check_keys(expr_elem, meta, required, optional=[]): raise QAPISemError(info, "'%s' of %s '%s' should only use true value" % (key, meta, name)) + if key == 'if': + check_if(expr, info) for key in required: if key not in expr: raise QAPISemError(info, "Key '%s' is missing from %s '%s'" @@ -899,28 +922,28 @@ def check_exprs(exprs): if 'enum' in expr: meta = 'enum' - check_keys(expr_elem, 'enum', ['data'], ['prefix']) + check_keys(expr_elem, 'enum', ['data'], ['if', 'prefix']) enum_types[expr[meta]] = expr elif 'union' in expr: meta = 'union' check_keys(expr_elem, 'union', ['data'], - ['base', 'discriminator']) + ['base', 'discriminator', 'if']) union_types[expr[meta]] = expr elif 'alternate' in expr: meta = 'alternate' - check_keys(expr_elem, 'alternate', ['data']) + check_keys(expr_elem, 'alternate', ['data'], ['if']) elif 'struct' in expr: meta = 'struct' - check_keys(expr_elem, 'struct', ['data'], ['base']) + check_keys(expr_elem, 'struct', ['data'], ['base', 'if']) struct_types[expr[meta]] = expr elif 'command' in expr: meta = 'command' check_keys(expr_elem, 'command', [], ['data', 'returns', 'gen', 'success-response', - 'boxed', 'allow-oob', 'allow-preconfig']) + 'boxed', 'allow-oob', 'allow-preconfig', 'if']) elif 'event' in expr: meta = 'event' - check_keys(expr_elem, 'event', [], ['data', 'boxed']) + check_keys(expr_elem, 'event', [], ['data', 'boxed', 'if']) else: raise QAPISemError(expr_elem['info'], "Expression is missing metatype") -- cgit 1.4.1 From 2cbc94376e718448699036be7f6e29ab75312b70 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Tue, 3 Jul 2018 17:56:36 +0200 Subject: qapi: pass 'if' condition into QAPISchemaEntity objects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Built-in objects remain unconditional. Explicitly defined objects use the condition specified in the schema. Implicitly defined objects inherit their condition from their users. For most of them, there is exactly one user, so the condition to use is obvious. The exception is wrapped types generated for simple union variants, which can be shared by any number of simple unions. The tight condition would be the disjunction of the conditions of these simple unions. For now, use the wrapped type's condition instead. Much simpler and good enough for now. Signed-off-by: Marc-André Lureau Reviewed-by: Markus Armbruster Message-Id: <20180703155648.11933-3-marcandre.lureau@redhat.com> Signed-off-by: Markus Armbruster --- scripts/qapi/common.py | 97 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 65 insertions(+), 32 deletions(-) (limited to 'scripts/qapi/common.py') diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index 991045a478..4f4014b387 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -1001,8 +1001,16 @@ def check_exprs(exprs): # Schema compiler frontend # +def listify_cond(ifcond): + if not ifcond: + return [] + if not isinstance(ifcond, list): + return [ifcond] + return ifcond + + class QAPISchemaEntity(object): - def __init__(self, name, info, doc): + def __init__(self, name, info, doc, ifcond=None): assert name is None or isinstance(name, str) self.name = name self.module = None @@ -1013,6 +1021,7 @@ class QAPISchemaEntity(object): # such place). self.info = info self.doc = doc + self.ifcond = listify_cond(ifcond) def c_name(self): return c_name(self.name) @@ -1145,8 +1154,8 @@ class QAPISchemaBuiltinType(QAPISchemaType): class QAPISchemaEnumType(QAPISchemaType): - def __init__(self, name, info, doc, values, prefix): - QAPISchemaType.__init__(self, name, info, doc) + def __init__(self, name, info, doc, ifcond, values, prefix): + QAPISchemaType.__init__(self, name, info, doc, ifcond) for v in values: assert isinstance(v, QAPISchemaMember) v.set_owner(name) @@ -1181,7 +1190,7 @@ class QAPISchemaEnumType(QAPISchemaType): class QAPISchemaArrayType(QAPISchemaType): def __init__(self, name, info, element_type): - QAPISchemaType.__init__(self, name, info, None) + QAPISchemaType.__init__(self, name, info, None, None) assert isinstance(element_type, str) self._element_type_name = element_type self.element_type = None @@ -1189,6 +1198,7 @@ class QAPISchemaArrayType(QAPISchemaType): def check(self, schema): self.element_type = schema.lookup_type(self._element_type_name) assert self.element_type + self.ifcond = self.element_type.ifcond def is_implicit(self): return True @@ -1210,11 +1220,12 @@ class QAPISchemaArrayType(QAPISchemaType): class QAPISchemaObjectType(QAPISchemaType): - def __init__(self, name, info, doc, base, local_members, variants): + def __init__(self, name, info, doc, ifcond, + base, local_members, variants): # struct has local_members, optional base, and no variants # flat union has base, variants, and no local_members # simple union has local_members, variants, and no base - QAPISchemaType.__init__(self, name, info, doc) + QAPISchemaType.__init__(self, name, info, doc, ifcond) assert base is None or isinstance(base, str) for m in local_members: assert isinstance(m, QAPISchemaObjectTypeMember) @@ -1410,8 +1421,8 @@ class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember): class QAPISchemaAlternateType(QAPISchemaType): - def __init__(self, name, info, doc, variants): - QAPISchemaType.__init__(self, name, info, doc) + def __init__(self, name, info, doc, ifcond, variants): + QAPISchemaType.__init__(self, name, info, doc, ifcond) assert isinstance(variants, QAPISchemaObjectTypeVariants) assert variants.tag_member variants.set_owner(name) @@ -1447,9 +1458,9 @@ class QAPISchemaAlternateType(QAPISchemaType): class QAPISchemaCommand(QAPISchemaEntity): - def __init__(self, name, info, doc, arg_type, ret_type, + def __init__(self, name, info, doc, ifcond, arg_type, ret_type, gen, success_response, boxed, allow_oob, allow_preconfig): - QAPISchemaEntity.__init__(self, name, info, doc) + QAPISchemaEntity.__init__(self, name, info, doc, ifcond) assert not arg_type or isinstance(arg_type, str) assert not ret_type or isinstance(ret_type, str) self._arg_type_name = arg_type @@ -1490,8 +1501,8 @@ class QAPISchemaCommand(QAPISchemaEntity): class QAPISchemaEvent(QAPISchemaEntity): - def __init__(self, name, info, doc, arg_type, boxed): - QAPISchemaEntity.__init__(self, name, info, doc) + def __init__(self, name, info, doc, ifcond, arg_type, boxed): + QAPISchemaEntity.__init__(self, name, info, doc, ifcond) assert not arg_type or isinstance(arg_type, str) self._arg_type_name = arg_type self.arg_type = None @@ -1590,22 +1601,22 @@ class QAPISchema(object): ('null', 'null', 'QNull' + pointer_suffix)]: self._def_builtin_type(*t) self.the_empty_object_type = QAPISchemaObjectType( - 'q_empty', None, None, None, [], None) + 'q_empty', None, None, None, None, [], None) self._def_entity(self.the_empty_object_type) qtype_values = self._make_enum_members(['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool']) - self._def_entity(QAPISchemaEnumType('QType', None, None, + self._def_entity(QAPISchemaEnumType('QType', None, None, None, qtype_values, 'QTYPE')) def _make_enum_members(self, values): return [QAPISchemaMember(v) for v in values] - def _make_implicit_enum_type(self, name, info, values): + def _make_implicit_enum_type(self, name, info, ifcond, values): # See also QAPISchemaObjectTypeMember._pretty_owner() name = name + 'Kind' # Use namespace reserved by add_name() self._def_entity(QAPISchemaEnumType( - name, info, None, self._make_enum_members(values), None)) + name, info, None, ifcond, self._make_enum_members(values), None)) return name def _make_array_type(self, element_type, info): @@ -1614,22 +1625,37 @@ class QAPISchema(object): self._def_entity(QAPISchemaArrayType(name, info, element_type)) return name - def _make_implicit_object_type(self, name, info, doc, role, members): + def _make_implicit_object_type(self, name, info, doc, ifcond, + role, members): if not members: return None # See also QAPISchemaObjectTypeMember._pretty_owner() name = 'q_obj_%s-%s' % (name, role) - if not self.lookup_entity(name, QAPISchemaObjectType): - self._def_entity(QAPISchemaObjectType(name, info, doc, None, - members, None)) + typ = self.lookup_entity(name, QAPISchemaObjectType) + if typ: + # The implicit object type has multiple users. This can + # happen only for simple unions' implicit wrapper types. + # Its ifcond should be the disjunction of its user's + # ifconds. Not implemented. Instead, we always pass the + # wrapped type's ifcond, which is trivially the same for all + # users. It's also necessary for the wrapper to compile. + # But it's not tight: the disjunction need not imply it. We + # may end up compiling useless wrapper types. + # TODO kill simple unions or implement the disjunction + assert ifcond == typ.ifcond + else: + self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond, + None, members, None)) return name def _def_enum_type(self, expr, info, doc): name = expr['enum'] data = expr['data'] prefix = expr.get('prefix') + ifcond = expr.get('if') self._def_entity(QAPISchemaEnumType( - name, info, doc, self._make_enum_members(data), prefix)) + name, info, doc, ifcond, + self._make_enum_members(data), prefix)) def _make_member(self, name, typ, info): optional = False @@ -1649,7 +1675,8 @@ class QAPISchema(object): name = expr['struct'] base = expr.get('base') data = expr['data'] - self._def_entity(QAPISchemaObjectType(name, info, doc, base, + ifcond = expr.get('if') + self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond, base, self._make_members(data, info), None)) @@ -1661,18 +1688,21 @@ class QAPISchema(object): assert len(typ) == 1 typ = self._make_array_type(typ[0], info) typ = self._make_implicit_object_type( - typ, info, None, 'wrapper', [self._make_member('data', typ, info)]) + typ, info, None, self.lookup_type(typ).ifcond, + 'wrapper', [self._make_member('data', typ, info)]) return QAPISchemaObjectTypeVariant(case, typ) def _def_union_type(self, expr, info, doc): name = expr['union'] data = expr['data'] base = expr.get('base') + ifcond = expr.get('if') tag_name = expr.get('discriminator') tag_member = None if isinstance(base, dict): - base = (self._make_implicit_object_type( - name, info, doc, 'base', self._make_members(base, info))) + base = self._make_implicit_object_type( + name, info, doc, ifcond, + 'base', self._make_members(base, info)) if tag_name: variants = [self._make_variant(key, value) for (key, value) in data.items()] @@ -1680,12 +1710,12 @@ class QAPISchema(object): else: variants = [self._make_simple_variant(key, value, info) for (key, value) in data.items()] - typ = self._make_implicit_enum_type(name, info, + typ = self._make_implicit_enum_type(name, info, ifcond, [v.name for v in variants]) tag_member = QAPISchemaObjectTypeMember('type', typ, False) members = [tag_member] self._def_entity( - QAPISchemaObjectType(name, info, doc, base, members, + QAPISchemaObjectType(name, info, doc, ifcond, base, members, QAPISchemaObjectTypeVariants(tag_name, tag_member, variants))) @@ -1693,11 +1723,12 @@ class QAPISchema(object): def _def_alternate_type(self, expr, info, doc): name = expr['alternate'] data = expr['data'] + ifcond = expr.get('if') variants = [self._make_variant(key, value) for (key, value) in data.items()] tag_member = QAPISchemaObjectTypeMember('type', 'QType', False) self._def_entity( - QAPISchemaAlternateType(name, info, doc, + QAPISchemaAlternateType(name, info, doc, ifcond, QAPISchemaObjectTypeVariants(None, tag_member, variants))) @@ -1711,13 +1742,14 @@ class QAPISchema(object): boxed = expr.get('boxed', False) allow_oob = expr.get('allow-oob', False) allow_preconfig = expr.get('allow-preconfig', False) + ifcond = expr.get('if') if isinstance(data, OrderedDict): data = self._make_implicit_object_type( - name, info, doc, 'arg', self._make_members(data, info)) + name, info, doc, ifcond, 'arg', self._make_members(data, info)) if isinstance(rets, list): assert len(rets) == 1 rets = self._make_array_type(rets[0], info) - self._def_entity(QAPISchemaCommand(name, info, doc, data, rets, + self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, data, rets, gen, success_response, boxed, allow_oob, allow_preconfig)) @@ -1725,10 +1757,11 @@ class QAPISchema(object): name = expr['event'] data = expr.get('data') boxed = expr.get('boxed', False) + ifcond = expr.get('if') if isinstance(data, OrderedDict): data = self._make_implicit_object_type( - name, info, doc, 'arg', self._make_members(data, info)) - self._def_entity(QAPISchemaEvent(name, info, doc, data, boxed)) + name, info, doc, ifcond, 'arg', self._make_members(data, info)) + self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, data, boxed)) def _def_exprs(self, exprs): for expr_elem in exprs: -- cgit 1.4.1 From 4fca21c1b043cb1ef2e197ef15e7474ba668d925 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Tue, 3 Jul 2018 17:56:37 +0200 Subject: qapi: leave the ifcond attribute undefined until check() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We commonly initialize attributes to None in .init(), then set their real value in .check(). Accessing the attribute before .check() yields None. If we're lucky, the code that accesses the attribute prematurely chokes on None. It won't for .ifcond, because None is a legitimate value. Leave the ifcond attribute undefined until check(). Suggested-by: Markus Armbruster Signed-off-by: Marc-André Lureau Reviewed-by: Markus Armbruster Message-Id: <20180703155648.11933-4-marcandre.lureau@redhat.com> Signed-off-by: Markus Armbruster --- scripts/qapi/common.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) (limited to 'scripts/qapi/common.py') diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index 4f4014b387..46e33e23e4 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -1021,13 +1021,19 @@ class QAPISchemaEntity(object): # such place). self.info = info self.doc = doc - self.ifcond = listify_cond(ifcond) + self._ifcond = ifcond # self.ifcond is set only after .check() def c_name(self): return c_name(self.name) def check(self, schema): - pass + if isinstance(self._ifcond, QAPISchemaType): + # inherit the condition from a type + typ = self._ifcond + typ.check(schema) + self.ifcond = typ.ifcond + else: + self.ifcond = listify_cond(self._ifcond) def is_implicit(self): return not self.info @@ -1164,6 +1170,7 @@ class QAPISchemaEnumType(QAPISchemaType): self.prefix = prefix def check(self, schema): + QAPISchemaType.check(self, schema) seen = {} for v in self.values: v.check_clash(self.info, seen) @@ -1196,8 +1203,10 @@ class QAPISchemaArrayType(QAPISchemaType): self.element_type = None def check(self, schema): + QAPISchemaType.check(self, schema) self.element_type = schema.lookup_type(self._element_type_name) assert self.element_type + self.element_type.check(schema) self.ifcond = self.element_type.ifcond def is_implicit(self): @@ -1240,6 +1249,7 @@ class QAPISchemaObjectType(QAPISchemaType): self.members = None def check(self, schema): + QAPISchemaType.check(self, schema) if self.members is False: # check for cycles raise QAPISemError(self.info, "Object %s contains itself" % self.name) @@ -1430,6 +1440,7 @@ class QAPISchemaAlternateType(QAPISchemaType): self.variants = variants def check(self, schema): + QAPISchemaType.check(self, schema) self.variants.tag_member.check(schema) # Not calling self.variants.check_clash(), because there's nothing # to clash with @@ -1474,6 +1485,7 @@ class QAPISchemaCommand(QAPISchemaEntity): self.allow_preconfig = allow_preconfig def check(self, schema): + QAPISchemaEntity.check(self, schema) if self._arg_type_name: self.arg_type = schema.lookup_type(self._arg_type_name) assert (isinstance(self.arg_type, QAPISchemaObjectType) or @@ -1509,6 +1521,7 @@ class QAPISchemaEvent(QAPISchemaEntity): self.boxed = boxed def check(self, schema): + QAPISchemaEntity.check(self, schema) if self._arg_type_name: self.arg_type = schema.lookup_type(self._arg_type_name) assert (isinstance(self.arg_type, QAPISchemaObjectType) or @@ -1642,7 +1655,7 @@ class QAPISchema(object): # But it's not tight: the disjunction need not imply it. We # may end up compiling useless wrapper types. # TODO kill simple unions or implement the disjunction - assert ifcond == typ.ifcond + assert ifcond == typ._ifcond # pylint: disable=protected-access else: self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond, None, members, None)) @@ -1688,7 +1701,7 @@ class QAPISchema(object): assert len(typ) == 1 typ = self._make_array_type(typ[0], info) typ = self._make_implicit_object_type( - typ, info, None, self.lookup_type(typ).ifcond, + typ, info, None, self.lookup_type(typ), 'wrapper', [self._make_member('data', typ, info)]) return QAPISchemaObjectTypeVariant(case, typ) -- cgit 1.4.1 From fbf09a2fa4d9460033023e56cc1b195be053b353 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Tue, 3 Jul 2018 17:56:38 +0200 Subject: qapi: add 'ifcond' to visitor methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modify the test visitor to check correct passing of values. Signed-off-by: Marc-André Lureau Reviewed-by: Markus Armbruster Message-Id: <20180703155648.11933-5-marcandre.lureau@redhat.com> [Accidental change to roms/seabios dropped] Signed-off-by: Markus Armbruster --- scripts/qapi/commands.py | 2 +- scripts/qapi/common.py | 31 +++++++++++++++++-------------- scripts/qapi/doc.py | 10 +++++----- scripts/qapi/events.py | 2 +- scripts/qapi/introspect.py | 12 ++++++------ scripts/qapi/types.py | 8 ++++---- scripts/qapi/visit.py | 8 ++++---- tests/qapi-schema/qapi-schema-test.out | 9 +++++++++ tests/qapi-schema/test-qapi.py | 20 +++++++++++++++----- 9 files changed, 62 insertions(+), 40 deletions(-) mode change 100644 => 100755 scripts/qapi/doc.py (limited to 'scripts/qapi/common.py') diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py index 3b0867c14f..dcc03c7859 100644 --- a/scripts/qapi/commands.py +++ b/scripts/qapi/commands.py @@ -277,7 +277,7 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds); c_prefix=c_name(self._prefix, protect=False))) genc.add(gen_registry(self._regy, self._prefix)) - def visit_command(self, name, info, arg_type, ret_type, gen, + def visit_command(self, name, info, ifcond, arg_type, ret_type, gen, success_response, boxed, allow_oob, allow_preconfig): if not gen: return diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index 46e33e23e4..feae646e09 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -1062,26 +1062,26 @@ class QAPISchemaVisitor(object): def visit_builtin_type(self, name, info, json_type): pass - def visit_enum_type(self, name, info, values, prefix): + def visit_enum_type(self, name, info, ifcond, values, prefix): pass - def visit_array_type(self, name, info, element_type): + def visit_array_type(self, name, info, ifcond, element_type): pass - def visit_object_type(self, name, info, base, members, variants): + def visit_object_type(self, name, info, ifcond, base, members, variants): pass - def visit_object_type_flat(self, name, info, members, variants): + def visit_object_type_flat(self, name, info, ifcond, members, variants): pass - def visit_alternate_type(self, name, info, variants): + def visit_alternate_type(self, name, info, ifcond, variants): pass - def visit_command(self, name, info, arg_type, ret_type, gen, + def visit_command(self, name, info, ifcond, arg_type, ret_type, gen, success_response, boxed, allow_oob, allow_preconfig): pass - def visit_event(self, name, info, arg_type, boxed): + def visit_event(self, name, info, ifcond, arg_type, boxed): pass @@ -1191,7 +1191,7 @@ class QAPISchemaEnumType(QAPISchemaType): return 'string' def visit(self, visitor): - visitor.visit_enum_type(self.name, self.info, + visitor.visit_enum_type(self.name, self.info, self.ifcond, self.member_names(), self.prefix) @@ -1225,7 +1225,8 @@ class QAPISchemaArrayType(QAPISchemaType): return 'array of ' + elt_doc_type def visit(self, visitor): - visitor.visit_array_type(self.name, self.info, self.element_type) + visitor.visit_array_type(self.name, self.info, self.ifcond, + self.element_type) class QAPISchemaObjectType(QAPISchemaType): @@ -1307,9 +1308,9 @@ class QAPISchemaObjectType(QAPISchemaType): return 'object' def visit(self, visitor): - visitor.visit_object_type(self.name, self.info, + visitor.visit_object_type(self.name, self.info, self.ifcond, self.base, self.local_members, self.variants) - visitor.visit_object_type_flat(self.name, self.info, + visitor.visit_object_type_flat(self.name, self.info, self.ifcond, self.members, self.variants) @@ -1462,7 +1463,8 @@ class QAPISchemaAlternateType(QAPISchemaType): return 'value' def visit(self, visitor): - visitor.visit_alternate_type(self.name, self.info, self.variants) + visitor.visit_alternate_type(self.name, self.info, self.ifcond, + self.variants) def is_empty(self): return False @@ -1505,7 +1507,7 @@ class QAPISchemaCommand(QAPISchemaEntity): assert isinstance(self.ret_type, QAPISchemaType) def visit(self, visitor): - visitor.visit_command(self.name, self.info, + visitor.visit_command(self.name, self.info, self.ifcond, self.arg_type, self.ret_type, self.gen, self.success_response, self.boxed, self.allow_oob, @@ -1538,7 +1540,8 @@ class QAPISchemaEvent(QAPISchemaEntity): raise QAPISemError(self.info, "Use of 'boxed' requires 'data'") def visit(self, visitor): - visitor.visit_event(self.name, self.info, self.arg_type, self.boxed) + visitor.visit_event(self.name, self.info, self.ifcond, + self.arg_type, self.boxed) class QAPISchema(object): diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py old mode 100644 new mode 100755 index b5630844f9..4db6674dc3 --- a/scripts/qapi/doc.py +++ b/scripts/qapi/doc.py @@ -204,14 +204,14 @@ class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor): def write(self, output_dir): self._gen.write(output_dir, self._prefix + 'qapi-doc.texi') - def visit_enum_type(self, name, info, values, prefix): + def visit_enum_type(self, name, info, ifcond, values, prefix): doc = self.cur_doc self._gen.add(TYPE_FMT(type='Enum', name=doc.symbol, body=texi_entity(doc, 'Values', member_func=texi_enum_value))) - def visit_object_type(self, name, info, base, members, variants): + def visit_object_type(self, name, info, ifcond, base, members, variants): doc = self.cur_doc if base and base.is_implicit(): base = None @@ -220,13 +220,13 @@ class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor): body=texi_entity(doc, 'Members', base, variants))) - def visit_alternate_type(self, name, info, variants): + def visit_alternate_type(self, name, info, ifcond, variants): doc = self.cur_doc self._gen.add(TYPE_FMT(type='Alternate', name=doc.symbol, body=texi_entity(doc, 'Members'))) - def visit_command(self, name, info, arg_type, ret_type, gen, + def visit_command(self, name, info, ifcond, arg_type, ret_type, gen, success_response, boxed, allow_oob, allow_preconfig): doc = self.cur_doc if boxed: @@ -240,7 +240,7 @@ class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor): name=doc.symbol, body=body)) - def visit_event(self, name, info, arg_type, boxed): + def visit_event(self, name, info, ifcond, arg_type, boxed): doc = self.cur_doc self._gen.add(MSG_FMT(type='Event', name=doc.symbol, diff --git a/scripts/qapi/events.py b/scripts/qapi/events.py index 5657524688..0a1afac134 100644 --- a/scripts/qapi/events.py +++ b/scripts/qapi/events.py @@ -184,7 +184,7 @@ class QAPISchemaGenEventVisitor(QAPISchemaModularCVisitor): genh.add(gen_enum(self._enum_name, self._event_names)) genc.add(gen_enum_lookup(self._enum_name, self._event_names)) - def visit_event(self, name, info, arg_type, boxed): + def visit_event(self, name, info, ifcond, arg_type, boxed): self._genh.add(gen_event_send_decl(name, arg_type, boxed)) self._genc.add(gen_event_send(name, arg_type, boxed, self._enum_name)) self._event_names.append(name) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 6ad198ae5b..245cfdfb65 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -149,26 +149,26 @@ const QLitObject %(c_name)s = %(c_string)s; def visit_builtin_type(self, name, info, json_type): self._gen_qlit(name, 'builtin', {'json-type': json_type}) - def visit_enum_type(self, name, info, values, prefix): + def visit_enum_type(self, name, info, ifcond, values, prefix): self._gen_qlit(name, 'enum', {'values': values}) - def visit_array_type(self, name, info, element_type): + def visit_array_type(self, name, info, ifcond, element_type): element = self._use_type(element_type) self._gen_qlit('[' + element + ']', 'array', {'element-type': element}) - def visit_object_type_flat(self, name, info, members, variants): + def visit_object_type_flat(self, name, info, ifcond, members, variants): obj = {'members': [self._gen_member(m) for m in members]} if variants: obj.update(self._gen_variants(variants.tag_member.name, variants.variants)) self._gen_qlit(name, 'object', obj) - def visit_alternate_type(self, name, info, variants): + def visit_alternate_type(self, name, info, ifcond, variants): self._gen_qlit(name, 'alternate', {'members': [{'type': self._use_type(m.type)} for m in variants.variants]}) - def visit_command(self, name, info, arg_type, ret_type, gen, + def visit_command(self, name, info, ifcond, arg_type, ret_type, gen, success_response, boxed, allow_oob, allow_preconfig): arg_type = arg_type or self._schema.the_empty_object_type ret_type = ret_type or self._schema.the_empty_object_type @@ -178,7 +178,7 @@ const QLitObject %(c_name)s = %(c_string)s; 'allow-oob': allow_oob, 'allow-preconfig': allow_preconfig}) - def visit_event(self, name, info, arg_type, boxed): + def visit_event(self, name, info, ifcond, arg_type, boxed): arg_type = arg_type or self._schema.the_empty_object_type self._gen_qlit(name, 'event', {'arg-type': self._use_type(arg_type)}) diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py index a599352e59..659075f884 100644 --- a/scripts/qapi/types.py +++ b/scripts/qapi/types.py @@ -208,16 +208,16 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor): self._genh.add(gen_type_cleanup_decl(name)) self._genc.add(gen_type_cleanup(name)) - def visit_enum_type(self, name, info, values, prefix): + def visit_enum_type(self, name, info, ifcond, values, prefix): self._genh.preamble_add(gen_enum(name, values, prefix)) self._genc.add(gen_enum_lookup(name, values, prefix)) - def visit_array_type(self, name, info, element_type): + def visit_array_type(self, name, info, ifcond, element_type): self._genh.preamble_add(gen_fwd_object_or_array(name)) self._genh.add(gen_array(name, element_type)) self._gen_type_cleanup(name) - def visit_object_type(self, name, info, base, members, variants): + def visit_object_type(self, name, info, ifcond, base, members, variants): # Nothing to do for the special empty builtin if name == 'q_empty': return @@ -231,7 +231,7 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor): # implicit types won't be directly allocated/freed self._gen_type_cleanup(name) - def visit_alternate_type(self, name, info, variants): + def visit_alternate_type(self, name, info, ifcond, variants): self._genh.preamble_add(gen_fwd_object_or_array(name)) self._genh.add(gen_object(name, None, [variants.tag_member], variants)) diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py index bdcafb64ee..34fe1ef5eb 100644 --- a/scripts/qapi/visit.py +++ b/scripts/qapi/visit.py @@ -310,15 +310,15 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor): ''', types=types)) - def visit_enum_type(self, name, info, values, prefix): + def visit_enum_type(self, name, info, ifcond, values, prefix): self._genh.add(gen_visit_decl(name, scalar=True)) self._genc.add(gen_visit_enum(name)) - def visit_array_type(self, name, info, element_type): + def visit_array_type(self, name, info, ifcond, element_type): self._genh.add(gen_visit_decl(name)) self._genc.add(gen_visit_list(name, element_type)) - def visit_object_type(self, name, info, base, members, variants): + def visit_object_type(self, name, info, ifcond, base, members, variants): # Nothing to do for the special empty builtin if name == 'q_empty': return @@ -331,7 +331,7 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor): self._genh.add(gen_visit_decl(name)) self._genc.add(gen_visit_object(name, base, members, variants)) - def visit_alternate_type(self, name, info, variants): + def visit_alternate_type(self, name, info, ifcond, variants): self._genh.add(gen_visit_decl(name)) self._genc.add(gen_visit_alternate(name, variants)) diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index ed25e5b60c..0da92455da 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -237,25 +237,34 @@ command __org.qemu_x-command q_obj___org.qemu_x-command-arg -> __org.qemu_x-Unio gen=True success_response=True boxed=False oob=False preconfig=False object TestIfStruct member foo: int optional=False + if ['defined(TEST_IF_STRUCT)'] enum TestIfEnum ['foo', 'bar'] + if ['defined(TEST_IF_ENUM)'] object q_obj_TestStruct-wrapper member data: TestStruct optional=False enum TestIfUnionKind ['foo'] + if ['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)'] object TestIfUnion member type: TestIfUnionKind optional=False tag type case foo: q_obj_TestStruct-wrapper + if ['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)'] alternate TestIfAlternate tag type case foo: int case bar: TestStruct + if ['defined(TEST_IF_ALT) && defined(TEST_IF_STRUCT)'] object q_obj_TestIfCmd-arg member foo: TestIfStruct optional=False + if ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'] command TestIfCmd q_obj_TestIfCmd-arg -> UserDefThree gen=True success_response=True boxed=False oob=False preconfig=False + if ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'] command TestCmdReturnDefThree None -> UserDefThree gen=True success_response=True boxed=False oob=False preconfig=False object q_obj_TestIfEvent-arg member foo: TestIfStruct optional=False + if ['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)'] event TestIfEvent q_obj_TestIfEvent-arg boxed=False + if ['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)'] diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py index 4512a41504..f514fe71e4 100644 --- a/tests/qapi-schema/test-qapi.py +++ b/tests/qapi-schema/test-qapi.py @@ -23,12 +23,13 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): def visit_include(self, name, info): print('include %s' % name) - def visit_enum_type(self, name, info, values, prefix): + def visit_enum_type(self, name, info, ifcond, values, prefix): print('enum %s %s' % (name, values)) if prefix: print(' prefix %s' % prefix) + self._print_if(ifcond) - def visit_object_type(self, name, info, base, members, variants): + def visit_object_type(self, name, info, ifcond, base, members, variants): print('object %s' % name) if base: print(' base %s' % base.name) @@ -36,21 +37,25 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): print(' member %s: %s optional=%s' % \ (m.name, m.type.name, m.optional)) self._print_variants(variants) + self._print_if(ifcond) - def visit_alternate_type(self, name, info, variants): + def visit_alternate_type(self, name, info, ifcond, variants): print('alternate %s' % name) self._print_variants(variants) + self._print_if(ifcond) - def visit_command(self, name, info, arg_type, ret_type, gen, + def visit_command(self, name, info, ifcond, arg_type, ret_type, gen, success_response, boxed, allow_oob, allow_preconfig): print('command %s %s -> %s' % \ (name, arg_type and arg_type.name, ret_type and ret_type.name)) print(' gen=%s success_response=%s boxed=%s oob=%s preconfig=%s' % \ (gen, success_response, boxed, allow_oob, allow_preconfig)) + self._print_if(ifcond) - def visit_event(self, name, info, arg_type, boxed): + def visit_event(self, name, info, ifcond, arg_type, boxed): print('event %s %s' % (name, arg_type and arg_type.name)) print(' boxed=%s' % boxed) + self._print_if(ifcond) @staticmethod def _print_variants(variants): @@ -59,6 +64,11 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): for v in variants.variants: print(' case %s: %s' % (v.name, v.type.name)) + @staticmethod + def _print_if(ifcond, indent=4): + if ifcond: + print('%sif %s' % (' ' * indent, ifcond)) + try: schema = QAPISchema(sys.argv[1]) -- cgit 1.4.1 From 485d948ce86f5a096dc848ec31b70cd66452d40d Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Tue, 3 Jul 2018 17:56:39 +0200 Subject: qapi: mcgen() shouldn't indent # lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Skip preprocessor lines when adding indentation, since that would likely result in invalid code. Signed-off-by: Marc-André Lureau Reviewed-by: Markus Armbruster Message-Id: <20180703155648.11933-6-marcandre.lureau@redhat.com> Signed-off-by: Markus Armbruster --- scripts/qapi/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'scripts/qapi/common.py') diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index feae646e09..1b56065a80 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -1941,8 +1941,8 @@ def cgen(code, **kwds): if indent_level: indent = genindent(indent_level) # re.subn() lacks flags support before Python 2.7, use re.compile() - raw = re.subn(re.compile(r'^.', re.MULTILINE), - indent + r'\g<0>', raw) + raw = re.subn(re.compile(r'^(?!(#|$))', re.MULTILINE), + indent, raw) raw = raw[0] return re.sub(re.escape(eatspace) + r' *', '', raw) -- cgit 1.4.1 From ded9fc28b5a07213f3e5e8ac7ea0494b85813de1 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Tue, 3 Jul 2018 17:56:40 +0200 Subject: qapi: add #if/#endif helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add helpers to wrap generated code with #if/#endif lines. A later patch wants to use QAPIGen for generating C snippets rather than full C files with copyright headers etc. Splice in class QAPIGenCCode between QAPIGen and QAPIGenC. Add a 'with' statement context manager that will be used to wrap generator visitor methods. The manager will check if code was generated before adding #if/#endif lines on QAPIGenCSnippet objects. Used in the following patches. Signed-off-by: Marc-André Lureau Message-Id: <20180703155648.11933-7-marcandre.lureau@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/common.py | 98 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 4 deletions(-) (limited to 'scripts/qapi/common.py') diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index 1b56065a80..9230a2a3e8 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -12,6 +12,7 @@ # See the COPYING file in the top-level directory. from __future__ import print_function +from contextlib import contextmanager import errno import os import re @@ -1974,6 +1975,40 @@ def guardend(name): name=guardname(name)) +def gen_if(ifcond): + ret = '' + for ifc in ifcond: + ret += mcgen(''' +#if %(cond)s +''', cond=ifc) + return ret + + +def gen_endif(ifcond): + ret = '' + for ifc in reversed(ifcond): + ret += mcgen(''' +#endif /* %(cond)s */ +''', cond=ifc) + return ret + + +def _wrap_ifcond(ifcond, before, after): + if before == after: + return after # suppress empty #if ... #endif + + assert after.startswith(before) + out = before + added = after[len(before):] + if added[0] == '\n': + out += '\n' + added = added[1:] + out += gen_if(ifcond) + out += added + out += gen_endif(ifcond) + return out + + def gen_enum_lookup(name, values, prefix=None): ret = mcgen(''' @@ -2071,6 +2106,10 @@ class QAPIGen(object): def add(self, text): self._body += text + def get_content(self, fname=None): + return (self._top(fname) + self._preamble + self._body + + self._bottom(fname)) + def _top(self, fname): return '' @@ -2091,8 +2130,7 @@ class QAPIGen(object): f = open(fd, 'r+', encoding='utf-8') else: f = os.fdopen(fd, 'r+') - text = (self._top(fname) + self._preamble + self._body - + self._bottom(fname)) + text = self.get_content(fname) oldtext = f.read(len(text) + 1) if text != oldtext: f.seek(0) @@ -2101,10 +2139,62 @@ class QAPIGen(object): f.close() -class QAPIGenC(QAPIGen): +@contextmanager +def ifcontext(ifcond, *args): + """A 'with' statement context manager to wrap with start_if()/end_if() - def __init__(self, blurb, pydoc): + *args: any number of QAPIGenCCode + + Example:: + + with ifcontext(ifcond, self._genh, self._genc): + modify self._genh and self._genc ... + + Is equivalent to calling:: + + self._genh.start_if(ifcond) + self._genc.start_if(ifcond) + modify self._genh and self._genc ... + self._genh.end_if() + self._genc.end_if() + """ + for arg in args: + arg.start_if(ifcond) + yield + for arg in args: + arg.end_if() + + +class QAPIGenCCode(QAPIGen): + + def __init__(self): QAPIGen.__init__(self) + self._start_if = None + + def start_if(self, ifcond): + assert self._start_if is None + self._start_if = (ifcond, self._body, self._preamble) + + def end_if(self): + assert self._start_if + self._wrap_ifcond() + self._start_if = None + + def _wrap_ifcond(self): + self._body = _wrap_ifcond(self._start_if[0], + self._start_if[1], self._body) + self._preamble = _wrap_ifcond(self._start_if[0], + self._start_if[2], self._preamble) + + def get_content(self, fname=None): + assert self._start_if is None + return QAPIGen.get_content(self, fname) + + +class QAPIGenC(QAPIGenCCode): + + def __init__(self, blurb, pydoc): + QAPIGenCCode.__init__(self) self._blurb = blurb self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc, re.MULTILINE)) -- cgit 1.4.1