diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2015-09-21 22:33:51 +0100 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2015-09-21 22:33:51 +0100 |
commit | 9e72681d16792d0ffc42bab634b1753ff299bdfd (patch) | |
tree | 4b73c0b5685250599051e7f0c20ddfad784972da /scripts | |
parent | 75ebcd7f080fa30893272f6fe07354e4ffa11b46 (diff) | |
parent | 1a9a507b2e3e90aa719c96b4c092e7fad7215f21 (diff) | |
download | qemu-9e72681d16792d0ffc42bab634b1753ff299bdfd.zip qemu-9e72681d16792d0ffc42bab634b1753ff299bdfd.tar.gz qemu-9e72681d16792d0ffc42bab634b1753ff299bdfd.tar.bz2 |
Merge remote-tracking branch 'remotes/armbru/tags/pull-qapi-2015-09-21' into staging
qapi: QMP introspection
# gpg: Signature made Mon 21 Sep 2015 08:59:17 BST using RSA key ID EB918653
# gpg: Good signature from "Markus Armbruster <armbru@redhat.com>"
# gpg: aka "Markus Armbruster <armbru@pond.sub.org>"
* remotes/armbru/tags/pull-qapi-2015-09-21: (26 commits)
qapi-introspect: Hide type names
qapi: New QMP command query-qmp-schema for QMP introspection
qapi: Pseudo-type '**' is now unused, drop it
qapi-schema: Fix up misleading specification of netdev_add
qom: Don't use 'gen': false for qom-get, qom-set, object-add
qapi: Introduce a first class 'any' type
qapi: Make output visitor return qnull() instead of NULL
qapi: Improve built-in type documentation
qapi-commands: De-duplicate output marshaling functions
qapi: De-duplicate parameter list generation
qapi: Rename qmp_marshal_input_FOO() to qmp_marshal_FOO()
qapi-commands: Rearrange code
qapi-visit: Rearrange code a bit
qapi: Clean up after recent conversions to QAPISchemaVisitor
qapi: Replace dirty is_c_ptr() by method c_null()
qapi-event: Convert to QAPISchemaVisitor, fixing data with base
qapi-event: Eliminate global variable event_enum_value
qapi: De-duplicate enum code generation
qapi-commands: Convert to QAPISchemaVisitor
qapi-visit: Convert to QAPISchemaVisitor, fixing bugs
...
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'scripts')
-rw-r--r-- | scripts/qapi-commands.py | 284 | ||||
-rw-r--r-- | scripts/qapi-event.py | 235 | ||||
-rw-r--r-- | scripts/qapi-introspect.py | 213 | ||||
-rw-r--r-- | scripts/qapi-types.py | 377 | ||||
-rw-r--r-- | scripts/qapi-visit.py | 369 | ||||
-rw-r--r-- | scripts/qapi.py | 702 |
6 files changed, 1361 insertions, 819 deletions
diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py index 890ce5d..810a897 100644 --- a/scripts/qapi-commands.py +++ b/scripts/qapi-commands.py @@ -12,22 +12,18 @@ # This work is licensed under the terms of the GNU GPL, version 2. # See the COPYING file in the top-level directory. -from ordereddict import OrderedDict from qapi import * import re -def generate_command_decl(name, args, ret_type): - arglist="" - for argname, argtype, optional in parse_args(args): - argtype = c_type(argtype, is_param=True) - if optional: - arglist += "bool has_%s, " % c_name(argname) - arglist += "%s %s, " % (argtype, c_name(argname)) + +def gen_command_decl(name, arg_type, ret_type): return mcgen(''' -%(ret_type)s qmp_%(name)s(%(args)sError **errp); +%(c_type)s qmp_%(c_name)s(%(params)s); ''', - ret_type=c_type(ret_type), name=c_name(name), - args=arglist) + c_type=(ret_type and ret_type.c_type()) or 'void', + c_name=c_name(name), + params=gen_params(arg_type, 'Error **errp')) + def gen_err_check(err): if not err: @@ -39,110 +35,124 @@ if (%(err)s) { ''', err=err) -def gen_sync_call(name, args, ret_type): - ret = "" - arglist="" - retval="" + +def gen_call(name, arg_type, ret_type): + ret = '' + + argstr = '' + if arg_type: + for memb in arg_type.members: + if memb.optional: + argstr += 'has_%s, ' % c_name(memb.name) + argstr += '%s, ' % c_name(memb.name) + + lhs = '' if ret_type: - retval = "retval = " - for argname, argtype, optional in parse_args(args): - if optional: - arglist += "has_%s, " % c_name(argname) - arglist += "%s, " % (c_name(argname)) + lhs = 'retval = ' + push_indent() ret = mcgen(''' -%(retval)sqmp_%(name)s(%(args)s&local_err); + +%(lhs)sqmp_%(c_name)s(%(args)s&local_err); ''', - name=c_name(name), args=arglist, retval=retval) + c_name=c_name(name), args=argstr, lhs=lhs) if ret_type: ret += gen_err_check('local_err') ret += mcgen(''' qmp_marshal_output_%(c_name)s(retval, ret, &local_err); ''', - c_name=c_name(name)) + c_name=ret_type.c_name()) pop_indent() return ret -def gen_visitor_input_containers_decl(args): - ret = "" + +def gen_marshal_vars(arg_type, ret_type): + ret = mcgen(''' + Error *local_err = NULL; +''') push_indent() - if len(args) > 0: + + if ret_type: + ret += mcgen(''' +%(c_type)s retval; +''', + c_type=ret_type.c_type()) + + if arg_type: ret += mcgen(''' QmpInputVisitor *mi = qmp_input_visitor_new_strict(QOBJECT(args)); QapiDeallocVisitor *md; Visitor *v; ''') - pop_indent() - - return ret -def gen_visitor_input_vars_decl(args): - ret = "" - push_indent() - for argname, argtype, optional in parse_args(args): - if optional: - ret += mcgen(''' -bool has_%(argname)s = false; -''', - argname=c_name(argname)) - if is_c_ptr(argtype): - ret += mcgen(''' -%(argtype)s %(argname)s = NULL; + for memb in arg_type.members: + if memb.optional: + ret += mcgen(''' +bool has_%(c_name)s = false; ''', - argname=c_name(argname), argtype=c_type(argtype)) - else: + c_name=c_name(memb.name)) ret += mcgen(''' -%(argtype)s %(argname)s = {0}; +%(c_type)s %(c_name)s = %(c_null)s; ''', - argname=c_name(argname), argtype=c_type(argtype)) + c_name=c_name(memb.name), + c_type=memb.type.c_type(), + c_null=memb.type.c_null()) + ret += '\n' + else: + ret += mcgen(''' + +(void)args; +''') pop_indent() return ret -def gen_visitor_input_block(args, dealloc=False): - ret = "" - errparg = '&local_err' - errarg = 'local_err' - if len(args) == 0: +def gen_marshal_input_visit(arg_type, dealloc=False): + ret = '' + + if not arg_type: return ret push_indent() if dealloc: errparg = 'NULL' - errarg = None; + errarg = None ret += mcgen(''' qmp_input_visitor_cleanup(mi); md = qapi_dealloc_visitor_new(); v = qapi_dealloc_get_visitor(md); ''') else: + errparg = '&local_err' + errarg = 'local_err' ret += mcgen(''' v = qmp_input_get_visitor(mi); ''') - for argname, argtype, optional in parse_args(args): - if optional: + for memb in arg_type.members: + if memb.optional: ret += mcgen(''' visit_optional(v, &has_%(c_name)s, "%(name)s", %(errp)s); ''', - c_name=c_name(argname), name=argname, errp=errparg) + c_name=c_name(memb.name), name=memb.name, + errp=errparg) ret += gen_err_check(errarg) ret += mcgen(''' if (has_%(c_name)s) { ''', - c_name=c_name(argname)) + c_name=c_name(memb.name)) push_indent() ret += mcgen(''' -visit_type_%(visitor)s(v, &%(c_name)s, "%(name)s", %(errp)s); +visit_type_%(c_type)s(v, &%(c_name)s, "%(name)s", %(errp)s); ''', - c_name=c_name(argname), name=argname, argtype=argtype, - visitor=type_name(argtype), errp=errparg) + c_name=c_name(memb.name), name=memb.name, + c_type=memb.type.c_name(), errp=errparg) ret += gen_err_check(errarg) - if optional: + if memb.optional: pop_indent() ret += mcgen(''' } @@ -155,12 +165,11 @@ qapi_dealloc_visitor_cleanup(md); pop_indent() return ret -def gen_marshal_output(name, ret_type): - if not ret_type: - return "" - ret = mcgen(''' -static void qmp_marshal_output_%(c_name)s(%(c_ret_type)s ret_in, QObject **ret_out, Error **errp) +def gen_marshal_output(ret_type): + return mcgen(''' + +static void qmp_marshal_output_%(c_name)s(%(c_type)s ret_in, QObject **ret_out, Error **errp) { Error *local_err = NULL; QmpOutputVisitor *mo = qmp_output_visitor_new(); @@ -168,7 +177,7 @@ static void qmp_marshal_output_%(c_name)s(%(c_ret_type)s ret_in, QObject **ret_o Visitor *v; v = qmp_output_get_visitor(mo); - visit_type_%(visitor)s(v, &ret_in, "unused", &local_err); + visit_type_%(c_name)s(v, &ret_in, "unused", &local_err); if (local_err) { goto out; } @@ -179,51 +188,40 @@ out: qmp_output_visitor_cleanup(mo); md = qapi_dealloc_visitor_new(); v = qapi_dealloc_get_visitor(md); - visit_type_%(visitor)s(v, &ret_in, "unused", NULL); + visit_type_%(c_name)s(v, &ret_in, "unused", NULL); qapi_dealloc_visitor_cleanup(md); } ''', - c_ret_type=c_type(ret_type), c_name=c_name(name), - visitor=type_name(ret_type)) + c_type=ret_type.c_type(), c_name=ret_type.c_name()) - return ret -def gen_marshal_input_decl(name, middle_mode): - ret = 'void qmp_marshal_input_%s(QDict *args, QObject **ret, Error **errp)' % c_name(name) +def gen_marshal_proto(name): + ret = 'void qmp_marshal_%s(QDict *args, QObject **ret, Error **errp)' % c_name(name) if not middle_mode: - ret = "static " + ret + ret = 'static ' + ret return ret -def gen_marshal_input(name, args, ret_type, middle_mode): - hdr = gen_marshal_input_decl(name, middle_mode) - - ret = mcgen(''' -%(header)s -{ - Error *local_err = NULL; -''', - header=hdr) - if ret_type: - ret += mcgen(''' - %(c_type)s retval; +def gen_marshal_decl(name): + return mcgen(''' +%(proto)s; ''', - c_type=c_type(ret_type)) + proto=gen_marshal_proto(name)) - if len(args) > 0: - ret += gen_visitor_input_containers_decl(args) - ret += gen_visitor_input_vars_decl(args) + '\n' - ret += gen_visitor_input_block(args) + '\n' - else: - ret += mcgen(''' - (void)args; +def gen_marshal(name, arg_type, ret_type): + ret = mcgen(''' -''') +%(proto)s +{ +''', + proto=gen_marshal_proto(name)) - ret += gen_sync_call(name, args, ret_type) + ret += gen_marshal_vars(arg_type, ret_type) + ret += gen_marshal_input_visit(arg_type) + ret += gen_call(name, arg_type, ret_type) - if re.search('^ *goto out\\;', ret, re.MULTILINE): + if re.search('^ *goto out;', ret, re.MULTILINE): ret += mcgen(''' out: @@ -231,27 +229,31 @@ out: ret += mcgen(''' error_propagate(errp, local_err); ''') - ret += gen_visitor_input_block(args, dealloc=True) + ret += gen_marshal_input_visit(arg_type, dealloc=True) ret += mcgen(''' } ''') return ret -def gen_registry(commands): - registry="" + +def gen_register_command(name, success_response): push_indent() - for cmd in commands: - options = 'QCO_NO_OPTIONS' - if not cmd.get('success-response', True): - options = 'QCO_NO_SUCCESS_RESP' + options = 'QCO_NO_OPTIONS' + if not success_response: + options = 'QCO_NO_SUCCESS_RESP' - registry += mcgen(''' -qmp_register_command("%(name)s", qmp_marshal_input_%(c_name)s, %(opts)s); + ret = mcgen(''' +qmp_register_command("%(name)s", qmp_marshal_%(c_name)s, %(opts)s); ''', - name=cmd['command'], c_name=c_name(cmd['command']), - opts=options) + name=name, c_name=c_name(name), + opts=options) pop_indent() + return ret + + +def gen_registry(registry): ret = mcgen(''' + static void qmp_init_marshal(void) { ''') @@ -263,6 +265,41 @@ qapi_init(qmp_init_marshal); ''') return ret + +class QAPISchemaGenCommandVisitor(QAPISchemaVisitor): + def __init__(self): + self.decl = None + self.defn = None + self._regy = None + self._visited_ret_types = None + + def visit_begin(self, schema): + self.decl = '' + self.defn = '' + self._regy = '' + self._visited_ret_types = set() + + def visit_end(self): + if not middle_mode: + self.defn += gen_registry(self._regy) + self._regy = None + self._visited_ret_types = None + + def visit_command(self, name, info, arg_type, ret_type, + gen, success_response): + if not gen: + return + self.decl += gen_command_decl(name, arg_type, ret_type) + if ret_type and ret_type not in self._visited_ret_types: + self._visited_ret_types.add(ret_type) + self.defn += gen_marshal_output(ret_type) + if middle_mode: + self.decl += gen_marshal_decl(name) + self.defn += gen_marshal(name, arg_type, ret_type) + if not middle_mode: + self._regy += gen_register_command(name, success_response) + + middle_mode = False (input_file, output_dir, do_c, do_h, prefix, opts) = \ @@ -272,10 +309,6 @@ for o, a in opts: if o in ("-m", "--middle"): middle_mode = True -exprs = parse_schema(input_file) -commands = filter(lambda expr: expr.has_key('command'), exprs) -commands = filter(lambda expr: not expr.has_key('gen'), commands) - c_comment = ''' /* * schema-defined QMP->QAPI command dispatch @@ -323,7 +356,7 @@ fdef.write(mcgen(''' #include "%(prefix)sqmp-commands.h" ''', - prefix=prefix)) + prefix=prefix)) fdecl.write(mcgen(''' #include "%(prefix)sqapi-types.h" @@ -331,29 +364,12 @@ fdecl.write(mcgen(''' #include "qapi/error.h" ''', - prefix=prefix)) - -for cmd in commands: - arglist = [] - ret_type = None - if cmd.has_key('data'): - arglist = cmd['data'] - if cmd.has_key('returns'): - ret_type = cmd['returns'] - ret = generate_command_decl(cmd['command'], arglist, ret_type) - fdecl.write(ret) - if ret_type: - ret = gen_marshal_output(cmd['command'], ret_type) + "\n" - fdef.write(ret) - - if middle_mode: - fdecl.write('%s;\n' % gen_marshal_input_decl(cmd['command'], middle_mode)) - - ret = gen_marshal_input(cmd['command'], arglist, ret_type, middle_mode) + "\n" - fdef.write(ret) + prefix=prefix)) -if not middle_mode: - ret = gen_registry(commands) - fdef.write(ret) +schema = QAPISchema(input_file) +gen = QAPISchemaGenCommandVisitor() +schema.visit(gen) +fdef.write(gen.defn) +fdecl.write(gen.decl) close_output(fdef, fdecl) diff --git a/scripts/qapi-event.py b/scripts/qapi-event.py index 7f238df..d15fad9 100644 --- a/scripts/qapi-event.py +++ b/scripts/qapi-event.py @@ -2,78 +2,64 @@ # QAPI event generator # # Copyright (c) 2014 Wenchao Xia +# Copyright (c) 2015 Red Hat Inc. # # Authors: # Wenchao Xia <wenchaoqemu@gmail.com> +# Markus Armbruster <armbru@redhat.com> # # This work is licensed under the terms of the GNU GPL, version 2. # See the COPYING file in the top-level directory. -from ordereddict import OrderedDict from qapi import * -def _generate_event_api_name(event_name, params): - api_name = "void qapi_event_send_%s(" % c_name(event_name).lower(); - l = len(api_name) - if params: - for argname, argentry, optional in parse_args(params): - if optional: - api_name += "bool has_%s,\n" % c_name(argname) - api_name += "".ljust(l) +def gen_event_send_proto(name, arg_type): + return 'void qapi_event_send_%(c_name)s(%(param)s)' % { + 'c_name': c_name(name.lower()), + 'param': gen_params(arg_type, 'Error **errp')} - api_name += "%s %s,\n" % (c_type(argentry, is_param=True), - c_name(argname)) - api_name += "".ljust(l) - api_name += "Error **errp)" - return api_name; - - -# Following are the core functions that generate C APIs to emit event. - -def generate_event_declaration(api_name): +def gen_event_send_decl(name, arg_type): return mcgen(''' -%(api_name)s; +%(proto)s; ''', - api_name = api_name) + proto=gen_event_send_proto(name, arg_type)) -def generate_event_implement(api_name, event_name, params): - # step 1: declare any variables - ret = mcgen(""" -%(api_name)s +def gen_event_send(name, arg_type): + ret = mcgen(''' + +%(proto)s { QDict *qmp; Error *local_err = NULL; QMPEventFuncEmit emit; -""", - api_name = api_name) +''', + proto=gen_event_send_proto(name, arg_type)) - if params: - ret += mcgen(""" + if arg_type and arg_type.members: + ret += mcgen(''' QmpOutputVisitor *qov; Visitor *v; QObject *obj; -""") +''') - # step 2: check emit function, create a dict - ret += mcgen(""" + ret += mcgen(''' emit = qmp_event_get_func_emit(); if (!emit) { return; } - qmp = qmp_event_build_dict("%(event_name)s"); + qmp = qmp_event_build_dict("%(name)s"); -""", - event_name = event_name) +''', + name=name) - # step 3: visit the params if params != None - if params: - ret += mcgen(""" + if arg_type and arg_type.members: + ret += mcgen(''' qov = qmp_output_visitor_new(); g_assert(qov); @@ -81,45 +67,46 @@ def generate_event_implement(api_name, event_name, params): g_assert(v); /* Fake visit, as if all members are under a structure */ - visit_start_struct(v, NULL, "", "%(event_name)s", 0, &local_err); + visit_start_struct(v, NULL, "", "%(name)s", 0, &local_err); if (local_err) { goto clean; } -""", - event_name = event_name) +''', + name=name) - for argname, argentry, optional in parse_args(params): - if optional: - ret += mcgen(""" - if (has_%(var)s) { -""", - var = c_name(argname)) + for memb in arg_type.members: + if memb.optional: + ret += mcgen(''' + if (has_%(c_name)s) { +''', + c_name=c_name(memb.name)) push_indent() - if argentry == "str": - var_type = "(char **)" + # Ugly: need to cast away the const + if memb.type.name == "str": + cast = '(char **)' else: - var_type = "" + cast = '' - ret += mcgen(""" - visit_type_%(type)s(v, %(var_type)s&%(var)s, "%(name)s", &local_err); + ret += mcgen(''' + visit_type_%(c_type)s(v, %(cast)s&%(c_name)s, "%(name)s", &local_err); if (local_err) { goto clean; } -""", - var_type = var_type, - var = c_name(argname), - type = type_name(argentry), - name = argname) +''', + cast=cast, + c_name=c_name(memb.name), + c_type=memb.type.c_name(), + name=memb.name) - if optional: + if memb.optional: pop_indent() - ret += mcgen(""" + ret += mcgen(''' } -""") +''') - ret += mcgen(""" + ret += mcgen(''' visit_end_struct(v, &local_err); if (local_err) { @@ -130,85 +117,48 @@ def generate_event_implement(api_name, event_name, params): g_assert(obj != NULL); qdict_put_obj(qmp, "data", obj); -""") +''') - # step 4: call qmp event api - ret += mcgen(""" - emit(%(event_enum_value)s, qmp, &local_err); + ret += mcgen(''' + emit(%(c_enum)s, qmp, &local_err); -""", - event_enum_value = event_enum_value) +''', + c_enum=c_enum_const(event_enum_name, name)) - # step 5: clean up - if params: - ret += mcgen(""" + if arg_type and arg_type.members: + ret += mcgen(''' clean: qmp_output_visitor_cleanup(qov); -""") - ret += mcgen(""" +''') + ret += mcgen(''' error_propagate(errp, local_err); QDECREF(qmp); } -""") - +''') return ret -# Following are the functions that generate an enum type for all defined -# events, similar to qapi-types.py. Here we already have enum name and -# values which were generated before and recorded in event_enum_*. It also -# works around the issue that "import qapi-types" can't work. - -def generate_event_enum_decl(event_enum_name, event_enum_values): - lookup_decl = mcgen(''' - -extern const char *%(event_enum_name)s_lookup[]; -''', - event_enum_name = event_enum_name) - - enum_decl = mcgen(''' -typedef enum %(event_enum_name)s { -''', - event_enum_name = event_enum_name) - - # append automatically generated _MAX value - enum_max_value = c_enum_const(event_enum_name, "MAX") - enum_values = event_enum_values + [ enum_max_value ] - - i = 0 - for value in enum_values: - enum_decl += mcgen(''' - %(value)s = %(i)d, -''', - value = value, - i = i) - i += 1 - - enum_decl += mcgen(''' -} %(event_enum_name)s; -''', - event_enum_name = event_enum_name) - - return lookup_decl + enum_decl +class QAPISchemaGenEventVisitor(QAPISchemaVisitor): + def __init__(self): + self.decl = None + self.defn = None + self._event_names = None -def generate_event_enum_lookup(event_enum_name, event_enum_strings): - ret = mcgen(''' + def visit_begin(self, schema): + self.decl = '' + self.defn = '' + self._event_names = [] -const char *%(event_enum_name)s_lookup[] = { -''', - event_enum_name = event_enum_name) + def visit_end(self): + self.decl += gen_enum(event_enum_name, self._event_names) + self.defn += gen_enum_lookup(event_enum_name, self._event_names) + self._event_names = None - for string in event_enum_strings: - ret += mcgen(''' - "%(string)s", -''', - string = string) + def visit_event(self, name, info, arg_type): + self.decl += gen_event_send_decl(name, arg_type) + self.defn += gen_event_send(name, arg_type) + self._event_names.append(name) - ret += mcgen(''' - NULL, -}; -''') - return ret (input_file, output_dir, do_c, do_h, prefix, dummy) = parse_command_line() @@ -263,35 +213,12 @@ fdecl.write(mcgen(''' ''', prefix=prefix)) -exprs = parse_schema(input_file) - event_enum_name = c_name(prefix + "QAPIEvent", protect=False) -event_enum_values = [] -event_enum_strings = [] - -for expr in exprs: - if expr.has_key('event'): - event_name = expr['event'] - params = expr.get('data') - if params and len(params) == 0: - params = None - - api_name = _generate_event_api_name(event_name, params) - ret = generate_event_declaration(api_name) - fdecl.write(ret) - - # We need an enum value per event - event_enum_value = c_enum_const(event_enum_name, event_name) - ret = generate_event_implement(api_name, event_name, params) - fdef.write(ret) - - # Record it, and generate enum later - event_enum_values.append(event_enum_value) - event_enum_strings.append(event_name) - -ret = generate_event_enum_decl(event_enum_name, event_enum_values) -fdecl.write(ret) -ret = generate_event_enum_lookup(event_enum_name, event_enum_strings) -fdef.write(ret) + +schema = QAPISchema(input_file) +gen = QAPISchemaGenEventVisitor() +schema.visit(gen) +fdef.write(gen.defn) +fdecl.write(gen.decl) close_output(fdef, fdecl) diff --git a/scripts/qapi-introspect.py b/scripts/qapi-introspect.py new file mode 100644 index 0000000..7d39320 --- /dev/null +++ b/scripts/qapi-introspect.py @@ -0,0 +1,213 @@ +# +# QAPI introspection generator +# +# Copyright (C) 2015 Red Hat, Inc. +# +# Authors: +# Markus Armbruster <armbru@redhat.com> +# +# This work is licensed under the terms of the GNU GPL, version 2. +# See the COPYING file in the top-level directory. + +from qapi import * + + +# Caveman's json.dumps() replacement (we're stuck at Python 2.4) +# TODO try to use json.dumps() once we get unstuck +def to_json(obj, level=0): + if obj is None: + ret = 'null' + elif isinstance(obj, str): + ret = '"' + obj.replace('"', r'\"') + '"' + elif isinstance(obj, list): + elts = [to_json(elt, level + 1) + for elt in obj] + ret = '[' + ', '.join(elts) + ']' + elif isinstance(obj, dict): + elts = ['"%s": %s' % (key.replace('"', r'\"'), + to_json(obj[key], level + 1)) + for key in sorted(obj.keys())] + ret = '{' + ', '.join(elts) + '}' + else: + assert False # not implemented + if level == 1: + ret = '\n' + ret + return ret + + +def to_c_string(string): + return '"' + string.replace('\\', r'\\').replace('"', r'\"') + '"' + + +class QAPISchemaGenIntrospectVisitor(QAPISchemaVisitor): + def __init__(self, unmask): + self._unmask = unmask + self.defn = None + self.decl = None + self._schema = None + self._jsons = None + self._used_types = None + self._name_map = None + + def visit_begin(self, schema): + self._schema = schema + self._jsons = [] + self._used_types = [] + self._name_map = {} + return QAPISchemaType # don't visit types for now + + def visit_end(self): + # visit the types that are actually used + jsons = self._jsons + self._jsons = [] + for typ in self._used_types: + typ.visit(self) + # generate C + # TODO can generate awfully long lines + jsons.extend(self._jsons) + name = prefix + 'qmp_schema_json' + self.decl = mcgen(''' +extern const char %(c_name)s[]; +''', + c_name=c_name(name)) + lines = to_json(jsons).split('\n') + c_string = '\n '.join([to_c_string(line) for line in lines]) + self.defn = mcgen(''' +const char %(c_name)s[] = %(c_string)s; +''', + c_name=c_name(name), + c_string=c_string) + self._schema = None + self._jsons = None + self._used_types = None + self._name_map = None + + def _name(self, name): + if self._unmask: + return name + if name not in self._name_map: + self._name_map[name] = '%d' % len(self._name_map) + return self._name_map[name] + + def _use_type(self, typ): + # Map the various integer types to plain int + if typ.json_type() == 'int': + typ = self._schema.lookup_type('int') + elif (isinstance(typ, QAPISchemaArrayType) and + typ.element_type.json_type() == 'int'): + typ = self._schema.lookup_type('intList') + # Add type to work queue if new + if typ not in self._used_types: + self._used_types.append(typ) + # Clients should examine commands and events, not types. Hide + # type names to reduce the temptation. Also saves a few + # characters. + if isinstance(typ, QAPISchemaBuiltinType): + return typ.name + return self._name(typ.name) + + def _gen_json(self, name, mtype, obj): + if mtype != 'command' and mtype != 'event' and mtype != 'builtin': + name = self._name(name) + obj['name'] = name + obj['meta-type'] = mtype + self._jsons.append(obj) + + def _gen_member(self, member): + ret = {'name': member.name, 'type': self._use_type(member.type)} + if member.optional: + ret['default'] = None + return ret + + def _gen_variants(self, tag_name, variants): + return {'tag': tag_name, + 'variants': [self._gen_variant(v) for v in variants]} + + def _gen_variant(self, variant): + return {'case': variant.name, 'type': self._use_type(variant.type)} + + def visit_builtin_type(self, name, info, json_type): + self._gen_json(name, 'builtin', {'json-type': json_type}) + + def visit_enum_type(self, name, info, values, prefix): + self._gen_json(name, 'enum', {'values': values}) + + def visit_array_type(self, name, info, element_type): + self._gen_json(name, 'array', + {'element-type': self._use_type(element_type)}) + + def visit_object_type_flat(self, name, info, 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_json(name, 'object', obj) + + def visit_alternate_type(self, name, info, variants): + self._gen_json(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, success_response): + arg_type = arg_type or self._schema.the_empty_object_type + ret_type = ret_type or self._schema.the_empty_object_type + self._gen_json(name, 'command', + {'arg-type': self._use_type(arg_type), + 'ret-type': self._use_type(ret_type)}) + + def visit_event(self, name, info, arg_type): + arg_type = arg_type or self._schema.the_empty_object_type + self._gen_json(name, 'event', {'arg-type': self._use_type(arg_type)}) + +# Debugging aid: unmask QAPI schema's type names +# We normally mask them, because they're not QMP wire ABI +opt_unmask = False + +(input_file, output_dir, do_c, do_h, prefix, opts) = \ + parse_command_line("u", ["unmask-non-abi-names"]) + +for o, a in opts: + if o in ("-u", "--unmask-non-abi-names"): + opt_unmask = True + +c_comment = ''' +/* + * QAPI/QMP schema introspection + * + * Copyright (C) 2015 Red Hat, Inc. + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ +''' +h_comment = ''' +/* + * QAPI/QMP schema introspection + * + * Copyright (C) 2015 Red Hat, Inc. + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ +''' + +(fdef, fdecl) = open_output(output_dir, do_c, do_h, prefix, + 'qmp-introspect.c', 'qmp-introspect.h', + c_comment, h_comment) + +fdef.write(mcgen(''' +#include "%(prefix)sqmp-introspect.h" + +''', + prefix=prefix)) + +schema = QAPISchema(input_file) +gen = QAPISchemaGenIntrospectVisitor(opt_unmask) +schema.visit(gen) +fdef.write(gen.defn) +fdecl.write(gen.decl) + +close_output(fdef, fdecl) diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py index a8453d1..b292682 100644 --- a/scripts/qapi-types.py +++ b/scripts/qapi-types.py @@ -2,96 +2,81 @@ # QAPI types generator # # Copyright IBM, Corp. 2011 +# Copyright (c) 2013-2015 Red Hat Inc. # # Authors: # Anthony Liguori <aliguori@us.ibm.com> +# Markus Armbruster <armbru@redhat.com> # # This work is licensed under the terms of the GNU GPL, version 2. # See the COPYING file in the top-level directory. -from ordereddict import OrderedDict from qapi import * -def generate_fwd_builtin(name): - return mcgen(''' - -typedef struct %(name)sList { - union { - %(type)s value; - uint64_t padding; - }; - struct %(name)sList *next; -} %(name)sList; -''', - type=c_type(name), - name=name) -def generate_fwd_struct(name): +def gen_fwd_object_or_array(name): return mcgen(''' -typedef struct %(name)s %(name)s; - -typedef struct %(name)sList { - union { - %(name)s *value; - uint64_t padding; - }; - struct %(name)sList *next; -} %(name)sList; +typedef struct %(c_name)s %(c_name)s; ''', - name=c_name(name)) + c_name=c_name(name)) -def generate_fwd_enum_struct(name): + +def gen_array(name, element_type): return mcgen(''' -typedef struct %(name)sList { +struct %(c_name)s { union { - %(name)s value; + %(c_type)s value; uint64_t padding; }; - struct %(name)sList *next; -} %(name)sList; + %(c_name)s *next; +}; ''', - name=c_name(name)) + c_name=c_name(name), c_type=element_type.c_type()) -def generate_struct_fields(members): + +def gen_struct_field(name, typ, optional): ret = '' - for argname, argentry, optional in parse_args(members): - if optional: - ret += mcgen(''' + if optional: + ret += mcgen(''' bool has_%(c_name)s; ''', - c_name=c_name(argname)) - ret += mcgen(''' + c_name=c_name(name)) + ret += mcgen(''' %(c_type)s %(c_name)s; ''', - c_type=c_type(argentry), c_name=c_name(argname)) - + c_type=typ.c_type(), c_name=c_name(name)) return ret -def generate_struct(expr): - structname = expr.get('struct', "") - members = expr['data'] - base = expr.get('base') +def gen_struct_fields(members): + ret = '' + for memb in members: + ret += gen_struct_field(memb.name, memb.type, memb.optional) + return ret + + +def gen_struct(name, base, members): ret = mcgen(''' -struct %(name)s { +struct %(c_name)s { ''', - name=c_name(structname)) + c_name=c_name(name)) if base: - ret += generate_struct_fields({'base': base}) + ret += gen_struct_field('base', base, False) - ret += generate_struct_fields(members) + ret += gen_struct_fields(members) # Make sure that all structs have at least one field; this avoids - # potential issues with attempting to malloc space for zero-length structs - # in C, and also incompatibility with C++ (where an empty struct is size 1). + # potential issues with attempting to malloc space for zero-length + # structs in C, and also incompatibility with C++ (where an empty + # struct is size 1). if not base and not members: - ret += mcgen(''' + ret += mcgen(''' char qapi_dummy_field_for_empty_struct; ''') @@ -101,81 +86,32 @@ struct %(name)s { return ret -def generate_enum_lookup(name, values, prefix=None): - ret = mcgen(''' - -const char *const %(name)s_lookup[] = { -''', - name=c_name(name)) - for value in values: - index = c_enum_const(name, value, prefix) - ret += mcgen(''' - [%(index)s] = "%(value)s", -''', - index = index, value = value) - - max_index = c_enum_const(name, 'MAX', prefix) - ret += mcgen(''' - [%(max_index)s] = NULL, -}; -''', - max_index=max_index) - return ret - -def generate_enum(name, values, prefix=None): - name = c_name(name) - lookup_decl = mcgen(''' -extern const char *const %(name)s_lookup[]; -''', - name=name) - - enum_decl = mcgen(''' - -typedef enum %(name)s { -''', - name=name) - - # append automatically generated _MAX value - enum_values = values + [ 'MAX' ] - - i = 0 - for value in enum_values: - enum_full_value = c_enum_const(name, value, prefix) - enum_decl += mcgen(''' - %(enum_full_value)s = %(i)d, -''', - enum_full_value = enum_full_value, - i=i) - i += 1 +def gen_alternate_qtypes_decl(name): + return mcgen(''' - enum_decl += mcgen(''' -} %(name)s; +extern const int %(c_name)s_qtypes[]; ''', - name=name) + c_name=c_name(name)) - return enum_decl + lookup_decl - -def generate_alternate_qtypes(expr): - - name = expr['alternate'] - members = expr['data'] +def gen_alternate_qtypes(name, variants): ret = mcgen(''' -const int %(name)s_qtypes[QTYPE_MAX] = { +const int %(c_name)s_qtypes[QTYPE_MAX] = { ''', - name=c_name(name)) + c_name=c_name(name)) - for key in members: - qtype = find_alternate_member_qtype(members[key]) - assert qtype, "Invalid alternate member" + for var in variants.variants: + qtype = var.type.alternate_qtype() + assert qtype ret += mcgen(''' [%(qtype)s] = %(enum_const)s, ''', - qtype = qtype, - enum_const = c_enum_const(name + 'Kind', key)) + qtype=qtype, + enum_const=c_enum_const(variants.tag_member.type.name, + var.name)) ret += mcgen(''' }; @@ -183,41 +119,26 @@ const int %(name)s_qtypes[QTYPE_MAX] = { return ret -def generate_union(expr, meta): - - name = c_name(expr[meta]) - typeinfo = expr['data'] - - base = expr.get('base') - discriminator = expr.get('discriminator') - - enum_define = discriminator_find_enum_define(expr) - if enum_define: - discriminator_type_name = enum_define['enum_name'] - else: - discriminator_type_name = '%sKind' % (name) - +def gen_union(name, base, variants): ret = mcgen(''' -struct %(name)s { +struct %(c_name)s { ''', - name=name) + c_name=c_name(name)) if base: ret += mcgen(''' /* Members inherited from %(c_name)s: */ ''', - c_name=c_name(base)) - base_fields = find_struct(base)['data'] - ret += generate_struct_fields(base_fields) + c_name=c_name(base.name)) + ret += gen_struct_fields(base.members) ret += mcgen(''' /* Own members: */ ''') else: - assert not discriminator ret += mcgen(''' - %(discriminator_type_name)s kind; + %(c_type)s kind; ''', - discriminator_type_name=c_name(discriminator_type_name)) + c_type=c_name(variants.tag_member.type.name)) # FIXME: What purpose does data serve, besides preventing a union that # has a branch named 'data'? We use it in qapi-visit.py to decide @@ -231,39 +152,41 @@ struct %(name)s { union { /* union tag is @%(c_name)s */ void *data; ''', - c_name=c_name(discriminator or 'kind')) - - for key in typeinfo: + # TODO ugly special case for simple union + # Use same tag name in C as on the wire to get rid of + # it, then: c_name=c_name(variants.tag_member.name) + c_name=c_name(variants.tag_name or 'kind')) + + for var in variants.variants: + # Ugly special case for simple union TODO get rid of it + typ = var.simple_union_type() or var.type ret += mcgen(''' %(c_type)s %(c_name)s; ''', - c_type=c_type(typeinfo[key]), - c_name=c_name(key)) + c_type=typ.c_type(), + c_name=c_name(var.name)) ret += mcgen(''' }; }; ''') - if meta == 'alternate': - ret += mcgen(''' -extern const int %(name)s_qtypes[]; -''', - name=name) - return ret -def generate_type_cleanup_decl(name): + +def gen_type_cleanup_decl(name): ret = mcgen(''' -void qapi_free_%(name)s(%(c_type)s obj); + +void qapi_free_%(c_name)s(%(c_name)s *obj); ''', - c_type=c_type(name), name=c_name(name)) + c_name=c_name(name)) return ret -def generate_type_cleanup(name): + +def gen_type_cleanup(name): ret = mcgen(''' -void qapi_free_%(name)s(%(c_type)s obj) +void qapi_free_%(c_name)s(%(c_name)s *obj) { QapiDeallocVisitor *md; Visitor *v; @@ -274,13 +197,83 @@ void qapi_free_%(name)s(%(c_type)s obj) md = qapi_dealloc_visitor_new(); v = qapi_dealloc_get_visitor(md); - visit_type_%(name)s(v, &obj, NULL, NULL); + visit_type_%(c_name)s(v, &obj, NULL, NULL); qapi_dealloc_visitor_cleanup(md); } ''', - c_type=c_type(name), name=c_name(name)) + c_name=c_name(name)) return ret + +class QAPISchemaGenTypeVisitor(QAPISchemaVisitor): + def __init__(self): + self.decl = None + self.defn = None + self._fwdecl = None + self._fwdefn = None + self._btin = None + + def visit_begin(self, schema): + self.decl = '' + self.defn = '' + self._fwdecl = '' + self._fwdefn = '' + self._btin = guardstart('QAPI_TYPES_BUILTIN') + + def visit_end(self): + self.decl = self._fwdecl + self.decl + self._fwdecl = None + self.defn = self._fwdefn + self.defn + self._fwdefn = None + # To avoid header dependency hell, we always generate + # declarations for built-in types in our header files and + # simply guard them. See also do_builtins (command line + # option -b). + self._btin += guardend('QAPI_TYPES_BUILTIN') + self.decl = self._btin + self.decl + self._btin = None + + def _gen_type_cleanup(self, name): + self.decl += gen_type_cleanup_decl(name) + self.defn += gen_type_cleanup(name) + + def visit_enum_type(self, name, info, values, prefix): + self._fwdecl += gen_enum(name, values, prefix) + self._fwdefn += gen_enum_lookup(name, values, prefix) + + def visit_array_type(self, name, info, element_type): + if isinstance(element_type, QAPISchemaBuiltinType): + self._btin += gen_fwd_object_or_array(name) + self._btin += gen_array(name, element_type) + self._btin += gen_type_cleanup_decl(name) + if do_builtins: + self.defn += gen_type_cleanup(name) + else: + self._fwdecl += gen_fwd_object_or_array(name) + self.decl += gen_array(name, element_type) + self._gen_type_cleanup(name) + + def visit_object_type(self, name, info, base, members, variants): + if info: + self._fwdecl += gen_fwd_object_or_array(name) + if variants: + assert not members # not implemented + self.decl += gen_union(name, base, variants) + else: + self.decl += gen_struct(name, base, members) + self._gen_type_cleanup(name) + + def visit_alternate_type(self, name, info, variants): + self._fwdecl += gen_fwd_object_or_array(name) + self._fwdefn += gen_alternate_qtypes(name, variants) + self.decl += gen_union(name, None, variants) + self.decl += gen_alternate_qtypes_decl(name) + self._gen_type_cleanup(name) + +# If you link code generated from multiple schemata, you want only one +# instance of the code for built-in types. Generate it only when +# do_builtins, enabled by command line option -b. See also +# QAPISchemaGenTypeVisitor.visit_end(). do_builtins = False (input_file, output_dir, do_c, do_h, prefix, opts) = \ @@ -334,81 +327,13 @@ fdef.write(mcgen(''' fdecl.write(mcgen(''' #include <stdbool.h> #include <stdint.h> +#include "qapi/qmp/qobject.h" ''')) -exprs = parse_schema(input_file) - -fdecl.write(guardstart("QAPI_TYPES_BUILTIN_STRUCT_DECL")) -for typename in builtin_types.keys(): - fdecl.write(generate_fwd_builtin(typename)) -fdecl.write(guardend("QAPI_TYPES_BUILTIN_STRUCT_DECL")) - -for expr in exprs: - ret = "" - if expr.has_key('struct'): - ret += generate_fwd_struct(expr['struct']) - elif expr.has_key('enum'): - ret += generate_enum(expr['enum'], expr['data'], - expr.get('prefix')) - ret += generate_fwd_enum_struct(expr['enum']) - fdef.write(generate_enum_lookup(expr['enum'], expr['data'], - expr.get('prefix'))) - elif expr.has_key('union'): - ret += generate_fwd_struct(expr['union']) - enum_define = discriminator_find_enum_define(expr) - if not enum_define: - ret += generate_enum('%sKind' % expr['union'], expr['data'].keys()) - fdef.write(generate_enum_lookup('%sKind' % expr['union'], - expr['data'].keys())) - elif expr.has_key('alternate'): - ret += generate_fwd_struct(expr['alternate']) - ret += generate_enum('%sKind' % expr['alternate'], expr['data'].keys()) - fdef.write(generate_enum_lookup('%sKind' % expr['alternate'], - expr['data'].keys())) - fdef.write(generate_alternate_qtypes(expr)) - else: - continue - fdecl.write(ret) - -# to avoid header dependency hell, we always generate declarations -# for built-in types in our header files and simply guard them -fdecl.write(guardstart("QAPI_TYPES_BUILTIN_CLEANUP_DECL")) -for typename in builtin_types.keys(): - fdecl.write(generate_type_cleanup_decl(typename + "List")) -fdecl.write(guardend("QAPI_TYPES_BUILTIN_CLEANUP_DECL")) - -# ...this doesn't work for cases where we link in multiple objects that -# have the functions defined, so we use -b option to provide control -# over these cases -if do_builtins: - for typename in builtin_types.keys(): - fdef.write(generate_type_cleanup(typename + "List")) - -for expr in exprs: - ret = "" - if expr.has_key('struct'): - ret += generate_struct(expr) + "\n" - ret += generate_type_cleanup_decl(expr['struct'] + "List") - fdef.write(generate_type_cleanup(expr['struct'] + "List")) - ret += generate_type_cleanup_decl(expr['struct']) - fdef.write(generate_type_cleanup(expr['struct'])) - elif expr.has_key('union'): - ret += generate_union(expr, 'union') + "\n" - ret += generate_type_cleanup_decl(expr['union'] + "List") - fdef.write(generate_type_cleanup(expr['union'] + "List")) - ret += generate_type_cleanup_decl(expr['union']) - fdef.write(generate_type_cleanup(expr['union'])) - elif expr.has_key('alternate'): - ret += generate_union(expr, 'alternate') + "\n" - ret += generate_type_cleanup_decl(expr['alternate'] + "List") - fdef.write(generate_type_cleanup(expr['alternate'] + "List")) - ret += generate_type_cleanup_decl(expr['alternate']) - fdef.write(generate_type_cleanup(expr['alternate'])) - elif expr.has_key('enum'): - ret += "\n" + generate_type_cleanup_decl(expr['enum'] + "List") - fdef.write(generate_type_cleanup(expr['enum'] + "List")) - else: - continue - fdecl.write(ret) +schema = QAPISchema(input_file) +gen = QAPISchemaGenTypeVisitor() +schema.visit(gen) +fdef.write(gen.defn) +fdecl.write(gen.decl) close_output(fdef, fdecl) diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py index 3cd662b..97343cf 100644 --- a/scripts/qapi-visit.py +++ b/scripts/qapi-visit.py @@ -12,25 +12,36 @@ # This work is licensed under the terms of the GNU GPL, version 2. # See the COPYING file in the top-level directory. -from ordereddict import OrderedDict from qapi import * import re implicit_structs_seen = set() struct_fields_seen = set() -def generate_visit_implicit_struct(type): - if type in implicit_structs_seen: + +def gen_visit_decl(name, scalar=False): + c_type = c_name(name) + ' *' + if not scalar: + c_type += '*' + return mcgen(''' +void visit_type_%(c_name)s(Visitor *m, %(c_type)sobj, const char *name, Error **errp); +''', + c_name=c_name(name), c_type=c_type) + + +def gen_visit_implicit_struct(typ): + if typ in implicit_structs_seen: return '' - implicit_structs_seen.add(type) + implicit_structs_seen.add(typ) + ret = '' - if type not in struct_fields_seen: + if typ.name not in struct_fields_seen: # Need a forward declaration ret += mcgen(''' static void visit_type_%(c_type)s_fields(Visitor *m, %(c_type)s **obj, Error **errp); ''', - c_type=type_name(type)) + c_type=typ.c_name()) ret += mcgen(''' @@ -46,52 +57,53 @@ static void visit_type_implicit_%(c_type)s(Visitor *m, %(c_type)s **obj, Error * error_propagate(errp, err); } ''', - c_type=type_name(type)) + c_type=typ.c_name()) return ret -def generate_visit_struct_fields(name, members, base = None): + +def gen_visit_struct_fields(name, base, members): struct_fields_seen.add(name) ret = '' if base: - ret += generate_visit_implicit_struct(base) + ret += gen_visit_implicit_struct(base) ret += mcgen(''' -static void visit_type_%(name)s_fields(Visitor *m, %(name)s **obj, Error **errp) +static void visit_type_%(c_name)s_fields(Visitor *m, %(c_name)s **obj, Error **errp) { Error *err = NULL; ''', - name=c_name(name)) + c_name=c_name(name)) push_indent() if base: ret += mcgen(''' -visit_type_implicit_%(type)s(m, &(*obj)->%(c_name)s, &err); +visit_type_implicit_%(c_type)s(m, &(*obj)->%(c_name)s, &err); if (err) { goto out; } ''', - type=type_name(base), c_name=c_name('base')) + c_type=base.c_name(), c_name=c_name('base')) - for argname, argentry, optional in parse_args(members): - if optional: + for memb in members: + if memb.optional: ret += mcgen(''' visit_optional(m, &(*obj)->has_%(c_name)s, "%(name)s", &err); if (!err && (*obj)->has_%(c_name)s) { ''', - c_name=c_name(argname), name=argname) + c_name=c_name(memb.name), name=memb.name) push_indent() ret += mcgen(''' -visit_type_%(type)s(m, &(*obj)->%(c_name)s, "%(name)s", &err); +visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "%(name)s", &err); ''', - type=type_name(argentry), c_name=c_name(argname), - name=argname) + c_type=memb.type.c_name(), c_name=c_name(memb.name), + name=memb.name) - if optional: + if memb.optional: pop_indent() ret += mcgen(''' } @@ -103,7 +115,7 @@ if (err) { ''') pop_indent() - if re.search('^ *goto out\\;', ret, re.MULTILINE): + if re.search('^ *goto out;', ret, re.MULTILINE): ret += mcgen(''' out: @@ -115,12 +127,17 @@ out: return ret -def generate_visit_struct_body(name): +def gen_visit_struct(name, base, members): + ret = gen_visit_struct_fields(name, base, members) + # FIXME: if *obj is NULL on entry, and visit_start_struct() assigns to # *obj, but then visit_type_FOO_fields() fails, we should clean up *obj # rather than leaving it non-NULL. As currently written, the caller must # call qapi_free_FOO() to avoid a memory leak of the partial FOO. - ret = mcgen(''' + ret += mcgen(''' + +void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error **errp) +{ Error *err = NULL; visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(c_name)s), &err); @@ -131,37 +148,17 @@ def generate_visit_struct_body(name): visit_end_struct(m, &err); } error_propagate(errp, err); +} ''', - name=name, c_name=c_name(name)) + name=name, c_name=c_name(name)) return ret -def generate_visit_struct(expr): - - name = expr['struct'] - members = expr['data'] - base = expr.get('base') - ret = generate_visit_struct_fields(name, members, base) - - ret += mcgen(''' - -void visit_type_%(name)s(Visitor *m, %(name)s **obj, const char *name, Error **errp) -{ -''', - name=c_name(name)) - - ret += generate_visit_struct_body(name) - - ret += mcgen(''' -} -''') - return ret - -def generate_visit_list(name): +def gen_visit_list(name, element_type): return mcgen(''' -void visit_type_%(name)sList(Visitor *m, %(name)sList **obj, const char *name, Error **errp) +void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error **errp) { Error *err = NULL; GenericList *i, **prev; @@ -174,8 +171,8 @@ void visit_type_%(name)sList(Visitor *m, %(name)sList **obj, const char *name, E for (prev = (GenericList **)obj; !err && (i = visit_next_list(m, prev, &err)) != NULL; prev = &i) { - %(name)sList *native_i = (%(name)sList *)i; - visit_type_%(name)s(m, &native_i->value, NULL, &err); + %(c_name)s *native_i = (%(c_name)s *)i; + visit_type_%(c_elt_type)s(m, &native_i->value, NULL, &err); } error_propagate(errp, err); @@ -185,9 +182,10 @@ out: error_propagate(errp, err); } ''', - name=type_name(name)) + c_name=c_name(name), c_elt_type=element_type.c_name()) + -def generate_visit_enum(name): +def gen_visit_enum(name): return mcgen(''' void visit_type_%(c_name)s(Visitor *m, %(c_name)s *obj, const char *name, Error **errp) @@ -197,44 +195,36 @@ void visit_type_%(c_name)s(Visitor *m, %(c_name)s *obj, const char *name, Error ''', c_name=c_name(name), name=name) -def generate_visit_alternate(name, members): + +def gen_visit_alternate(name, variants): ret = mcgen(''' -void visit_type_%(name)s(Visitor *m, %(name)s **obj, const char *name, Error **errp) +void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error **errp) { Error *err = NULL; - visit_start_implicit_struct(m, (void**) obj, sizeof(%(name)s), &err); + visit_start_implicit_struct(m, (void**) obj, sizeof(%(c_name)s), &err); if (err) { goto out; } - visit_get_next_type(m, (int*) &(*obj)->kind, %(name)s_qtypes, name, &err); + visit_get_next_type(m, (int*) &(*obj)->kind, %(c_name)s_qtypes, name, &err); if (err) { goto out_end; } switch ((*obj)->kind) { ''', - name=c_name(name)) - - # For alternate, always use the default enum type automatically generated - # as name + 'Kind' - disc_type = c_name(name) + 'Kind' - - for key in members: - assert (members[key] in builtin_types.keys() - or find_struct(members[key]) - or find_union(members[key]) - or find_enum(members[key])), "Invalid alternate member" + c_name=c_name(name)) - enum_full_value = c_enum_const(disc_type, key) + for var in variants.variants: ret += mcgen(''' - case %(enum_full_value)s: + case %(case)s: visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, name, &err); break; ''', - enum_full_value = enum_full_value, - c_type = type_name(members[key]), - c_name = c_name(key)) + case=c_enum_const(variants.tag_member.type.name, + var.name), + c_type=var.type.c_name(), + c_name=c_name(var.name)) ret += mcgen(''' default: @@ -252,34 +242,17 @@ out: return ret -def generate_visit_union(expr): - - name = expr['union'] - members = expr['data'] - - base = expr.get('base') - discriminator = expr.get('discriminator') - - enum_define = discriminator_find_enum_define(expr) - if enum_define: - # Use the enum type as discriminator - ret = "" - disc_type = c_name(enum_define['enum_name']) - else: - # There will always be a discriminator in the C switch code, by default - # it is an enum type generated silently - ret = generate_visit_enum(name + 'Kind') - disc_type = c_name(name) + 'Kind' +def gen_visit_union(name, base, variants): + ret = '' if base: - assert discriminator - base_fields = find_struct(base)['data'].copy() - del base_fields[discriminator] - ret += generate_visit_struct_fields(name, base_fields) + members = [m for m in base.members if m != variants.tag_member] + ret += gen_visit_struct_fields(name, None, members) - if discriminator: - for key in members: - ret += generate_visit_implicit_struct(members[key]) + for var in variants.variants: + # Ugly special case for simple union TODO get rid of it + if not var.simple_union_type(): + ret += gen_visit_implicit_struct(var.type) ret += mcgen(''' @@ -297,48 +270,57 @@ void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error if base: ret += mcgen(''' - visit_type_%(name)s_fields(m, obj, &err); + visit_type_%(c_name)s_fields(m, obj, &err); if (err) { goto out_obj; } ''', - name=c_name(name)) - - if not discriminator: - tag = 'kind' - disc_key = "type" - else: - tag = discriminator - disc_key = discriminator + c_name=c_name(name)) + + tag_key = variants.tag_member.name + if not variants.tag_name: + # we pointlessly use a different key for simple unions + tag_key = 'type' ret += mcgen(''' - visit_type_%(disc_type)s(m, &(*obj)->%(c_tag)s, "%(disc_key)s", &err); + visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "%(name)s", &err); if (err) { goto out_obj; } if (!visit_start_union(m, !!(*obj)->data, &err) || err) { goto out_obj; } - switch ((*obj)->%(c_tag)s) { + switch ((*obj)->%(c_name)s) { ''', - disc_type = disc_type, - c_tag=c_name(tag), - disc_key = disc_key) - - for key in members: - if not discriminator: - fmt = 'visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "data", &err);' + c_type=variants.tag_member.type.c_name(), + # TODO ugly special case for simple union + # Use same tag name in C as on the wire to get rid of + # it, then: c_name=c_name(variants.tag_member.name) + c_name=c_name(variants.tag_name or 'kind'), + name=tag_key) + + for var in variants.variants: + # TODO ugly special case for simple union + simple_union_type = var.simple_union_type() + ret += mcgen(''' + case %(case)s: +''', + case=c_enum_const(variants.tag_member.type.name, + var.name)) + if simple_union_type: + ret += mcgen(''' + visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "data", &err); +''', + c_type=simple_union_type.c_name(), + c_name=c_name(var.name)) else: - fmt = 'visit_type_implicit_%(c_type)s(m, &(*obj)->%(c_name)s, &err);' - - enum_full_value = c_enum_const(disc_type, key) + ret += mcgen(''' + visit_type_implicit_%(c_type)s(m, &(*obj)->%(c_name)s, &err); +''', + c_type=var.type.c_name(), + c_name=c_name(var.name)) ret += mcgen(''' - case %(enum_full_value)s: - ''' + fmt + ''' break; -''', - enum_full_value = enum_full_value, - c_type=type_name(members[key]), - c_name=c_name(key)) +''') ret += mcgen(''' default: @@ -359,38 +341,59 @@ out: return ret -def generate_declaration(name, builtin_type=False): - ret = "" - if not builtin_type: - name = c_name(name) - ret += mcgen(''' - -void visit_type_%(name)s(Visitor *m, %(name)s **obj, const char *name, Error **errp); -''', - name=name) - - ret += mcgen(''' -void visit_type_%(name)sList(Visitor *m, %(name)sList **obj, const char *name, Error **errp); -''', - name=name) - - return ret - -def generate_enum_declaration(name): - ret = mcgen(''' -void visit_type_%(name)sList(Visitor *m, %(name)sList **obj, const char *name, Error **errp); -''', - name=c_name(name)) - - return ret - -def generate_decl_enum(name): - return mcgen(''' - -void visit_type_%(name)s(Visitor *m, %(name)s *obj, const char *name, Error **errp); -''', - name=c_name(name)) +class QAPISchemaGenVisitVisitor(QAPISchemaVisitor): + def __init__(self): + self.decl = None + self.defn = None + self._btin = None + + def visit_begin(self, schema): + self.decl = '' + self.defn = '' + self._btin = guardstart('QAPI_VISIT_BUILTIN') + + def visit_end(self): + # To avoid header dependency hell, we always generate + # declarations for built-in types in our header files and + # simply guard them. See also do_builtins (command line + # option -b). + self._btin += guardend('QAPI_VISIT_BUILTIN') + self.decl = self._btin + self.decl + self._btin = None + + def visit_enum_type(self, name, info, values, prefix): + self.decl += gen_visit_decl(name, scalar=True) + self.defn += gen_visit_enum(name) + + def visit_array_type(self, name, info, element_type): + decl = gen_visit_decl(name) + defn = gen_visit_list(name, element_type) + if isinstance(element_type, QAPISchemaBuiltinType): + self._btin += decl + if do_builtins: + self.defn += defn + else: + self.decl += decl + self.defn += defn + + def visit_object_type(self, name, info, base, members, variants): + if info: + self.decl += gen_visit_decl(name) + if variants: + assert not members # not implemented + self.defn += gen_visit_union(name, base, variants) + else: + self.defn += gen_visit_struct(name, base, members) + + def visit_alternate_type(self, name, info, variants): + self.decl += gen_visit_decl(name) + self.defn += gen_visit_alternate(name, variants) + +# If you link code generated from multiple schemata, you want only one +# instance of the code for built-in types. Generate it only when +# do_builtins, enabled by command line option -b. See also +# QAPISchemaGenVisitVisitor.visit_end(). do_builtins = False (input_file, output_dir, do_c, do_h, prefix, opts) = \ @@ -437,7 +440,7 @@ fdef.write(mcgen(''' #include "qemu-common.h" #include "%(prefix)sqapi-visit.h" ''', - prefix = prefix)) + prefix=prefix)) fdecl.write(mcgen(''' #include "qapi/visitor.h" @@ -446,56 +449,10 @@ fdecl.write(mcgen(''' ''', prefix=prefix)) -exprs = parse_schema(input_file) - -# to avoid header dependency hell, we always generate declarations -# for built-in types in our header files and simply guard them -fdecl.write(guardstart("QAPI_VISIT_BUILTIN_VISITOR_DECL")) -for typename in builtin_types.keys(): - fdecl.write(generate_declaration(typename, builtin_type=True)) -fdecl.write(guardend("QAPI_VISIT_BUILTIN_VISITOR_DECL")) - -# ...this doesn't work for cases where we link in multiple objects that -# have the functions defined, so we use -b option to provide control -# over these cases -if do_builtins: - for typename in builtin_types.keys(): - fdef.write(generate_visit_list(typename)) - -for expr in exprs: - if expr.has_key('struct'): - ret = generate_visit_struct(expr) - ret += generate_visit_list(expr['struct']) - fdef.write(ret) - - ret = generate_declaration(expr['struct']) - fdecl.write(ret) - elif expr.has_key('union'): - ret = generate_visit_union(expr) - ret += generate_visit_list(expr['union']) - fdef.write(ret) - - enum_define = discriminator_find_enum_define(expr) - ret = "" - if not enum_define: - ret = generate_decl_enum('%sKind' % expr['union']) - ret += generate_declaration(expr['union']) - fdecl.write(ret) - elif expr.has_key('alternate'): - ret = generate_visit_alternate(expr['alternate'], expr['data']) - ret += generate_visit_list(expr['alternate']) - fdef.write(ret) - - ret = generate_decl_enum('%sKind' % expr['alternate']) - ret += generate_declaration(expr['alternate']) - fdecl.write(ret) - elif expr.has_key('enum'): - ret = generate_visit_list(expr['enum']) - ret += generate_visit_enum(expr['enum']) - fdef.write(ret) - - ret = generate_decl_enum(expr['enum']) - ret += generate_enum_declaration(expr['enum']) - fdecl.write(ret) +schema = QAPISchema(input_file) +gen = QAPISchemaGenVisitVisitor() +schema.visit(gen) +fdef.write(gen.defn) +fdecl.write(gen.decl) close_output(fdef, fdecl) diff --git a/scripts/qapi.py b/scripts/qapi.py index c4423b7..06478bb 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -33,12 +33,14 @@ builtin_types = { 'uint32': 'QTYPE_QINT', 'uint64': 'QTYPE_QINT', 'size': 'QTYPE_QINT', + 'any': None, # any qtype_code possible, actually } # Whitelist of commands allowed to return a non-dictionary returns_whitelist = [ # From QMP: 'human-monitor-command', + 'qom-get', 'query-migrate-cache-size', 'query-tpm-models', 'query-tpm-types', @@ -103,7 +105,7 @@ class QAPIExprError(Exception): return error_path(self.info['parent']) + \ "%s:%d: %s" % (self.info['file'], self.info['line'], self.msg) -class QAPISchema: +class QAPISchemaParser(object): def __init__(self, fp, previously_included = [], incl_info = None): abs_fname = os.path.abspath(fp.name) @@ -149,8 +151,8 @@ class QAPISchema: except IOError, e: raise QAPIExprError(expr_info, '%s: %s' % (e.strerror, include)) - exprs_include = QAPISchema(fobj, previously_included, - expr_info) + exprs_include = QAPISchemaParser(fobj, previously_included, + expr_info) self.exprs.extend(exprs_include.exprs) else: expr_elem = {'expr': expr, @@ -302,6 +304,8 @@ class QAPISchema: # # Semantic analysis of schema expressions +# TODO fold into QAPISchema +# TODO catching name collisions in generated code would be nice # def find_base_fields(base): @@ -424,15 +428,12 @@ def is_enum(name): def check_type(expr_info, source, value, allow_array = False, allow_dict = False, allow_optional = False, - allow_star = False, allow_metas = []): + allow_metas = []): global all_names if value is None: return - if allow_star and value == '**': - return - # Check if array type for value is okay if isinstance(value, list): if not allow_array: @@ -446,10 +447,6 @@ def check_type(expr_info, source, value, allow_array = False, # Check if type name for value is okay if isinstance(value, str): - if value == '**': - raise QAPIExprError(expr_info, - "%s uses '**' but did not request 'gen':false" - % source) if not value in all_names: raise QAPIExprError(expr_info, "%s uses unknown type '%s'" @@ -475,7 +472,7 @@ def check_type(expr_info, source, value, allow_array = False, # Todo: allow dictionaries to represent default values of # an optional argument. check_type(expr_info, "Member '%s' of %s" % (key, source), arg, - allow_array=True, allow_star=allow_star, + allow_array=True, allow_metas=['built-in', 'union', 'alternate', 'struct', 'enum']) @@ -495,18 +492,16 @@ def check_member_clash(expr_info, base_name, data, source = ""): def check_command(expr, expr_info): name = expr['command'] - allow_star = expr.has_key('gen') check_type(expr_info, "'data' for command '%s'" % name, expr.get('data'), allow_dict=True, allow_optional=True, - allow_metas=['struct'], allow_star=allow_star) + allow_metas=['struct']) returns_meta = ['union', 'struct'] if name in returns_whitelist: returns_meta += ['built-in', 'alternate', 'enum'] check_type(expr_info, "'returns' for command '%s'" % name, expr.get('returns'), allow_array=True, - allow_optional=True, allow_metas=returns_meta, - allow_star=allow_star) + allow_optional=True, allow_metas=returns_meta) def check_event(expr, expr_info): global events @@ -751,36 +746,538 @@ def check_exprs(exprs): else: assert False, 'unexpected meta type' - return map(lambda expr_elem: expr_elem['expr'], exprs) + return exprs -def parse_schema(fname): - try: - schema = QAPISchema(open(fname, "r")) - return check_exprs(schema.exprs) - except (QAPISchemaError, QAPIExprError), e: - print >>sys.stderr, e - exit(1) # -# Code generation helpers +# Schema compiler frontend # -def parse_args(typeinfo): - if isinstance(typeinfo, str): - struct = find_struct(typeinfo) - assert struct != None - typeinfo = struct['data'] +class QAPISchemaEntity(object): + def __init__(self, name, info): + assert isinstance(name, str) + self.name = name + self.info = info + + def c_name(self): + return c_name(self.name) + + def check(self, schema): + pass + + def visit(self, visitor): + pass + + +class QAPISchemaVisitor(object): + def visit_begin(self, schema): + pass + + def visit_end(self): + pass + + def visit_builtin_type(self, name, info, json_type): + pass + + def visit_enum_type(self, name, info, values, prefix): + pass + + def visit_array_type(self, name, info, element_type): + pass + + def visit_object_type(self, name, info, base, members, variants): + pass + + def visit_object_type_flat(self, name, info, members, variants): + pass + + def visit_alternate_type(self, name, info, variants): + pass + + def visit_command(self, name, info, arg_type, ret_type, + gen, success_response): + pass + + def visit_event(self, name, info, arg_type): + pass + + +class QAPISchemaType(QAPISchemaEntity): + def c_type(self, is_param=False): + return c_name(self.name) + pointer_suffix + + def c_null(self): + return 'NULL' + + def json_type(self): + pass + + def alternate_qtype(self): + json2qtype = { + 'string': 'QTYPE_QSTRING', + 'number': 'QTYPE_QFLOAT', + 'int': 'QTYPE_QINT', + 'boolean': 'QTYPE_QBOOL', + 'object': 'QTYPE_QDICT' + } + return json2qtype.get(self.json_type()) + + +class QAPISchemaBuiltinType(QAPISchemaType): + def __init__(self, name, json_type, c_type, c_null): + QAPISchemaType.__init__(self, name, None) + assert not c_type or isinstance(c_type, str) + assert json_type in ('string', 'number', 'int', 'boolean', 'null', + 'value') + self._json_type_name = json_type + self._c_type_name = c_type + self._c_null_val = c_null + + def c_name(self): + return self.name + + def c_type(self, is_param=False): + if is_param and self.name == 'str': + return 'const ' + self._c_type_name + return self._c_type_name + + def c_null(self): + return self._c_null_val + + def json_type(self): + return self._json_type_name + + def visit(self, visitor): + visitor.visit_builtin_type(self.name, self.info, self.json_type()) + + +class QAPISchemaEnumType(QAPISchemaType): + def __init__(self, name, info, values, prefix): + QAPISchemaType.__init__(self, name, info) + for v in values: + assert isinstance(v, str) + assert prefix is None or isinstance(prefix, str) + self.values = values + self.prefix = prefix + + def check(self, schema): + assert len(set(self.values)) == len(self.values) + + def c_type(self, is_param=False): + return c_name(self.name) + + def c_null(self): + return c_enum_const(self.name, (self.values + ['MAX'])[0], + self.prefix) + + def json_type(self): + return 'string' + + def visit(self, visitor): + visitor.visit_enum_type(self.name, self.info, + self.values, self.prefix) + + +class QAPISchemaArrayType(QAPISchemaType): + def __init__(self, name, info, element_type): + 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 + + def json_type(self): + return 'array' + + def visit(self, visitor): + visitor.visit_array_type(self.name, self.info, self.element_type) + + +class QAPISchemaObjectType(QAPISchemaType): + def __init__(self, name, info, base, local_members, variants): + QAPISchemaType.__init__(self, name, info) + assert base is None or isinstance(base, str) + for m in local_members: + assert isinstance(m, QAPISchemaObjectTypeMember) + assert (variants is None or + isinstance(variants, QAPISchemaObjectTypeVariants)) + self._base_name = base + self.base = None + self.local_members = local_members + self.variants = variants + self.members = None + + def check(self, schema): + assert self.members is not False # not running in cycles + if self.members: + return + self.members = False # mark as being checked + if self._base_name: + self.base = schema.lookup_type(self._base_name) + assert isinstance(self.base, QAPISchemaObjectType) + assert not self.base.variants # not implemented + self.base.check(schema) + members = list(self.base.members) + else: + members = [] + seen = {} + for m in members: + seen[m.name] = m + for m in self.local_members: + m.check(schema, members, seen) + if self.variants: + self.variants.check(schema, members, seen) + self.members = members + + def c_name(self): + assert self.info + return QAPISchemaType.c_name(self) + + def c_type(self, is_param=False): + assert self.info + return QAPISchemaType.c_type(self) + + def json_type(self): + return 'object' + + def visit(self, visitor): + visitor.visit_object_type(self.name, self.info, + self.base, self.local_members, self.variants) + visitor.visit_object_type_flat(self.name, self.info, + self.members, self.variants) + + +class QAPISchemaObjectTypeMember(object): + def __init__(self, name, typ, optional): + assert isinstance(name, str) + assert isinstance(typ, str) + assert isinstance(optional, bool) + self.name = name + self._type_name = typ + self.type = None + self.optional = optional + + def check(self, schema, all_members, seen): + assert self.name not in seen + self.type = schema.lookup_type(self._type_name) + assert self.type + all_members.append(self) + seen[self.name] = self + + +class QAPISchemaObjectTypeVariants(object): + def __init__(self, tag_name, tag_enum, variants): + assert tag_name is None or isinstance(tag_name, str) + assert tag_enum is None or isinstance(tag_enum, str) + for v in variants: + assert isinstance(v, QAPISchemaObjectTypeVariant) + self.tag_name = tag_name + if tag_name: + assert not tag_enum + self.tag_member = None + else: + self.tag_member = QAPISchemaObjectTypeMember('type', tag_enum, + False) + self.variants = variants + + def check(self, schema, members, seen): + if self.tag_name: + self.tag_member = seen[self.tag_name] + else: + self.tag_member.check(schema, members, seen) + assert isinstance(self.tag_member.type, QAPISchemaEnumType) + for v in self.variants: + vseen = dict(seen) + v.check(schema, self.tag_member.type, vseen) + +class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember): + def __init__(self, name, typ): + QAPISchemaObjectTypeMember.__init__(self, name, typ, False) + + def check(self, schema, tag_type, seen): + QAPISchemaObjectTypeMember.check(self, schema, [], seen) + assert self.name in tag_type.values + + # This function exists to support ugly simple union special cases + # TODO get rid of them, and drop the function + def simple_union_type(self): + if isinstance(self.type, QAPISchemaObjectType) and not self.type.info: + assert len(self.type.members) == 1 + assert not self.type.variants + return self.type.members[0].type + return None + - for member in typeinfo: - argname = member - argentry = typeinfo[member] +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, [], {}) + + def json_type(self): + return 'value' + + def visit(self, visitor): + visitor.visit_alternate_type(self.name, self.info, self.variants) + + +class QAPISchemaCommand(QAPISchemaEntity): + def __init__(self, name, info, arg_type, ret_type, gen, success_response): + 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) + + def visit(self, visitor): + visitor.visit_command(self.name, self.info, + self.arg_type, self.ret_type, + self.gen, self.success_response) + + +class QAPISchemaEvent(QAPISchemaEntity): + def __init__(self, name, info, arg_type): + 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 + + def visit(self, visitor): + visitor.visit_event(self.name, self.info, self.arg_type) + + +class QAPISchema(object): + def __init__(self, fname): + try: + self.exprs = check_exprs(QAPISchemaParser(open(fname, "r")).exprs) + except (QAPISchemaError, QAPIExprError), err: + print >>sys.stderr, err + exit(1) + self._entity_dict = {} + self._def_predefineds() + self._def_exprs() + self.check() + + def _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, json_type, c_type, c_null): + self._def_entity(QAPISchemaBuiltinType(name, json_type, + c_type, c_null)) + self._make_array_type(name) # TODO really needed? + + def _def_predefineds(self): + for t in [('str', 'string', 'char' + pointer_suffix, 'NULL'), + ('number', 'number', 'double', '0'), + ('int', 'int', 'int64_t', '0'), + ('int8', 'int', 'int8_t', '0'), + ('int16', 'int', 'int16_t', '0'), + ('int32', 'int', 'int32_t', '0'), + ('int64', 'int', 'int64_t', '0'), + ('uint8', 'int', 'uint8_t', '0'), + ('uint16', 'int', 'uint16_t', '0'), + ('uint32', 'int', 'uint32_t', '0'), + ('uint64', 'int', 'uint64_t', '0'), + ('size', 'int', 'uint64_t', '0'), + ('bool', 'boolean', 'bool', 'false'), + ('any', 'value', 'QObject' + pointer_suffix, 'NULL')]: + self._def_builtin_type(*t) + self.the_empty_object_type = QAPISchemaObjectType(':empty', None, None, + [], None) + self._def_entity(self.the_empty_object_type) + + def _make_implicit_enum_type(self, name, values): + name = name + 'Kind' + self._def_entity(QAPISchemaEnumType(name, None, values, None)) + return name + + def _make_array_type(self, element_type): + name = element_type + 'List' + if not self.lookup_type(name): + self._def_entity(QAPISchemaArrayType(name, None, element_type)) + return name + + def _make_implicit_object_type(self, name, role, members): + if not members: + return None + name = ':obj-%s-%s' % (name, role) + if not self.lookup_entity(name, QAPISchemaObjectType): + self._def_entity(QAPISchemaObjectType(name, None, None, + members, None)) + return name + + def _def_enum_type(self, expr, info): + name = expr['enum'] + data = expr['data'] + prefix = expr.get('prefix') + self._def_entity(QAPISchemaEnumType(name, info, data, prefix)) + self._make_array_type(name) # TODO really needed? + + def _make_member(self, name, typ): optional = False - if member.startswith('*'): - argname = member[1:] + if name.startswith('*'): + name = name[1:] optional = True - # Todo: allow argentry to be OrderedDict, for providing the - # value of an optional argument. - yield (argname, argentry, optional) + if isinstance(typ, list): + assert len(typ) == 1 + typ = self._make_array_type(typ[0]) + return QAPISchemaObjectTypeMember(name, typ, optional) + + def _make_members(self, data): + return [self._make_member(key, value) + for (key, value) in data.iteritems()] + + def _def_struct_type(self, expr, info): + name = expr['struct'] + base = expr.get('base') + data = expr['data'] + self._def_entity(QAPISchemaObjectType(name, info, base, + self._make_members(data), + None)) + self._make_array_type(name) # TODO really needed? + + def _make_variant(self, case, typ): + return QAPISchemaObjectTypeVariant(case, typ) + + def _make_simple_variant(self, case, typ): + if isinstance(typ, list): + assert len(typ) == 1 + typ = self._make_array_type(typ[0]) + typ = self._make_implicit_object_type(typ, 'wrapper', + [self._make_member('data', typ)]) + return QAPISchemaObjectTypeVariant(case, typ) + + def _make_tag_enum(self, type_name, variants): + return self._make_implicit_enum_type(type_name, + [v.name for v in variants]) + + def _def_union_type(self, expr, info): + name = expr['union'] + data = expr['data'] + base = expr.get('base') + tag_name = expr.get('discriminator') + tag_enum = None + if tag_name: + variants = [self._make_variant(key, value) + for (key, value) in data.iteritems()] + else: + variants = [self._make_simple_variant(key, value) + for (key, value) in data.iteritems()] + tag_enum = self._make_tag_enum(name, variants) + self._def_entity( + QAPISchemaObjectType(name, info, base, + self._make_members(OrderedDict()), + QAPISchemaObjectTypeVariants(tag_name, + tag_enum, + variants))) + self._make_array_type(name) # TODO really needed? + + def _def_alternate_type(self, expr, info): + name = expr['alternate'] + data = expr['data'] + variants = [self._make_variant(key, value) + for (key, value) in data.iteritems()] + tag_enum = self._make_tag_enum(name, variants) + self._def_entity( + QAPISchemaAlternateType(name, info, + QAPISchemaObjectTypeVariants(None, + tag_enum, + variants))) + self._make_array_type(name) # TODO really needed? + + def _def_command(self, expr, info): + name = expr['command'] + data = expr.get('data') + rets = expr.get('returns') + gen = expr.get('gen', True) + success_response = expr.get('success-response', True) + if isinstance(data, OrderedDict): + data = self._make_implicit_object_type(name, 'arg', + self._make_members(data)) + if isinstance(rets, list): + assert len(rets) == 1 + rets = self._make_array_type(rets[0]) + self._def_entity(QAPISchemaCommand(name, info, data, rets, gen, + success_response)) + + def _def_event(self, expr, info): + name = expr['event'] + data = expr.get('data') + if isinstance(data, OrderedDict): + data = self._make_implicit_object_type(name, 'arg', + self._make_members(data)) + self._def_entity(QAPISchemaEvent(name, info, data)) + + def _def_exprs(self): + for expr_elem in self.exprs: + expr = expr_elem['expr'] + info = expr_elem['info'] + if 'enum' in expr: + self._def_enum_type(expr, info) + elif 'struct' in expr: + self._def_struct_type(expr, info) + elif 'union' in expr: + self._def_union_type(expr, info) + elif 'alternate' in expr: + self._def_alternate_type(expr, info) + elif 'command' in expr: + self._def_command(expr, info) + elif 'event' in expr: + self._def_event(expr, info) + else: + assert False + + def check(self): + for ent in self._entity_dict.values(): + ent.check(self) + + def visit(self, visitor): + ignore = visitor.visit_begin(self) + for name in sorted(self._entity_dict.keys()): + if not ignore or not isinstance(self._entity_dict[name], ignore): + self._entity_dict[name].visit(visitor) + visitor.visit_end() + + +# +# Code generation helpers +# def camel_case(name): new_name = '' @@ -864,70 +1361,9 @@ def c_name(name, protect=True): return "q_" + name return name.translate(c_name_trans) -# Map type @name to the C typedef name for the list form. -# -# ['Name'] -> 'NameList', ['x-Foo'] -> 'x_FooList', ['int'] -> 'intList' -def c_list_type(name): - return type_name(name) + 'List' - -# Map type @value to the C typedef form. -# -# Used for converting 'type' from a 'member':'type' qapi definition -# into the alphanumeric portion of the type for a generated C parameter, -# as well as generated C function names. See c_type() for the rest of -# the conversion such as adding '*' on pointer types. -# 'int' -> 'int', '[x-Foo]' -> 'x_FooList', '__a.b_c' -> '__a_b_c' -def type_name(value): - if type(value) == list: - return c_list_type(value[0]) - if value in builtin_types.keys(): - return value - return c_name(value) - eatspace = '\033EATSPACE.' pointer_suffix = ' *' + eatspace -# Map type @name to its C type expression. -# If @is_param, const-qualify the string type. -# -# This function is used for computing the full C type of 'member':'name'. -# A special suffix is added in c_type() for pointer types, and it's -# stripped in mcgen(). So please notice this when you check the return -# value of c_type() outside mcgen(). -def c_type(value, is_param=False): - if value == 'str': - if is_param: - return 'const char' + pointer_suffix - return 'char' + pointer_suffix - - elif value == 'int': - return 'int64_t' - elif (value == 'int8' or value == 'int16' or value == 'int32' or - value == 'int64' or value == 'uint8' or value == 'uint16' or - value == 'uint32' or value == 'uint64'): - return value + '_t' - elif value == 'size': - return 'uint64_t' - elif value == 'bool': - return 'bool' - elif value == 'number': - return 'double' - elif type(value) == list: - return c_list_type(value[0]) + pointer_suffix - elif is_enum(value): - return c_name(value) - elif value == None: - return 'void' - elif value in events: - return camel_case(value) + 'Event' + pointer_suffix - else: - # complex type name - assert isinstance(value, str) and value != "" - return c_name(value) + pointer_suffix - -def is_c_ptr(value): - return c_type(value).endswith(pointer_suffix) - def genindent(count): ret = "" for i in range(count): @@ -982,6 +1418,74 @@ def guardend(name): ''', name=guardname(name)) +def gen_enum_lookup(name, values, prefix=None): + ret = mcgen(''' + +const char *const %(c_name)s_lookup[] = { +''', + c_name=c_name(name)) + for value in values: + index = c_enum_const(name, value, prefix) + ret += mcgen(''' + [%(index)s] = "%(value)s", +''', + index=index, value=value) + + max_index = c_enum_const(name, 'MAX', prefix) + ret += mcgen(''' + [%(max_index)s] = NULL, +}; +''', + max_index=max_index) + return ret + +def gen_enum(name, values, prefix=None): + # append automatically generated _MAX value + enum_values = values + ['MAX'] + + ret = mcgen(''' + +typedef enum %(c_name)s { +''', + c_name=c_name(name)) + + i = 0 + for value in enum_values: + ret += mcgen(''' + %(c_enum)s = %(i)d, +''', + c_enum=c_enum_const(name, value, prefix), + i=i) + i += 1 + + ret += mcgen(''' +} %(c_name)s; +''', + c_name=c_name(name)) + + ret += mcgen(''' + +extern const char *const %(c_name)s_lookup[]; +''', + c_name=c_name(name)) + return ret + +def gen_params(arg_type, extra): + if not arg_type: + return extra + assert not arg_type.variants + ret = '' + sep = '' + for memb in arg_type.members: + ret += sep + sep = ', ' + if memb.optional: + ret += 'bool has_%s, ' % c_name(memb.name) + ret += '%s %s' % (memb.type.c_type(is_param=True), c_name(memb.name)) + if extra: + ret += sep + extra + return ret + # # Common command line parsing # |