qapi: Record 'include' directives in intermediate representation

The include directive permits modular QAPI schemata, but the generated
code is monolithic all the same.  To permit generating modular code,
the front end needs to pass more information on inclusions to the back
ends.  The commit before last added the necessary information to the
parse tree.  This commit adds it to the intermediate representation
and its QAPISchemaVisitor.  A later commit will use this to to
generate modular code.

New entity QAPISchemaInclude represents inclusions.  Call new visitor
method visit_include() for it, so visitors can see the sub-modules a
module includes.

Note that unlike other entities, QAPISchemaInclude has no name, and is
therefore not added to entity_dict.

New QAPISchemaEntity attribute @module names the entity's source file.
Call new visitor method visit_module() when it changes during a visit,
so visitors can keep track of the module being visited.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20180211093607.27351-18-armbru@redhat.com>
[eblake: avoid accidental deletion of self._predefining]
Signed-off-by: Eric Blake <eblake@redhat.com>
This commit is contained in:
Markus Armbruster 2018-02-11 10:35:55 +01:00 committed by Eric Blake
parent 8a84767cc4
commit cf40a0a5c2
12 changed files with 71 additions and 4 deletions

View file

@ -984,8 +984,9 @@ def check_exprs(exprs):
class QAPISchemaEntity(object): class QAPISchemaEntity(object):
def __init__(self, name, info, doc): def __init__(self, name, info, doc):
assert isinstance(name, str) assert name is None or isinstance(name, str)
self.name = name self.name = name
self.module = None
# For explicitly defined entities, info points to the (explicit) # For explicitly defined entities, info points to the (explicit)
# definition. For builtins (and their arrays), info is None. # definition. For builtins (and their arrays), info is None.
# For implicitly defined entities, info points to a place that # For implicitly defined entities, info points to a place that
@ -1014,10 +1015,16 @@ def visit_begin(self, schema):
def visit_end(self): def visit_end(self):
pass pass
def visit_module(self, fname):
pass
def visit_needed(self, entity): def visit_needed(self, entity):
# Default to visiting everything # Default to visiting everything
return True return True
def visit_include(self, fname, info):
pass
def visit_builtin_type(self, name, info, json_type): def visit_builtin_type(self, name, info, json_type):
pass pass
@ -1044,6 +1051,16 @@ def visit_event(self, name, info, arg_type, boxed):
pass pass
class QAPISchemaInclude(QAPISchemaEntity):
def __init__(self, fname, info):
QAPISchemaEntity.__init__(self, None, info, None)
self.fname = fname
def visit(self, visitor):
visitor.visit_include(self.fname, self.info)
class QAPISchemaType(QAPISchemaEntity): class QAPISchemaType(QAPISchemaEntity):
# Return the C type for common use. # Return the C type for common use.
# For the types we commonly box, this is a pointer type. # For the types we commonly box, this is a pointer type.
@ -1471,6 +1488,7 @@ def visit(self, visitor):
class QAPISchema(object): class QAPISchema(object):
def __init__(self, fname): def __init__(self, fname):
self._fname = fname
parser = QAPISchemaParser(open(fname, 'r')) parser = QAPISchemaParser(open(fname, 'r'))
exprs = check_exprs(parser.exprs) exprs = check_exprs(parser.exprs)
self.docs = parser.docs self.docs = parser.docs
@ -1485,9 +1503,13 @@ def __init__(self, fname):
def _def_entity(self, ent): def _def_entity(self, ent):
# Only the predefined types are allowed to not have info # Only the predefined types are allowed to not have info
assert ent.info or self._predefining assert ent.info or self._predefining
assert ent.name not in self._entity_dict assert ent.name is None or ent.name not in self._entity_dict
self._entity_list.append(ent) self._entity_list.append(ent)
self._entity_dict[ent.name] = ent if ent.name is not None:
self._entity_dict[ent.name] = ent
if ent.info:
ent.module = os.path.relpath(ent.info['file'],
os.path.dirname(self._fname))
def lookup_entity(self, name, typ=None): def lookup_entity(self, name, typ=None):
ent = self._entity_dict.get(name) ent = self._entity_dict.get(name)
@ -1498,6 +1520,15 @@ def lookup_entity(self, name, typ=None):
def lookup_type(self, name): def lookup_type(self, name):
return self.lookup_entity(name, QAPISchemaType) return self.lookup_entity(name, QAPISchemaType)
def _def_include(self, expr, info, doc):
include = expr['include']
assert doc is None
main_info = info
while main_info['parent']:
main_info = main_info['parent']
fname = os.path.relpath(include, os.path.dirname(main_info['file']))
self._def_entity(QAPISchemaInclude(fname, info))
def _def_builtin_type(self, name, json_type, c_type): def _def_builtin_type(self, name, json_type, c_type):
self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type)) self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
# TODO As long as we have QAPI_TYPES_BUILTIN to share multiple # TODO As long as we have QAPI_TYPES_BUILTIN to share multiple
@ -1680,7 +1711,7 @@ def _def_exprs(self, exprs):
elif 'event' in expr: elif 'event' in expr:
self._def_event(expr, info, doc) self._def_event(expr, info, doc)
elif 'include' in expr: elif 'include' in expr:
pass self._def_include(expr, info, doc)
else: else:
assert False assert False
@ -1690,8 +1721,12 @@ def check(self):
def visit(self, visitor): def visit(self, visitor):
visitor.visit_begin(self) visitor.visit_begin(self)
module = None
for entity in self._entity_list: for entity in self._entity_list:
if visitor.visit_needed(entity): if visitor.visit_needed(entity):
if entity.module != module:
module = entity.module
visitor.visit_module(module)
entity.visit(visitor) entity.visit(visitor)
visitor.visit_end() visitor.visit_end()

View file

@ -1,4 +1,5 @@
object q_empty object q_empty
enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool'] enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool']
prefix QTYPE prefix QTYPE
module comments.json
enum Status ['good', 'bad', 'ugly'] enum Status ['good', 'bad', 'ugly']

View file

@ -1,6 +1,7 @@
object q_empty object q_empty
enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool'] enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool']
prefix QTYPE prefix QTYPE
module doc-bad-section.json
enum Enum ['one', 'two'] enum Enum ['one', 'two']
doc symbol=Enum doc symbol=Enum
body= body=

View file

@ -1,6 +1,7 @@
object q_empty object q_empty
enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool'] enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool']
prefix QTYPE prefix QTYPE
module doc-good.json
enum Enum ['one', 'two'] enum Enum ['one', 'two']
object Base object Base
member base1: Enum optional=False member base1: Enum optional=False

View file

@ -1,5 +1,6 @@
object q_empty object q_empty
enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool'] enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool']
prefix QTYPE prefix QTYPE
module event-case.json
event oops None event oops None
boxed=False boxed=False

View file

@ -1,6 +1,7 @@
object q_empty object q_empty
enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool'] enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool']
prefix QTYPE prefix QTYPE
module ident-with-escape.json
object q_obj_fooA-arg object q_obj_fooA-arg
member bar1: str optional=False member bar1: str optional=False
command fooA q_obj_fooA-arg -> None command fooA q_obj_fooA-arg -> None

View file

@ -1,4 +1,9 @@
object q_empty object q_empty
enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool'] enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool']
prefix QTYPE prefix QTYPE
module include-relpath.json
include include/relpath.json
module include/relpath.json
include include-relpath-sub.json
module include-relpath-sub.json
enum Status ['good', 'bad', 'ugly'] enum Status ['good', 'bad', 'ugly']

View file

@ -1,4 +1,14 @@
object q_empty object q_empty
enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool'] enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool']
prefix QTYPE prefix QTYPE
module include-repetition.json
include comments.json
module comments.json
enum Status ['good', 'bad', 'ugly'] enum Status ['good', 'bad', 'ugly']
module include-repetition.json
include include-repetition-sub.json
module include-repetition-sub.json
include comments.json
include comments.json
module include-repetition.json
include comments.json

View file

@ -1,4 +1,7 @@
object q_empty object q_empty
enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool'] enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool']
prefix QTYPE prefix QTYPE
module include-simple.json
include include-simple-sub.json
module include-simple-sub.json
enum Status ['good', 'bad', 'ugly'] enum Status ['good', 'bad', 'ugly']

View file

@ -1,6 +1,7 @@
object q_empty object q_empty
enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool'] enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool']
prefix QTYPE prefix QTYPE
module indented-expr.json
command eins None -> None command eins None -> None
gen=True success_response=True boxed=False gen=True success_response=True boxed=False
command zwei None -> None command zwei None -> None

View file

@ -1,6 +1,7 @@
object q_empty object q_empty
enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool'] enum QType ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 'qbool']
prefix QTYPE prefix QTYPE
module qapi-schema-test.json
object TestStruct object TestStruct
member integer: int optional=False member integer: int optional=False
member boolean: bool optional=False member boolean: bool optional=False

View file

@ -16,6 +16,13 @@
class QAPISchemaTestVisitor(QAPISchemaVisitor): class QAPISchemaTestVisitor(QAPISchemaVisitor):
def visit_module(self, name):
print('module %s' % name)
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, values, prefix):
print('enum %s %s' % (name, values)) print('enum %s %s' % (name, values))
if prefix: if prefix: