QAPI patches for 2018-07-03

-----BEGIN PGP SIGNATURE-----
 
 iQIcBAABAgAGBQJbO+InAAoJEDhwtADrkYZTtsQQAI2IEFWwsIWtjh+JO2xWHjTC
 sXrzD2m8g50xledX1mKgQdA9NujvrSreQBFEgXvOEz65K8hl1HIqNp9C4NeOPUGn
 vVxDGRrMz1rGRrBXXM9mf2v3ZSMw0mVAmpBzQ2lO/fPsGEAThJ4451c0mHiMhBXi
 Ez2v3Qod597ITWCG73DccCN7ehl+3HCpqAyvBjVjtolmnt0h4oefqb8OV1l59kl3
 dywlnEoMcLq80uznAkkCnQ89yZR6ygrZIYfyMynpkTo4quf/ZB8eHF8Au4Jyynby
 1cAPAs0tfjmfvSpSEGvWyhmuu8jKm9pGnby7Me18HV/dkyNWzQWWMpzWpYHxiu41
 fa0SpBpw4g112SaeNOOt0g+SDpcdtvTheWmyPbzSL1yTDJ2NTppbl2zpNo9BHY3X
 jFSXpNi74RTB4KDXx+t/QXJepo3f35dTOOoOvodhT4EHzTIkPQoalKbKZij6zDHo
 CoBRrJQ8WQIxaYuiYHCDdRxTBbq44XbW30rWDnjmUBcLW6uMuUOiZxUh+n2GQjP7
 dEZel23zslqPdFexnTX32miIfkiHABKWIr7EzZ4NC8P/0U8McKD/lxGP8IHrs9Lo
 D8UYgfOGse2w5MWXCxSwq+VY5aFPasYrpVpi31gnCzLtJ9Q8qBMoqIGdyFKHzxXj
 qTvkP1iSHnXjSsq0KryZ
 =uZ84
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/armbru/tags/pull-qapi-2018-07-03' into staging

QAPI patches for 2018-07-03

# gpg: Signature made Tue 03 Jul 2018 21:52:55 BST
# gpg:                using RSA key 3870B400EB918653
# gpg: Good signature from "Markus Armbruster <armbru@redhat.com>"
# gpg:                 aka "Markus Armbruster <armbru@pond.sub.org>"
# Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867  4E5F 3870 B400 EB91 8653

* remotes/armbru/tags/pull-qapi-2018-07-03:
  qapi: add conditions to SPICE type/commands/events on the schema
  qapi: add conditions to VNC type/commands/events on the schema
  qapi: add 'If:' section to generated documentation
  qapi-types: add #if conditions to types & visitors
  qapi/events: add #if conditions to events
  qapi/commands: add #if conditions to commands
  qapi-introspect: add preprocessor conditions to generated QLit
  qapi-introspect: modify to_qlit() to append ',' on level > 0
  qapi: add #if/#endif helpers
  qapi: mcgen() shouldn't indent # lines
  qapi: add 'ifcond' to visitor methods
  qapi: leave the ifcond attribute undefined until check()
  qapi: pass 'if' condition into QAPISchemaEntity objects
  qapi: add 'if' to top-level expressions

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2018-07-05 10:31:36 +01:00
commit 5dafaf4fbc
39 changed files with 561 additions and 225 deletions

View file

@ -239,7 +239,7 @@ class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
QAPISchemaModularCVisitor.__init__(
self, prefix, 'qapi-commands',
' * Schema-defined QAPI/QMP commands', __doc__)
self._regy = ''
self._regy = QAPIGenCCode()
self._visited_ret_types = {}
def _begin_module(self, name):
@ -275,20 +275,28 @@ class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
''',
c_prefix=c_name(self._prefix, protect=False)))
genc.add(gen_registry(self._regy, self._prefix))
genc.add(gen_registry(self._regy.get_content(), 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
self._genh.add(gen_command_decl(name, arg_type, boxed, ret_type))
# FIXME: If T is a user-defined type, the user is responsible
# for making this work, i.e. to make T's condition the
# conjunction of the T-returning commands' conditions. If T
# is a built-in type, this isn't possible: the
# qmp_marshal_output_T() will be generated unconditionally.
if ret_type and ret_type not in self._visited_ret_types[self._genc]:
self._visited_ret_types[self._genc].add(ret_type)
self._genc.add(gen_marshal_output(ret_type))
self._genh.add(gen_marshal_decl(name))
self._genc.add(gen_marshal(name, arg_type, boxed, ret_type))
self._regy += gen_register_command(name, success_response, allow_oob,
allow_preconfig)
with ifcontext(ret_type.ifcond,
self._genh, self._genc, self._regy):
self._genc.add(gen_marshal_output(ret_type))
with ifcontext(ifcond, self._genh, self._genc, self._regy):
self._genh.add(gen_command_decl(name, arg_type, boxed, ret_type))
self._genh.add(gen_marshal_decl(name))
self._genc.add(gen_marshal(name, arg_type, boxed, ret_type))
self._regy.add(gen_register_command(name, success_response,
allow_oob, allow_preconfig))
def gen_commands(schema, output_dir, prefix):

View file

@ -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
@ -638,6 +639,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 +893,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 +923,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")
@ -978,8 +1002,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
@ -990,12 +1022,19 @@ class QAPISchemaEntity(object):
# such place).
self.info = info
self.doc = doc
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
@ -1024,26 +1063,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
@ -1122,8 +1161,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)
@ -1132,6 +1171,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)
@ -1152,20 +1192,23 @@ 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)
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
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):
return True
@ -1183,15 +1226,17 @@ 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):
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)
@ -1206,6 +1251,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)
@ -1263,9 +1309,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)
@ -1387,8 +1433,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)
@ -1396,6 +1442,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
@ -1417,16 +1464,17 @@ 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
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
@ -1440,6 +1488,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
@ -1459,7 +1508,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,
@ -1467,14 +1516,15 @@ 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
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
@ -1491,7 +1541,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):
@ -1567,22 +1618,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):
@ -1591,22 +1642,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 # pylint: disable=protected-access
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
@ -1626,7 +1692,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))
@ -1638,18 +1705,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),
'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()]
@ -1657,12 +1727,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)))
@ -1670,11 +1740,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)))
@ -1688,13 +1759,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))
@ -1702,10 +1774,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:
@ -1869,8 +1942,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)
@ -1902,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('''
@ -1999,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 ''
@ -2019,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)
@ -2029,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()
*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):
QAPIGen.__init__(self)
QAPIGenCCode.__init__(self)
self._blurb = blurb
self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
re.MULTILINE))

32
scripts/qapi/doc.py Normal file → Executable file
View file

@ -174,7 +174,7 @@ def texi_members(doc, what, base, variants, member_func):
return '\n@b{%s:}\n@table @asis\n%s@end table\n' % (what, items)
def texi_sections(doc):
def texi_sections(doc, ifcond):
"""Format additional sections following arguments"""
body = ''
for section in doc.sections:
@ -185,14 +185,16 @@ def texi_sections(doc):
body += texi_example(section.text)
else:
body += texi_format(section.text)
if ifcond:
body += '\n\n@b{If:} @code{%s}' % ", ".join(ifcond)
return body
def texi_entity(doc, what, base=None, variants=None,
def texi_entity(doc, what, ifcond, base=None, variants=None,
member_func=texi_member):
return (texi_body(doc)
+ texi_members(doc, what, base, variants, member_func)
+ texi_sections(doc))
+ texi_sections(doc, ifcond))
class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):
@ -204,47 +206,47 @@ 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',
body=texi_entity(doc, 'Values', ifcond,
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
self._gen.add(TYPE_FMT(type='Object',
name=doc.symbol,
body=texi_entity(doc, 'Members',
body=texi_entity(doc, 'Members', ifcond,
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')))
body=texi_entity(doc, 'Members', 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):
doc = self.cur_doc
if boxed:
body = texi_body(doc)
body += ('\n@b{Arguments:} the members of @code{%s}\n'
% arg_type.name)
body += texi_sections(doc)
body += texi_sections(doc, ifcond)
else:
body = texi_entity(doc, 'Arguments')
body = texi_entity(doc, 'Arguments', ifcond)
self._gen.add(MSG_FMT(type='Command',
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,
body=texi_entity(doc, 'Arguments')))
body=texi_entity(doc, 'Arguments', ifcond)))
def symbol(self, doc, entity):
if self._gen._body:
@ -257,7 +259,7 @@ class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):
assert not doc.args
if self._gen._body:
self._gen.add('\n')
self._gen.add(texi_body(doc) + texi_sections(doc))
self._gen.add(texi_body(doc) + texi_sections(doc, None))
def gen_doc(schema, output_dir, prefix):

View file

@ -184,9 +184,11 @@ 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):
self._genh.add(gen_event_send_decl(name, arg_type, boxed))
self._genc.add(gen_event_send(name, arg_type, boxed, self._enum_name))
def visit_event(self, name, info, ifcond, arg_type, boxed):
with ifcontext(ifcond, self._genh, self._genc):
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)

View file

@ -18,6 +18,15 @@ def to_qlit(obj, level=0, suppress_first_indent=False):
def indent(level):
return level * 4 * ' '
if isinstance(obj, tuple):
ifobj, ifcond = obj
ret = gen_if(ifcond)
ret += to_qlit(ifobj, level)
endif = gen_endif(ifcond)
if endif:
ret += '\n' + endif
return ret
ret = ''
if not suppress_first_indent:
ret += indent(level)
@ -26,11 +35,11 @@ def to_qlit(obj, level=0, suppress_first_indent=False):
elif isinstance(obj, str):
ret += 'QLIT_QSTR(' + to_c_string(obj) + ')'
elif isinstance(obj, list):
elts = [to_qlit(elt, level + 1)
elts = [to_qlit(elt, level + 1).strip('\n')
for elt in obj]
elts.append(indent(level + 1) + "{}")
ret += 'QLIT_QLIST(((QLitObject[]) {\n'
ret += ',\n'.join(elts) + '\n'
ret += '\n'.join(elts) + '\n'
ret += indent(level) + '}))'
elif isinstance(obj, dict):
elts = []
@ -45,6 +54,8 @@ def to_qlit(obj, level=0, suppress_first_indent=False):
ret += 'QLIT_QBOOL(%s)' % ('true' if obj else 'false')
else:
assert False # not implemented
if level > 0:
ret += ','
return ret
@ -126,12 +137,12 @@ const QLitObject %(c_name)s = %(c_string)s;
return '[' + self._use_type(typ.element_type) + ']'
return self._name(typ.name)
def _gen_qlit(self, name, mtype, obj):
def _gen_qlit(self, name, mtype, obj, ifcond):
if mtype not in ('command', 'event', 'builtin', 'array'):
name = self._name(name)
obj['name'] = name
obj['meta-type'] = mtype
self._qlits.append(obj)
self._qlits.append((obj, ifcond))
def _gen_member(self, member):
ret = {'name': member.name, 'type': self._use_type(member.type)}
@ -147,28 +158,29 @@ const QLitObject %(c_name)s = %(c_string)s;
return {'case': variant.name, 'type': self._use_type(variant.type)}
def visit_builtin_type(self, name, info, json_type):
self._gen_qlit(name, 'builtin', {'json-type': json_type})
self._gen_qlit(name, 'builtin', {'json-type': json_type}, [])
def visit_enum_type(self, name, info, values, prefix):
self._gen_qlit(name, 'enum', {'values': values})
def visit_enum_type(self, name, info, ifcond, values, prefix):
self._gen_qlit(name, 'enum', {'values': values}, ifcond)
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})
self._gen_qlit('[' + element + ']', 'array', {'element-type': element},
ifcond)
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)
self._gen_qlit(name, 'object', obj, ifcond)
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]})
for m in variants.variants]}, 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):
arg_type = arg_type or self._schema.the_empty_object_type
ret_type = ret_type or self._schema.the_empty_object_type
@ -176,11 +188,12 @@ const QLitObject %(c_name)s = %(c_string)s;
{'arg-type': self._use_type(arg_type),
'ret-type': self._use_type(ret_type),
'allow-oob': allow_oob,
'allow-preconfig': allow_preconfig})
'allow-preconfig': allow_preconfig}, ifcond)
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)})
self._gen_qlit(name, 'event', {'arg-type': self._use_type(arg_type)},
ifcond)
def gen_introspect(schema, output_dir, prefix, opt_unmask):

View file

@ -55,7 +55,7 @@ def gen_struct_members(members):
return ret
def gen_object(name, base, members, variants):
def gen_object(name, ifcond, base, members, variants):
if name in objects_seen:
return ''
objects_seen.add(name)
@ -64,11 +64,14 @@ def gen_object(name, base, members, variants):
if variants:
for v in variants.variants:
if isinstance(v.type, QAPISchemaObjectType):
ret += gen_object(v.type.name, v.type.base,
ret += gen_object(v.type.name, v.type.ifcond, v.type.base,
v.type.local_members, v.type.variants)
ret += mcgen('''
''')
ret += gen_if(ifcond)
ret += mcgen('''
struct %(c_name)s {
''',
c_name=c_name(name))
@ -101,6 +104,7 @@ struct %(c_name)s {
ret += mcgen('''
};
''')
ret += gen_endif(ifcond)
return ret
@ -208,34 +212,40 @@ 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):
self._genh.preamble_add(gen_enum(name, values, prefix))
self._genc.add(gen_enum_lookup(name, values, prefix))
def visit_enum_type(self, name, info, ifcond, values, prefix):
with ifcontext(ifcond, self._genh, self._genc):
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):
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_array_type(self, name, info, ifcond, element_type):
with ifcontext(ifcond, self._genh, self._genc):
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
self._genh.preamble_add(gen_fwd_object_or_array(name))
self._genh.add(gen_object(name, base, members, variants))
if base and not base.is_implicit():
self._genh.add(gen_upcast(name, base))
# TODO Worth changing the visitor signature, so we could
# directly use rather than repeat type.is_implicit()?
if not name.startswith('q_'):
# implicit types won't be directly allocated/freed
self._gen_type_cleanup(name)
with ifcontext(ifcond, self._genh):
self._genh.preamble_add(gen_fwd_object_or_array(name))
self._genh.add(gen_object(name, ifcond, base, members, variants))
with ifcontext(ifcond, self._genh, self._genc):
if base and not base.is_implicit():
self._genh.add(gen_upcast(name, base))
# TODO Worth changing the visitor signature, so we could
# directly use rather than repeat type.is_implicit()?
if not name.startswith('q_'):
# implicit types won't be directly allocated/freed
self._gen_type_cleanup(name)
def visit_alternate_type(self, name, info, variants):
self._genh.preamble_add(gen_fwd_object_or_array(name))
self._genh.add(gen_object(name, None,
def visit_alternate_type(self, name, info, ifcond, variants):
with ifcontext(ifcond, self._genh):
self._genh.preamble_add(gen_fwd_object_or_array(name))
self._genh.add(gen_object(name, ifcond, None,
[variants.tag_member], variants))
self._gen_type_cleanup(name)
with ifcontext(ifcond, self._genh, self._genc):
self._gen_type_cleanup(name)
def gen_types(schema, output_dir, prefix, opt_builtins):

View file

@ -310,30 +310,35 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor):
''',
types=types))
def visit_enum_type(self, name, info, values, prefix):
self._genh.add(gen_visit_decl(name, scalar=True))
self._genc.add(gen_visit_enum(name))
def visit_enum_type(self, name, info, ifcond, values, prefix):
with ifcontext(ifcond, self._genh, self._genc):
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):
self._genh.add(gen_visit_decl(name))
self._genc.add(gen_visit_list(name, element_type))
def visit_array_type(self, name, info, ifcond, element_type):
with ifcontext(ifcond, self._genh, self._genc):
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
self._genh.add(gen_visit_members_decl(name))
self._genc.add(gen_visit_object_members(name, base, members, variants))
# TODO Worth changing the visitor signature, so we could
# directly use rather than repeat type.is_implicit()?
if not name.startswith('q_'):
# only explicit types need an allocating visit
self._genh.add(gen_visit_decl(name))
self._genc.add(gen_visit_object(name, base, members, variants))
with ifcontext(ifcond, self._genh, self._genc):
self._genh.add(gen_visit_members_decl(name))
self._genc.add(gen_visit_object_members(name, base,
members, variants))
# TODO Worth changing the visitor signature, so we could
# directly use rather than repeat type.is_implicit()?
if not name.startswith('q_'):
# only explicit types need an allocating visit
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):
self._genh.add(gen_visit_decl(name))
self._genc.add(gen_visit_alternate(name, variants))
def visit_alternate_type(self, name, info, ifcond, variants):
with ifcontext(ifcond, self._genh, self._genc):
self._genh.add(gen_visit_decl(name))
self._genc.add(gen_visit_alternate(name, variants))
def gen_visit(schema, output_dir, prefix, opt_builtins):