[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Qemu-devel] [PATCH RFC v4 02/32] qapi: New QAPISchema intermediate
From: |
Markus Armbruster |
Subject: |
Re: [Qemu-devel] [PATCH RFC v4 02/32] qapi: New QAPISchema intermediate reperesentation |
Date: |
Fri, 04 Sep 2015 09:12:09 +0200 |
User-agent: |
Gnus/5.13 (Gnus v5.13) Emacs/24.5 (gnu/linux) |
"Daniel P. Berrange" <address@hidden> writes:
> On Thu, Sep 03, 2015 at 04:29:53PM +0200, Markus Armbruster wrote:
>> 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.
>>
>> 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 <address@hidden>
>> Reviewed-by: Eric Blake <address@hidden>
>
> Reviewed-by: Daniel P. Berrange <address@hidden>
>
> A few comments inline, but nothing worth blocking on, just style.
>
>
>> diff --git a/scripts/qapi.py b/scripts/qapi.py
>> index a0165dd..c42bea1 100644
>> --- a/scripts/qapi.py
>> +++ b/scripts/qapi.py
>> @@ -747,15 +749,355 @@ def check_exprs(exprs):
>> else:
>> assert False, 'unexpected meta type'
>>
>> - return map(lambda expr_elem: expr_elem['expr'], exprs)
>> -
>> -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)
>> + 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
>
> From a style POV, I'd recommend having a single blank
> line before each new function def, to make things more
> readable. I won't nack the patch for that, but if you
> have need to rebase & post a new version of this series
> I think it'd be nice to add the the extra blank lines
> when defining all these classes.
I have such blank lines in the more complex stuff. Didn't feel like it
in trivial classes like this, but if others want the blank lines, I'm
happy to add them.
> Python has a nice tool called pep8 which can apply a
> configurable bunch of style checks, it is probably
> worth someone wiring it up to make check in QEMU, since
> we have an increasing amount of python code. An exercise
> for a motivated reader.....
I've been using pylint (and dear-oh-dear how much pre-existing lint it
finds!), wasn't aware of pep8, can throw it in.
Automatic checking would be nice, but our current code is probably too
untidy for that. Besides, I don't have the time and energy to set it
up now.
pylint is reasonably happy with the qapi-*.py now. I hope the follow-up
work on qapi.py will get it into the same state.
>> +
>> +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):
>> + QAPISchemaType.__init__(self, name, info)
>> + for v in values:
>> + assert isinstance(v, str)
>> + self.values = values
>> + 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 == None or isinstance(base, str)
>> + for m in local_members:
>> + assert isinstance(m, QAPISchemaObjectTypeMember)
>> + assert variants == 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 != 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 == None or isinstance(tag_name, str)
>> + assert tag_enum == 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('kind', 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
>> + # TODO try to get rid of .simple_union_type()
>> + 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):
>> + 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:
>
> Preferred syntax for exceptions these days is
>
> except Foo as err
>
> instead of
>
> except Foo, err
>
> since the former is portable to Python3, should we need to consider
> that in future. Again, not a show stopper.
Does it work with Python 2.4? That's where we're currently stuck.
>> + print >>sys.stderr, err
>> + exit(1)
>
> I think it is probably better practice to not have classes print
> to stderr / exit. I'd just let the error propagate to the caller
> and have the top level script catch exceptions, print error message
> and exit. Again since this is just style issue I won't nack it for
> that but something to consider if needing to re-spin this series
> again.
Quadruplicates the exception handling...
I think I'll leave this question open for now.
>> + 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))
>> + 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']
>> + self._def_entity(QAPISchemaEnumType(name, info, data))
>> + 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)
>
> Trailing whitespace at end of this line
Will fix, and search for more.
>> + 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)
>>
>> #
>> # Code generation helpers
>> diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
>> index 634ef2d..461c713 100644
>> --- a/tests/qapi-schema/test-qapi.py
>> +++ b/tests/qapi-schema/test-qapi.py
>> @@ -16,7 +16,7 @@ import os
>> import sys
>>
>> try:
>> - exprs = parse_schema(sys.argv[1])
>> + exprs = QAPISchema(sys.argv[1]).get_exprs()
>> except SystemExit:
>>
>
> Following on earlier comment, this could have a block
>
> except Exception as e:
> print >>sys.stderr, e
> sys.exit(1)
>
> instead of having the object constructor call exit itself.
Thanks!
- [Qemu-devel] [PATCH RFC v4 04/32] qapi: New QAPISchemaVisitor, (continued)
- [Qemu-devel] [PATCH RFC v4 04/32] qapi: New QAPISchemaVisitor, Markus Armbruster, 2015/09/03
- [Qemu-devel] [PATCH RFC v4 08/32] Revert "qapi: Generate comments to simplify splitting for review", Markus Armbruster, 2015/09/03
- [Qemu-devel] [PATCH RFC v4 14/32] qapi-event: Eliminate global variable event_enum_value, Markus Armbruster, 2015/09/03
- [Qemu-devel] [PATCH RFC v4 20/32] qapi-visit: Rearrange code a bit, Markus Armbruster, 2015/09/03
- [Qemu-devel] [PATCH RFC v4 10/32] qapi-types: Convert to QAPISchemaVisitor, fixing flat unions, Markus Armbruster, 2015/09/03
- [Qemu-devel] [PATCH RFC v4 23/32] qapi: De-duplicate parameter list generation, Markus Armbruster, 2015/09/03
- [Qemu-devel] [PATCH RFC v4 02/32] qapi: New QAPISchema intermediate reperesentation, Markus Armbruster, 2015/09/03
- Re: [Qemu-devel] [PATCH RFC v4 02/32] qapi: New QAPISchema intermediate reperesentation, Eric Blake, 2015/09/03
- Re: [Qemu-devel] [PATCH RFC v4 02/32] qapi: New QAPISchema intermediate reperesentation, Eric Blake, 2015/09/04
- Re: [Qemu-devel] [PATCH RFC v4 02/32] qapi: New QAPISchema intermediate reperesentation, Markus Armbruster, 2015/09/04
- [Qemu-devel] [PATCH RFC v4 02.5/32] qapi: Hide internal data members of schema objects., Eric Blake, 2015/09/04
- Re: [Qemu-devel] [PATCH RFC v4 02.5/32] qapi: Hide internal data members of schema objects., Eric Blake, 2015/09/05
- Re: [Qemu-devel] [PATCH RFC v4 02.5/32] qapi: Hide internal data members of schema objects., Eric Blake, 2015/09/05
- Re: [Qemu-devel] [PATCH RFC v4 02.5/32] qapi: Hide internal data members of schema objects., Eric Blake, 2015/09/05
- Re: [Qemu-devel] [PATCH RFC v4 02.5/32] qapi: Hide internal data members of schema objects., Markus Armbruster, 2015/09/07