diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2020-10-12 11:29:41 +0100 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2020-10-12 11:29:42 +0100 |
commit | 2387df497b4b4bcf754eb7398edca82889e2ef54 (patch) | |
tree | 11d3099549e5d67012a4f0818a41ede540940a36 /scripts/qapi | |
parent | 48a340d9b23ffcf7704f2de14d1e505481a84a1c (diff) | |
parent | b4c0aa59aff520e2a55edd5fef393058ca6520de (diff) |
Merge remote-tracking branch 'remotes/armbru/tags/pull-qapi-2020-10-10' into staging
QAPI patches patches for 2020-10-10
# gpg: Signature made Sat 10 Oct 2020 10:43:14 BST
# gpg: using RSA key 354BC8B3D7EB2A6B68674E5F3870B400EB918653
# gpg: issuer "armbru@redhat.com"
# gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" [full]
# gpg: aka "Markus Armbruster <armbru@pond.sub.org>" [full]
# Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867 4E5F 3870 B400 EB91 8653
* remotes/armbru/tags/pull-qapi-2020-10-10: (34 commits)
qapi/visit.py: add type hint annotations
qapi/visit.py: remove unused parameters from gen_visit_object
qapi/visit.py: assert tag_member contains a QAPISchemaEnumType
qapi/types.py: remove one-letter variables
qapi/types.py: add type hint annotations
qapi/gen.py: delint with pylint
qapi/gen.py: update write() to be more idiomatic
qapi/gen.py: Remove unused parameter
qapi/gen.py: add type hint annotations
qapi/gen: Make _is_user_module() return bool
qapi/source.py: delint with pylint
qapi/source.py: add type hint annotations
qapi/commands.py: add type hint annotations
qapi/commands.py: Don't re-bind to variable of different type
qapi/events.py: Move comments into docstrings
qapi/events.py: add type hint annotations
qapi: establish mypy type-checking baseline
qapi/common.py: move build_params into gen.py
qapi/common.py: Convert comments into docstrings, and elaborate
qapi/common.py: add type hint annotations
...
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'scripts/qapi')
-rw-r--r-- | scripts/qapi/.flake8 | 2 | ||||
-rw-r--r-- | scripts/qapi/.isort.cfg | 7 | ||||
-rw-r--r-- | scripts/qapi/commands.py | 94 | ||||
-rw-r--r-- | scripts/qapi/common.py | 164 | ||||
-rw-r--r-- | scripts/qapi/events.py | 62 | ||||
-rw-r--r-- | scripts/qapi/expr.py | 7 | ||||
-rw-r--r-- | scripts/qapi/gen.py | 182 | ||||
-rw-r--r-- | scripts/qapi/introspect.py | 16 | ||||
-rw-r--r-- | scripts/qapi/main.py | 95 | ||||
-rw-r--r-- | scripts/qapi/mypy.ini | 30 | ||||
-rw-r--r-- | scripts/qapi/parser.py | 6 | ||||
-rw-r--r-- | scripts/qapi/pylintrc | 70 | ||||
-rw-r--r-- | scripts/qapi/schema.py | 33 | ||||
-rw-r--r-- | scripts/qapi/source.py | 35 | ||||
-rw-r--r-- | scripts/qapi/types.py | 125 | ||||
-rw-r--r-- | scripts/qapi/visit.py | 122 |
16 files changed, 755 insertions, 295 deletions
diff --git a/scripts/qapi/.flake8 b/scripts/qapi/.flake8 new file mode 100644 index 0000000000..6b158c68b8 --- /dev/null +++ b/scripts/qapi/.flake8 @@ -0,0 +1,2 @@ +[flake8] +extend-ignore = E722 # Prefer pylint's bare-except checks to flake8's diff --git a/scripts/qapi/.isort.cfg b/scripts/qapi/.isort.cfg new file mode 100644 index 0000000000..643caa1fbd --- /dev/null +++ b/scripts/qapi/.isort.cfg @@ -0,0 +1,7 @@ +[settings] +force_grid_wrap=4 +force_sort_within_sections=True +include_trailing_comma=True +line_length=72 +lines_after_imports=2 +multi_line_output=3 diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py index 6e6fc94a14..50978090b4 100644 --- a/scripts/qapi/commands.py +++ b/scripts/qapi/commands.py @@ -13,11 +13,34 @@ This work is licensed under the terms of the GNU GPL, version 2. See the COPYING file in the top-level directory. """ -from qapi.common import * -from qapi.gen import QAPIGenCCode, QAPISchemaModularCVisitor, ifcontext - - -def gen_command_decl(name, arg_type, boxed, ret_type): +from typing import ( + Dict, + List, + Optional, + Set, +) + +from .common import c_name, mcgen +from .gen import ( + QAPIGenC, + QAPIGenCCode, + QAPISchemaModularCVisitor, + build_params, + ifcontext, +) +from .schema import ( + QAPISchema, + QAPISchemaFeature, + QAPISchemaObjectType, + QAPISchemaType, +) +from .source import QAPISourceInfo + + +def gen_command_decl(name: str, + arg_type: Optional[QAPISchemaObjectType], + boxed: bool, + ret_type: Optional[QAPISchemaType]) -> str: return mcgen(''' %(c_type)s qmp_%(c_name)s(%(params)s); ''', @@ -26,7 +49,10 @@ def gen_command_decl(name, arg_type, boxed, ret_type): params=build_params(arg_type, boxed, 'Error **errp')) -def gen_call(name, arg_type, boxed, ret_type): +def gen_call(name: str, + arg_type: Optional[QAPISchemaObjectType], + boxed: bool, + ret_type: Optional[QAPISchemaType]) -> str: ret = '' argstr = '' @@ -62,10 +88,11 @@ def gen_call(name, arg_type, boxed, ret_type): return ret -def gen_marshal_output(ret_type): +def gen_marshal_output(ret_type: QAPISchemaType) -> str: return mcgen(''' -static void qmp_marshal_output_%(c_name)s(%(c_type)s ret_in, QObject **ret_out, Error **errp) +static void qmp_marshal_output_%(c_name)s(%(c_type)s ret_in, + QObject **ret_out, Error **errp) { Visitor *v; @@ -82,19 +109,22 @@ static void qmp_marshal_output_%(c_name)s(%(c_type)s ret_in, QObject **ret_out, c_type=ret_type.c_type(), c_name=ret_type.c_name()) -def build_marshal_proto(name): +def build_marshal_proto(name: str) -> str: return ('void qmp_marshal_%s(QDict *args, QObject **ret, Error **errp)' % c_name(name)) -def gen_marshal_decl(name): +def gen_marshal_decl(name: str) -> str: return mcgen(''' %(proto)s; ''', proto=build_marshal_proto(name)) -def gen_marshal(name, arg_type, boxed, ret_type): +def gen_marshal(name: str, + arg_type: Optional[QAPISchemaObjectType], + boxed: bool, + ret_type: Optional[QAPISchemaType]) -> str: have_args = boxed or (arg_type and not arg_type.is_empty()) ret = mcgen(''' @@ -176,8 +206,11 @@ out: return ret -def gen_register_command(name, success_response, allow_oob, allow_preconfig, - coroutine): +def gen_register_command(name: str, + success_response: bool, + allow_oob: bool, + allow_preconfig: bool, + coroutine: bool) -> str: options = [] if not success_response: @@ -192,18 +225,16 @@ def gen_register_command(name, success_response, allow_oob, allow_preconfig, if not options: options = ['QCO_NO_OPTIONS'] - options = " | ".join(options) - ret = mcgen(''' qmp_register_command(cmds, "%(name)s", qmp_marshal_%(c_name)s, %(opts)s); ''', name=name, c_name=c_name(name), - opts=options) + opts=" | ".join(options)) return ret -def gen_registry(registry, prefix): +def gen_registry(registry: str, prefix: str) -> str: ret = mcgen(''' void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds) @@ -220,15 +251,14 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds) class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor): - - def __init__(self, prefix): + def __init__(self, prefix: str): super().__init__( prefix, 'qapi-commands', ' * Schema-defined QAPI/QMP commands', None, __doc__) self._regy = QAPIGenCCode(None) - self._visited_ret_types = {} + self._visited_ret_types: Dict[QAPIGenC, Set[QAPISchemaType]] = {} - def _begin_user_module(self, name): + def _begin_user_module(self, name: str) -> None: self._visited_ret_types[self._genc] = set() commands = self._module_basename('qapi-commands', name) types = self._module_basename('qapi-types', name) @@ -252,7 +282,7 @@ class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor): ''', types=types)) - def visit_end(self): + def visit_end(self) -> None: self._add_system_module('init', ' * QAPI Commands initialization') self._genh.add(mcgen(''' #include "qapi/qmp/dispatch.h" @@ -268,9 +298,19 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds); prefix=self._prefix)) self._genc.add(gen_registry(self._regy.get_content(), self._prefix)) - def visit_command(self, name, info, ifcond, features, - arg_type, ret_type, gen, success_response, boxed, - allow_oob, allow_preconfig, coroutine): + def visit_command(self, + name: str, + info: QAPISourceInfo, + ifcond: List[str], + features: List[QAPISchemaFeature], + arg_type: Optional[QAPISchemaObjectType], + ret_type: Optional[QAPISchemaType], + gen: bool, + success_response: bool, + boxed: bool, + allow_oob: bool, + allow_preconfig: bool, + coroutine: bool) -> None: if not gen: return # FIXME: If T is a user-defined type, the user is responsible @@ -292,7 +332,9 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds); coroutine)) -def gen_commands(schema, output_dir, prefix): +def gen_commands(schema: QAPISchema, + output_dir: str, + prefix: str) -> None: vis = QAPISchemaGenCommandVisitor(prefix) schema.visit(vis) vis.write(output_dir) diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index ba35abea47..11b86beeab 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -12,12 +12,28 @@ # See the COPYING file in the top-level directory. import re +from typing import Optional, Sequence -# ENUMName -> ENUM_NAME, EnumName1 -> ENUM_NAME1 -# ENUM_NAME -> ENUM_NAME, ENUM_NAME1 -> ENUM_NAME1, ENUM_Name2 -> ENUM_NAME2 -# ENUM24_Name -> ENUM24_NAME -def camel_to_upper(value): +#: Magic string that gets removed along with all space to its right. +EATSPACE = '\033EATSPACE.' +POINTER_SUFFIX = ' *' + EATSPACE +_C_NAME_TRANS = str.maketrans('.-', '__') + + +def camel_to_upper(value: str) -> str: + """ + Converts CamelCase to CAMEL_CASE. + + Examples:: + + ENUMName -> ENUM_NAME + EnumName1 -> ENUM_NAME1 + ENUM_NAME -> ENUM_NAME + ENUM_NAME1 -> ENUM_NAME1 + ENUM_Name2 -> ENUM_NAME2 + ENUM24_Name -> ENUM24_NAME + """ c_fun_str = c_name(value, False) if value.isupper(): return c_fun_str @@ -25,36 +41,47 @@ def camel_to_upper(value): new_name = '' length = len(c_fun_str) for i in range(length): - c = c_fun_str[i] - # When c is upper and no '_' appears before, do more checks - if c.isupper() and (i > 0) and c_fun_str[i - 1] != '_': + char = c_fun_str[i] + # When char is upper case and no '_' appears before, do more checks + if char.isupper() and (i > 0) and c_fun_str[i - 1] != '_': if i < length - 1 and c_fun_str[i + 1].islower(): new_name += '_' elif c_fun_str[i - 1].isdigit(): new_name += '_' - new_name += c + new_name += char return new_name.lstrip('_').upper() -def c_enum_const(type_name, const_name, prefix=None): +def c_enum_const(type_name: str, + const_name: str, + prefix: Optional[str] = None) -> str: + """ + Generate a C enumeration constant name. + + :param type_name: The name of the enumeration. + :param const_name: The name of this constant. + :param prefix: Optional, prefix that overrides the type_name. + """ if prefix is not None: type_name = prefix return camel_to_upper(type_name) + '_' + c_name(const_name, False).upper() -c_name_trans = str.maketrans('.-', '__') +def c_name(name: str, protect: bool = True) -> str: + """ + Map ``name`` to a valid C identifier. + Used for converting 'name' from a 'name':'type' qapi definition + into a generated struct member, as well as converting type names + into substrings of a generated C function name. -# Map @name to a valid C identifier. -# If @protect, avoid returning certain ticklish identifiers (like -# C keywords) by prepending 'q_'. -# -# Used for converting 'name' from a 'name':'type' qapi definition -# into a generated struct member, as well as converting type names -# into substrings of a generated C function name. -# '__a.b_c' -> '__a_b_c', 'x-foo' -> 'x_foo' -# protect=True: 'int' -> 'q_int'; protect=False: 'int' -> 'int' -def c_name(name, protect=True): + '__a.b_c' -> '__a_b_c', 'x-foo' -> 'x_foo' + protect=True: 'int' -> 'q_int'; protect=False: 'int' -> 'int' + + :param name: The name to map. + :param protect: If true, avoid returning certain ticklish identifiers + (like C keywords) by prepending ``q_``. + """ # ANSI X3J11/88-090, 3.1.1 c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', 'double', 'else', 'enum', 'extern', @@ -82,61 +109,75 @@ def c_name(name, protect=True): 'not_eq', 'or', 'or_eq', 'xor', 'xor_eq']) # namespace pollution: polluted_words = set(['unix', 'errno', 'mips', 'sparc', 'i386']) - name = name.translate(c_name_trans) + name = name.translate(_C_NAME_TRANS) if protect and (name in c89_words | c99_words | c11_words | gcc_words | cpp_words | polluted_words): return 'q_' + name return name -eatspace = '\033EATSPACE.' -pointer_suffix = ' *' + eatspace +class Indentation: + """ + Indentation level management. + :param initial: Initial number of spaces, default 0. + """ + def __init__(self, initial: int = 0) -> None: + self._level = initial -def genindent(count): - ret = '' - for _ in range(count): - ret += ' ' - return ret + def __int__(self) -> int: + return self._level + + def __repr__(self) -> str: + return "{}({:d})".format(type(self).__name__, self._level) + + def __str__(self) -> str: + """Return the current indentation as a string of spaces.""" + return ' ' * self._level + def __bool__(self) -> bool: + """True when there is a non-zero indentation.""" + return bool(self._level) -indent_level = 0 + def increase(self, amount: int = 4) -> None: + """Increase the indentation level by ``amount``, default 4.""" + self._level += amount + def decrease(self, amount: int = 4) -> None: + """Decrease the indentation level by ``amount``, default 4.""" + if self._level < amount: + raise ArithmeticError( + f"Can't remove {amount:d} spaces from {self!r}") + self._level -= amount -def push_indent(indent_amount=4): - global indent_level - indent_level += indent_amount +#: Global, current indent level for code generation. +indent = Indentation() -def pop_indent(indent_amount=4): - global indent_level - indent_level -= indent_amount +def cgen(code: str, **kwds: object) -> str: + """ + Generate ``code`` with ``kwds`` interpolated. -# Generate @code with @kwds interpolated. -# Obey indent_level, and strip eatspace. -def cgen(code, **kwds): + Obey `indent`, and strip `EATSPACE`. + """ raw = code % kwds - if indent_level: - indent = genindent(indent_level) - # re.subn() lacks flags support before Python 2.7, use re.compile() - raw = re.subn(re.compile(r'^(?!(#|$))', re.MULTILINE), - indent, raw) - raw = raw[0] - return re.sub(re.escape(eatspace) + r' *', '', raw) + if indent: + raw = re.sub(r'^(?!(#|$))', str(indent), raw, flags=re.MULTILINE) + return re.sub(re.escape(EATSPACE) + r' *', '', raw) -def mcgen(code, **kwds): +def mcgen(code: str, **kwds: object) -> str: if code[0] == '\n': code = code[1:] return cgen(code, **kwds) -def c_fname(filename): +def c_fname(filename: str) -> str: return re.sub(r'[^A-Za-z0-9_]', '_', filename) -def guardstart(name): +def guardstart(name: str) -> str: return mcgen(''' #ifndef %(name)s #define %(name)s @@ -145,7 +186,7 @@ def guardstart(name): name=c_fname(name).upper()) -def guardend(name): +def guardend(name: str) -> str: return mcgen(''' #endif /* %(name)s */ @@ -153,7 +194,7 @@ def guardend(name): name=c_fname(name).upper()) -def gen_if(ifcond): +def gen_if(ifcond: Sequence[str]) -> str: ret = '' for ifc in ifcond: ret += mcgen(''' @@ -162,31 +203,10 @@ def gen_if(ifcond): return ret -def gen_endif(ifcond): +def gen_endif(ifcond: Sequence[str]) -> str: ret = '' for ifc in reversed(ifcond): ret += mcgen(''' #endif /* %(cond)s */ ''', cond=ifc) return ret - - -def build_params(arg_type, boxed, extra=None): - ret = '' - sep = '' - if boxed: - assert arg_type - ret += '%s arg' % arg_type.c_param_type() - sep = ', ' - elif arg_type: - assert not arg_type.variants - 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_param_type(), - c_name(memb.name)) - if extra: - ret += sep + extra - return ret if ret else 'void' diff --git a/scripts/qapi/events.py b/scripts/qapi/events.py index b544af5a1c..599f3d1f56 100644 --- a/scripts/qapi/events.py +++ b/scripts/qapi/events.py @@ -12,19 +12,31 @@ This work is licensed under the terms of the GNU GPL, version 2. See the COPYING file in the top-level directory. """ -from qapi.common import * -from qapi.gen import QAPISchemaModularCVisitor, ifcontext -from qapi.schema import QAPISchemaEnumMember -from qapi.types import gen_enum, gen_enum_lookup - - -def build_event_send_proto(name, arg_type, boxed): +from typing import List + +from .common import c_enum_const, c_name, mcgen +from .gen import QAPISchemaModularCVisitor, build_params, ifcontext +from .schema import ( + QAPISchema, + QAPISchemaEnumMember, + QAPISchemaFeature, + QAPISchemaObjectType, +) +from .source import QAPISourceInfo +from .types import gen_enum, gen_enum_lookup + + +def build_event_send_proto(name: str, + arg_type: QAPISchemaObjectType, + boxed: bool) -> str: return 'void qapi_event_send_%(c_name)s(%(param)s)' % { 'c_name': c_name(name.lower()), 'param': build_params(arg_type, boxed)} -def gen_event_send_decl(name, arg_type, boxed): +def gen_event_send_decl(name: str, + arg_type: QAPISchemaObjectType, + boxed: bool) -> str: return mcgen(''' %(proto)s; @@ -32,8 +44,12 @@ def gen_event_send_decl(name, arg_type, boxed): proto=build_event_send_proto(name, arg_type, boxed)) -# Declare and initialize an object 'qapi' using parameters from build_params() -def gen_param_var(typ): +def gen_param_var(typ: QAPISchemaObjectType) -> str: + """ + Generate a struct variable holding the event parameters. + + Initialize it with the function arguments defined in `gen_event_send`. + """ assert not typ.variants ret = mcgen(''' %(c_name)s param = { @@ -61,7 +77,11 @@ def gen_param_var(typ): return ret -def gen_event_send(name, arg_type, boxed, event_enum_name, event_emit): +def gen_event_send(name: str, + arg_type: QAPISchemaObjectType, + boxed: bool, + event_enum_name: str, + event_emit: str) -> str: # FIXME: Our declaration of local variables (and of 'errp' in the # parameter list) can collide with exploded members of the event's # data type passed in as parameters. If this collision ever hits in @@ -137,15 +157,15 @@ def gen_event_send(name, arg_type, boxed, event_enum_name, event_emit): class QAPISchemaGenEventVisitor(QAPISchemaModularCVisitor): - def __init__(self, prefix): + def __init__(self, prefix: str): super().__init__( prefix, 'qapi-events', ' * Schema-defined QAPI/QMP events', None, __doc__) self._event_enum_name = c_name(prefix + 'QAPIEvent', protect=False) - self._event_enum_members = [] + self._event_enum_members: List[QAPISchemaEnumMember] = [] self._event_emit_name = c_name(prefix + 'qapi_event_emit') - def _begin_user_module(self, name): + def _begin_user_module(self, name: str) -> None: events = self._module_basename('qapi-events', name) types = self._module_basename('qapi-types', name) visit = self._module_basename('qapi-visit', name) @@ -168,7 +188,7 @@ class QAPISchemaGenEventVisitor(QAPISchemaModularCVisitor): ''', types=types)) - def visit_end(self): + def visit_end(self) -> None: self._add_system_module('emit', ' * QAPI Events emission') self._genc.preamble_add(mcgen(''' #include "qemu/osdep.h" @@ -189,7 +209,13 @@ void %(event_emit)s(%(event_enum)s event, QDict *qdict); event_emit=self._event_emit_name, event_enum=self._event_enum_name)) - def visit_event(self, name, info, ifcond, features, arg_type, boxed): + def visit_event(self, + name: str, + info: QAPISourceInfo, + ifcond: List[str], + features: List[QAPISchemaFeature], + arg_type: QAPISchemaObjectType, + boxed: bool) -> None: with ifcontext(ifcond, self._genh, self._genc): self._genh.add(gen_event_send_decl(name, arg_type, boxed)) self._genc.add(gen_event_send(name, arg_type, boxed, @@ -200,7 +226,9 @@ void %(event_emit)s(%(event_enum)s event, QDict *qdict); self._event_enum_members.append(QAPISchemaEnumMember(name, None)) -def gen_events(schema, output_dir, prefix): +def gen_events(schema: QAPISchema, + output_dir: str, + prefix: str) -> None: vis = QAPISchemaGenEventVisitor(prefix) schema.visit(vis) vis.write(output_dir) diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py index a15c1fb474..2fcaaa2497 100644 --- a/scripts/qapi/expr.py +++ b/scripts/qapi/expr.py @@ -14,10 +14,11 @@ # This work is licensed under the terms of the GNU GPL, version 2. # See the COPYING file in the top-level directory. -import re from collections import OrderedDict -from qapi.common import c_name -from qapi.error import QAPISemError +import re + +from .common import c_name +from .error import QAPISemError # Names must be letters, numbers, -, and _. They must start with letter, diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py index ca66c82b5b..b40f18eee3 100644 --- a/scripts/qapi/gen.py +++ b/scripts/qapi/gen.py @@ -2,7 +2,7 @@ # # QAPI code generation # -# Copyright (c) 2018-2019 Red Hat Inc. +# Copyright (c) 2015-2019 Red Hat Inc. # # Authors: # Markus Armbruster <armbru@redhat.com> @@ -11,39 +11,54 @@ # This work is licensed under the terms of the GNU GPL, version 2. # See the COPYING file in the top-level directory. - -import errno +from contextlib import contextmanager import os import re -from contextlib import contextmanager - -from qapi.common import * -from qapi.schema import QAPISchemaVisitor +from typing import ( + Dict, + Iterator, + List, + Optional, + Tuple, +) + +from .common import ( + c_fname, + c_name, + gen_endif, + gen_if, + guardend, + guardstart, + mcgen, +) +from .schema import QAPISchemaObjectType, QAPISchemaVisitor +from .source import QAPISourceInfo class QAPIGen: - - def __init__(self, fname): + def __init__(self, fname: Optional[str]): self.fname = fname self._preamble = '' self._body = '' - def preamble_add(self, text): + def preamble_add(self, text: str) -> None: self._preamble += text - def add(self, text): + def add(self, text: str) -> None: self._body += text - def get_content(self): + def get_content(self) -> str: return self._top() + self._preamble + self._body + self._bottom() - def _top(self): + def _top(self) -> str: + # pylint: disable=no-self-use return '' - def _bottom(self): + def _bottom(self) -> str: + # pylint: disable=no-self-use return '' - def write(self, output_dir): + def write(self, output_dir: str) -> None: # Include paths starting with ../ are used to reuse modules of the main # schema in specialised schemas. Don't overwrite the files that are # already generated for the main schema. @@ -51,24 +66,22 @@ class QAPIGen: return pathname = os.path.join(output_dir, self.fname) odir = os.path.dirname(pathname) + if odir: - try: - os.makedirs(odir) - except os.error as e: - if e.errno != errno.EEXIST: - raise + os.makedirs(odir, exist_ok=True) + + # use os.open for O_CREAT to create and read a non-existant file fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666) - f = open(fd, 'r+', encoding='utf-8') - text = self.get_content() - oldtext = f.read(len(text) + 1) - if text != oldtext: - f.seek(0) - f.truncate(0) - f.write(text) - f.close() + with os.fdopen(fd, 'r+', encoding='utf-8') as fp: + text = self.get_content() + oldtext = fp.read(len(text) + 1) + if text != oldtext: + fp.seek(0) + fp.truncate(0) + fp.write(text) -def _wrap_ifcond(ifcond, before, after): +def _wrap_ifcond(ifcond: List[str], before: str, after: str) -> str: if before == after: return after # suppress empty #if ... #endif @@ -84,41 +97,62 @@ def _wrap_ifcond(ifcond, before, after): return out -class QAPIGenCCode(QAPIGen): +def build_params(arg_type: Optional[QAPISchemaObjectType], + boxed: bool, + extra: Optional[str] = None) -> str: + ret = '' + sep = '' + if boxed: + assert arg_type + ret += '%s arg' % arg_type.c_param_type() + sep = ', ' + elif arg_type: + assert not arg_type.variants + 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_param_type(), + c_name(memb.name)) + if extra: + ret += sep + extra + return ret if ret else 'void' - def __init__(self, fname): + +class QAPIGenCCode(QAPIGen): + def __init__(self, fname: Optional[str]): super().__init__(fname) - self._start_if = None + self._start_if: Optional[Tuple[List[str], str, str]] = None - def start_if(self, ifcond): + def start_if(self, ifcond: List[str]) -> None: assert self._start_if is None self._start_if = (ifcond, self._body, self._preamble) - def end_if(self): + def end_if(self) -> None: assert self._start_if self._wrap_ifcond() self._start_if = None - def _wrap_ifcond(self): + def _wrap_ifcond(self) -> None: self._body = _wrap_ifcond(self._start_if[0], self._start_if[1], self._body) self._preamble = _wrap_ifcond(self._start_if[0], self._start_if[2], self._preamble) - def get_content(self): + def get_content(self) -> str: assert self._start_if is None return super().get_content() class QAPIGenC(QAPIGenCCode): - - def __init__(self, fname, blurb, pydoc): + def __init__(self, fname: str, blurb: str, pydoc: str): super().__init__(fname) self._blurb = blurb self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc, re.MULTILINE)) - def _top(self): + def _top(self) -> str: return mcgen(''' /* AUTOMATICALLY GENERATED, DO NOT MODIFY */ @@ -134,7 +168,7 @@ class QAPIGenC(QAPIGenCCode): ''', blurb=self._blurb, copyright=self._copyright) - def _bottom(self): + def _bottom(self) -> str: return mcgen(''' /* Dummy declaration to prevent empty .o file */ @@ -144,19 +178,20 @@ char qapi_dummy_%(name)s; class QAPIGenH(QAPIGenC): - - def _top(self): + def _top(self) -> str: return super()._top() + guardstart(self.fname) - def _bottom(self): + def _bottom(self) -> str: return guardend(self.fname) @contextmanager -def ifcontext(ifcond, *args): - """A 'with' statement context manager to wrap with start_if()/end_if() +def ifcontext(ifcond: List[str], *args: QAPIGenCCode) -> Iterator[None]: + """ + A with-statement context manager that wraps with `start_if()` / `end_if()`. - *args: any number of QAPIGenCCode + :param ifcond: A list of conditionals, passed to `start_if()`. + :param args: any number of `QAPIGenCCode`. Example:: @@ -179,8 +214,11 @@ def ifcontext(ifcond, *args): class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor): - - def __init__(self, prefix, what, blurb, pydoc): + def __init__(self, + prefix: str, + what: str, + blurb: str, + pydoc: str): self._prefix = prefix self._what = what self._genc = QAPIGenC(self._prefix + self._what + '.c', @@ -188,38 +226,42 @@ class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor): self._genh = QAPIGenH(self._prefix + self._what + '.h', blurb, pydoc) - def write(self, output_dir): + def write(self, output_dir: str) -> None: self._genc.write(output_dir) self._genh.write(output_dir) class QAPISchemaModularCVisitor(QAPISchemaVisitor): - - def __init__(self, prefix, what, user_blurb, builtin_blurb, pydoc): + def __init__(self, + prefix: str, + what: str, + user_blurb: str, + builtin_blurb: Optional[str], + pydoc: str): self._prefix = prefix self._what = what self._user_blurb = user_blurb self._builtin_blurb = builtin_blurb self._pydoc = pydoc - self._genc = None - self._genh = None - self._module = {} - self._main_module = None + self._genc: Optional[QAPIGenC] = None + self._genh: Optional[QAPIGenH] = None + self._module: Dict[Optional[str], Tuple[QAPIGenC, QAPIGenH]] = {} + self._main_module: Optional[str] = None @staticmethod - def _is_user_module(name): - return name and not name.startswith('./') + def _is_user_module(name: Optional[str]) -> bool: + return bool(name and not name.startswith('./')) @staticmethod - def _is_builtin_module(name): + def _is_builtin_module(name: Optional[str]) -> bool: return not name - def _module_dirname(self, what, name): + def _module_dirname(self, name: Optional[str]) -> str: if self._is_user_module(name): return os.path.dirname(name) return '' - def _module_basename(self, what, name): + def _module_basename(self, what: str, name: Optional[str]) -> str: ret = '' if self._is_builtin_module(name) else self._prefix if self._is_user_module(name): basename = os.path.basename(name) @@ -231,27 +273,27 @@ class QAPISchemaModularCVisitor(QAPISchemaVisitor): ret += re.sub(r'-', '-' + name + '-', what) return ret - def _module_filename(self, what, name): - return os.path.join(self._module_dirname(what, name), + def _module_filename(self, what: str, name: Optional[str]) -> str: + return os.path.join(self._module_dirname(name), self._module_basename(what, name)) - def _add_module(self, name, blurb): + def _add_module(self, name: Optional[str], blurb: str) -> None: basename = self._module_filename(self._what, name) genc = QAPIGenC(basename + '.c', blurb, self._pydoc) genh = QAPIGenH(basename + '.h', blurb, self._pydoc) self._module[name] = (genc, genh) self._genc, self._genh = self._module[name] - def _add_user_module(self, name, blurb): + def _add_user_module(self, name: str, blurb: str) -> None: assert self._is_user_module(name) if self._main_module is None: self._main_module = name self._add_module(name, blurb) - def _add_system_module(self, name, blurb): + def _add_system_module(self, name: Optional[str], blurb: str) -> None: self._add_module(name and './' + name, blurb) - def write(self, output_dir, opt_builtins=False): + def write(self, output_dir: str, opt_builtins: bool = False) -> None: for name in self._module: if self._is_builtin_module(name) and not opt_builtins: continue @@ -259,13 +301,13 @@ class QAPISchemaModularCVisitor(QAPISchemaVisitor): genc.write(output_dir) genh.write(output_dir) - def _begin_system_module(self, name): + def _begin_system_module(self, name: None) -> None: pass - def _begin_user_module(self, name): + def _begin_user_module(self, name: str) -> None: pass - def visit_module(self, name): + def visit_module(self, name: Optional[str]) -> None: if name is None: if self._builtin_blurb: self._add_system_module(None, self._builtin_blurb) @@ -279,7 +321,7 @@ class QAPISchemaModularCVisitor(QAPISchemaVisitor): self._add_user_module(name, self._user_blurb) self._begin_user_module(name) - def visit_include(self, name, info): + def visit_include(self, name: str, info: QAPISourceInfo) -> None: relname = os.path.relpath(self._module_filename(self._what, name), os.path.dirname(self._genh.fname)) self._genh.preamble_add(mcgen(''' diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 5907b09cd5..fafec94e02 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -10,10 +10,18 @@ This work is licensed under the terms of the GNU GPL, version 2. See the COPYING file in the top-level directory. """ -from qapi.common import * -from qapi.gen import QAPISchemaMonolithicCVisitor -from qapi.schema import (QAPISchemaArrayType, QAPISchemaBuiltinType, - QAPISchemaType) +from .common import ( + c_name, + gen_endif, + gen_if, + mcgen, +) +from .gen import QAPISchemaMonolithicCVisitor +from .schema import ( + QAPISchemaArrayType, + QAPISchemaBuiltinType, + QAPISchemaType, +) def _make_tree(obj, ifcond, features, extra=None): diff --git a/scripts/qapi/main.py b/scripts/qapi/main.py new file mode 100644 index 0000000000..42517210b8 --- /dev/null +++ b/scripts/qapi/main.py @@ -0,0 +1,95 @@ +# This work is licensed under the terms of the GNU GPL, version 2 or later. +# See the COPYING file in the top-level directory. + +""" +QAPI Generator + +This is the main entry point for generating C code from the QAPI schema. +""" + +import argparse +import re +import sys +from typing import Optional + +from .commands import gen_commands +from .error import QAPIError +from .events import gen_events +from .introspect import gen_introspect +from .schema import QAPISchema +from .types import gen_types +from .visit import gen_visit + + +def invalid_prefix_char(prefix: str) -> Optional[str]: + match = re.match(r'([A-Za-z_.-][A-Za-z0-9_.-]*)?', prefix) + if match.end() != len(prefix): + return prefix[match.end()] + return None + + +def generate(schema_file: str, + output_dir: str, + prefix: str, + unmask: bool = False, + builtins: bool = False) -> None: + """ + Generate C code for the given schema into the target directory. + + :param schema_file: The primary QAPI schema file. + :param output_dir: The output directory to store generated code. + :param prefix: Optional C-code prefix for symbol names. + :param unmask: Expose non-ABI names through introspection? + :param builtins: Generate code for built-in types? + + :raise QAPIError: On failures. + """ + assert invalid_prefix_char(prefix) is None + + schema = QAPISchema(schema_file) + gen_types(schema, output_dir, prefix, builtins) + gen_visit(schema, output_dir, prefix, builtins) + gen_commands(schema, output_dir, prefix) + gen_events(schema, output_dir, prefix) + gen_introspect(schema, output_dir, prefix, unmask) + + +def main() -> int: + """ + gapi-gen executable entry point. + Expects arguments via sys.argv, see --help for details. + + :return: int, 0 on success, 1 on failure. + """ + parser = argparse.ArgumentParser( + description='Generate code from a QAPI schema') + parser.add_argument('-b', '--builtins', action='store_true', + help="generate code for built-in types") + parser.add_argument('-o', '--output-dir', action='store', + default='', + help="write output to directory OUTPUT_DIR") + parser.add_argument('-p', '--prefix', action='store', + default='', + help="prefix for symbols") + parser.add_argument('-u', '--unmask-non-abi-names', action='store_true', + dest='unmask', + help="expose non-ABI names in introspection") + parser.add_argument('schema', action='store') + args = parser.parse_args() + + funny_char = invalid_prefix_char(args.prefix) + if funny_char: + msg = f"funny character '{funny_char}' in argument of --prefix" + print(f"{sys.argv[0]}: {msg}", file=sys.stderr) + return 1 + + try: + generate(args.schema, + output_dir=args.output_dir, + prefix=args.prefix, + unmask=args.unmask, + builtins=args.builtins) + except QAPIError as err: + print(f"{sys.argv[0]}: {str(err)}", file=sys.stderr) + return 1 + return 0 diff --git a/scripts/qapi/mypy.ini b/scripts/qapi/mypy.ini new file mode 100644 index 0000000000..74fc6c8215 --- /dev/null +++ b/scripts/qapi/mypy.ini @@ -0,0 +1,30 @@ +[mypy] +strict = True +strict_optional = False +disallow_untyped_calls = False +python_version = 3.6 + +[mypy-qapi.error] +disallow_untyped_defs = False +disallow_incomplete_defs = False +check_untyped_defs = False + +[mypy-qapi.expr] +disallow_untyped_defs = False +disallow_incomplete_defs = False +check_untyped_defs = False + +[mypy-qapi.introspect] +disallow_untyped_defs = False +disallow_incomplete_defs = False +check_untyped_defs = False + +[mypy-qapi.parser] +disallow_untyped_defs = False +disallow_incomplete_defs = False +check_untyped_defs = False + +[mypy-qapi.schema] +disallow_untyped_defs = False +disallow_incomplete_defs = False +check_untyped_defs = False diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py index 9d1a3e2eea..e7b9d670ad 100644 --- a/scripts/qapi/parser.py +++ b/scripts/qapi/parser.py @@ -14,12 +14,12 @@ # This work is licensed under the terms of the GNU GPL, version 2. # See the COPYING file in the top-level directory. +from collections import OrderedDict import os import re -from collections import OrderedDict -from qapi.error import QAPIParseError, QAPISemError -from qapi.source import QAPISourceInfo +from .error import QAPIParseError, QAPISemError +from .source import QAPISourceInfo class QAPISchemaParser: diff --git a/scripts/qapi/pylintrc b/scripts/qapi/pylintrc new file mode 100644 index 0000000000..b9e077a164 --- /dev/null +++ b/scripts/qapi/pylintrc @@ -0,0 +1,70 @@ +[MASTER] + +# Add files or directories matching the regex patterns to the ignore list. +# The regex matches against base names, not paths. +ignore-patterns=error.py, + expr.py, + parser.py, + schema.py, + + +[MESSAGES CONTROL] + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=fixme, + missing-docstring, + too-many-arguments, + too-many-branches, + too-many-statements, + too-many-instance-attributes, + +[REPORTS] + +[REFACTORING] + +[MISCELLANEOUS] + +[LOGGING] + +[BASIC] + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _, + fp, # fp = open(...) + fd, # fd = os.open(...) + +[VARIABLES] + +[STRING] + +[SPELLING] + +[FORMAT] + +[SIMILARITIES] + +# Ignore import statements themselves when computing similarities. +ignore-imports=yes + +[TYPECHECK] + +[CLASSES] + +[IMPORTS] + +[DESIGN] + +[EXCEPTIONS] diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py index d1307ec661..720449feee 100644 --- a/scripts/qapi/schema.py +++ b/scripts/qapi/schema.py @@ -14,18 +14,19 @@ # TODO catching name collisions in generated code would be nice +from collections import OrderedDict import os import re -from collections import OrderedDict +from typing import Optional -from qapi.common import c_name, pointer_suffix -from qapi.error import QAPIError, QAPISemError -from qapi.expr import check_exprs -from qapi.parser import QAPISchemaParser +from .common import POINTER_SUFFIX, c_name +from .error import QAPIError, QAPISemError +from .expr import check_exprs +from .parser import QAPISchemaParser class QAPISchemaEntity: - meta = None + meta: Optional[str] = None def __init__(self, name, info, doc, ifcond=None, features=None): assert name is None or isinstance(name, str) @@ -309,7 +310,7 @@ class QAPISchemaArrayType(QAPISchemaType): return True def c_type(self): - return c_name(self.name) + pointer_suffix + return c_name(self.name) + POINTER_SUFFIX def json_type(self): return 'array' @@ -430,7 +431,7 @@ class QAPISchemaObjectType(QAPISchemaType): def c_type(self): assert not self.is_implicit() - return c_name(self.name) + pointer_suffix + return c_name(self.name) + POINTER_SUFFIX def c_unboxed_type(self): return c_name(self.name) @@ -504,7 +505,7 @@ class QAPISchemaAlternateType(QAPISchemaType): v.connect_doc(doc) def c_type(self): - return c_name(self.name) + pointer_suffix + return c_name(self.name) + POINTER_SUFFIX def json_type(self): return 'value' @@ -536,7 +537,7 @@ class QAPISchemaVariants: v.set_defined_in(name) def check(self, schema, seen): - if not self.tag_member: # flat union + if not self.tag_member: # flat union self.tag_member = seen.get(c_name(self._tag_name)) base = "'base'" # Pointing to the base type when not implicit would be @@ -824,7 +825,7 @@ class QAPISchema: self._entity_dict = {} self._module_dict = OrderedDict() self._schema_dir = os.path.dirname(fname) - self._make_module(None) # built-ins + self._make_module(None) # built-ins self._make_module(fname) self._predefining = True self._def_predefineds() @@ -899,7 +900,7 @@ class QAPISchema: self._make_array_type(name, None) def _def_predefineds(self): - for t in [('str', 'string', 'char' + pointer_suffix), + for t in [('str', 'string', 'char' + POINTER_SUFFIX), ('number', 'number', 'double'), ('int', 'int', 'int64_t'), ('int8', 'int', 'int8_t'), @@ -912,8 +913,8 @@ class QAPISchema: ('uint64', 'int', 'uint64_t'), ('size', 'int', 'uint64_t'), ('bool', 'boolean', 'bool'), - ('any', 'value', 'QObject' + pointer_suffix), - ('null', 'null', 'QNull' + pointer_suffix)]: + ('any', 'value', 'QObject' + POINTER_SUFFIX), + ('null', 'null', 'QNull' + POINTER_SUFFIX)]: self._def_builtin_type(*t) self.the_empty_object_type = QAPISchemaObjectType( 'q_empty', None, None, None, None, None, [], None) @@ -968,7 +969,9 @@ class QAPISchema: # But it's not tight: the disjunction need not imply it. We # may end up compiling useless wrapper types. # TODO kill simple unions or implement the disjunction - assert (ifcond or []) == typ._ifcond # pylint: disable=protected-access + + # pylint: disable=protected-access + assert (ifcond or []) == typ._ifcond else: self._def_entity(QAPISchemaObjectType( name, info, None, ifcond, None, None, members, None)) diff --git a/scripts/qapi/source.py b/scripts/qapi/source.py index e97b9a8e15..d7a79a9b8a 100644 --- a/scripts/qapi/source.py +++ b/scripts/qapi/source.py @@ -11,37 +11,46 @@ import copy import sys +from typing import List, Optional, TypeVar class QAPISchemaPragma: - def __init__(self): + # Replace with @dataclass in Python 3.7+ + # pylint: disable=too-few-public-methods + + def __init__(self) -> None: # Are documentation comments required? self.doc_required = False # Whitelist of commands allowed to return a non-dictionary - self.returns_whitelist = [] + self.returns_whitelist: List[str] = [] # Whitelist of entities allowed to violate case conventions - self.name_case_whitelist = [] + self.name_case_whitelist: List[str] = [] class QAPISourceInfo: - def __init__(self, fname, line, parent): + T = TypeVar('T', bound='QAPISourceInfo') + + def __init__(self, fname: str, line: int, + parent: Optional['QAPISourceInfo']): self.fname = fname self.line = line self.parent = parent - self.pragma = parent.pragma if parent else QAPISchemaPragma() - self.defn_meta = None - self.defn_name = None + self.pragma: QAPISchemaPragma = ( + parent.pragma if parent else QAPISchemaPragma() + ) + self.defn_meta: Optional[str] = None + self.defn_name: Optional[str] = None - def set_defn(self, meta, name): + def set_defn(self, meta: str, name: str) -> None: self.defn_meta = meta self.defn_name = name - def next_line(self): + def next_line(self: T) -> T: info = copy.copy(self) info.line += 1 return info - def loc(self): + def loc(self) -> str: if self.fname is None: return sys.argv[0] ret = self.fname @@ -49,13 +58,13 @@ class QAPISourceInfo: ret += ':%d' % self.line return ret - def in_defn(self): + def in_defn(self) -> str: if self.defn_name: return "%s: In %s '%s':\n" % (self.fname, self.defn_meta, self.defn_name) return '' - def include_path(self): + def include_path(self) -> str: ret = '' parent = self.parent while parent: @@ -63,5 +72,5 @@ class QAPISourceInfo: parent = parent.parent return ret - def __str__(self): + def __str__(self) -> str: return self.include_path() + self.in_defn() + self.loc() diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py index 3640f17cd6..2b4916cdaa 100644 --- a/scripts/qapi/types.py +++ b/scripts/qapi/types.py @@ -13,9 +13,26 @@ This work is licensed under the terms of the GNU GPL, version 2. # See the COPYING file in the top-level directory. """ -from qapi.common import * -from qapi.gen import QAPISchemaModularCVisitor, ifcontext -from qapi.schema import QAPISchemaEnumMember, QAPISchemaObjectType +from typing import List, Optional + +from .common import ( + c_enum_const, + c_name, + gen_endif, + gen_if, + mcgen, +) +from .gen import QAPISchemaModularCVisitor, ifcontext +from .schema import ( + QAPISchema, + QAPISchemaEnumMember, + QAPISchemaFeature, + QAPISchemaObjectType, + QAPISchemaObjectTypeMember, + QAPISchemaType, + QAPISchemaVariants, +) +from .source import QAPISourceInfo # variants must be emitted before their container; track what has already @@ -23,21 +40,23 @@ from qapi.schema import QAPISchemaEnumMember, QAPISchemaObjectType objects_seen = set() -def gen_enum_lookup(name, members, prefix=None): +def gen_enum_lookup(name: str, + members: List[QAPISchemaEnumMember], + prefix: Optional[str] = None) -> str: ret = mcgen(''' const QEnumLookup %(c_name)s_lookup = { .array = (const char *const[]) { ''', c_name=c_name(name)) - for m in members: - ret += gen_if(m.ifcond) - index = c_enum_const(name, m.name, prefix) + for memb in members: + ret += gen_if(memb.ifcond) + index = c_enum_const(name, memb.name, prefix) ret += mcgen(''' [%(index)s] = "%(name)s", ''', - index=index, name=m.name) - ret += gen_endif(m.ifcond) + index=index, name=memb.name) + ret += gen_endif(memb.ifcond) ret += mcgen(''' }, @@ -48,7 +67,9 @@ const QEnumLookup %(c_name)s_lookup = { return ret -def gen_enum(name, members, prefix=None): +def gen_enum(name: str, + members: List[QAPISchemaEnumMember], + prefix: Optional[str] = None) -> str: # append automatically generated _MAX value enum_members = members + [QAPISchemaEnumMember('_MAX', None)] @@ -58,13 +79,13 @@ typedef enum %(c_name)s { ''', c_name=c_name(name)) - for m in enum_members: - ret += gen_if(m.ifcond) + for memb in enum_members: + ret += gen_if(memb.ifcond) ret += mcgen(''' %(c_enum)s, ''', - c_enum=c_enum_const(name, m.name, prefix)) - ret += gen_endif(m.ifcond) + c_enum=c_enum_const(name, memb.name, prefix)) + ret += gen_endif(memb.ifcond) ret += mcgen(''' } %(c_name)s; @@ -82,7 +103,7 @@ extern const QEnumLookup %(c_name)s_lookup; return ret -def gen_fwd_object_or_array(name): +def gen_fwd_object_or_array(name: str) -> str: return mcgen(''' typedef struct %(c_name)s %(c_name)s; @@ -90,7 +111,7 @@ typedef struct %(c_name)s %(c_name)s; c_name=c_name(name)) -def gen_array(name, element_type): +def gen_array(name: str, element_type: QAPISchemaType) -> str: return mcgen(''' struct %(c_name)s { @@ -101,7 +122,7 @@ struct %(c_name)s { c_name=c_name(name), c_type=element_type.c_type()) -def gen_struct_members(members): +def gen_struct_members(members: List[QAPISchemaObjectTypeMember]) -> str: ret = '' for memb in members: ret += gen_if(memb.ifcond) @@ -118,17 +139,21 @@ def gen_struct_members(members): return ret -def gen_object(name, ifcond, base, members, variants): +def gen_object(name: str, ifcond: List[str], + base: Optional[QAPISchemaObjectType], + members: List[QAPISchemaObjectTypeMember], + variants: Optional[QAPISchemaVariants]) -> str: if name in objects_seen: return '' objects_seen.add(name) ret = '' - if variants: - for v in variants.variants: - if isinstance(v.type, QAPISchemaObjectType): - ret += gen_object(v.type.name, v.type.ifcond, v.type.base, - v.type.local_members, v.type.variants) + for var in variants.variants if variants else (): + obj = var.type + if not isinstance(obj, QAPISchemaObjectType): + continue + ret += gen_object(obj.name, obj.ifcond, obj.base, + obj.local_members, obj.variants) ret += mcgen(''' @@ -172,7 +197,7 @@ struct %(c_name)s { return ret -def gen_upcast(name, base): +def gen_upcast(name: str, base: QAPISchemaObjectType) -> str: # C makes const-correctness ugly. We have to cast away const to let # this function work for both const and non-const obj. return mcgen(''' @@ -185,7 +210,7 @@ static inline %(base)s *qapi_%(c_name)s_base(const %(c_name)s *obj) c_name=c_name(name), base=base.c_name()) -def gen_variants(variants): +def gen_variants(variants: QAPISchemaVariants) -> str: ret = mcgen(''' union { /* union tag is @%(c_name)s */ ''', @@ -209,7 +234,7 @@ def gen_variants(variants): return ret -def gen_type_cleanup_decl(name): +def gen_type_cleanup_decl(name: str) -> str: ret = mcgen(''' void qapi_free_%(c_name)s(%(c_name)s *obj); @@ -219,7 +244,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(%(c_name)s, qapi_free_%(c_name)s) return ret -def gen_type_cleanup(name): +def gen_type_cleanup(name: str) -> str: ret = mcgen(''' void qapi_free_%(c_name)s(%(c_name)s *obj) @@ -241,12 +266,12 @@ void qapi_free_%(c_name)s(%(c_name)s *obj) class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor): - def __init__(self, prefix): + def __init__(self, prefix: str): super().__init__( prefix, 'qapi-types', ' * Schema-defined QAPI types', ' * Built-in QAPI types', __doc__) - def _begin_system_module(self, name): + def _begin_system_module(self, name: None) -> None: self._genc.preamble_add(mcgen(''' #include "qemu/osdep.h" #include "qapi/dealloc-visitor.h" @@ -257,7 +282,7 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor): #include "qapi/util.h" ''')) - def _begin_user_module(self, name): + def _begin_user_module(self, name: str) -> None: types = self._module_basename('qapi-types', name) visit = self._module_basename('qapi-visit', name) self._genc.preamble_add(mcgen(''' @@ -271,27 +296,43 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor): #include "qapi/qapi-builtin-types.h" ''')) - def visit_begin(self, schema): + def visit_begin(self, schema: QAPISchema) -> None: # gen_object() is recursive, ensure it doesn't visit the empty type objects_seen.add(schema.the_empty_object_type.name) - def _gen_type_cleanup(self, name): + def _gen_type_cleanup(self, name: str) -> None: self._genh.add(gen_type_cleanup_decl(name)) self._genc.add(gen_type_cleanup(name)) - def visit_enum_type(self, name, info, ifcond, features, members, prefix): + def visit_enum_type(self, + name: str, + info: Optional[QAPISourceInfo], + ifcond: List[str], + features: List[QAPISchemaFeature], + members: List[QAPISchemaEnumMember], + prefix: Optional[str]) -> None: with ifcontext(ifcond, self._genh, self._genc): self._genh.preamble_add(gen_enum(name, members, prefix)) self._genc.add(gen_enum_lookup(name, members, prefix)) - def visit_array_type(self, name, info, ifcond, element_type): + def visit_array_type(self, + name: str, + info: Optional[QAPISourceInfo], + ifcond: List[str], + element_type: QAPISchemaType) -> None: with ifcontext(ifcond, self._genh, self._genc): self._genh.preamble_add(gen_fwd_object_or_array(name)) self._genh.add(gen_array(name, element_type)) self._gen_type_cleanup(name) - def visit_object_type(self, name, info, ifcond, features, - base, members, variants): + def visit_object_type(self, + name: str, + info: Optional[QAPISourceInfo], + ifcond: List[str], + features: List[QAPISchemaFeature], + base: Optional[QAPISchemaObjectType], + members: List[QAPISchemaObjectTypeMember], + variants: Optional[QAPISchemaVariants]) -> None: # Nothing to do for the special empty builtin if name == 'q_empty': return @@ -307,7 +348,12 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor): # implicit types won't be directly allocated/freed self._gen_type_cleanup(name) - def visit_alternate_type(self, name, info, ifcond, features, variants): + def visit_alternate_type(self, + name: str, + info: QAPISourceInfo, + ifcond: List[str], + features: List[QAPISchemaFeature], + variants: QAPISchemaVariants) -> None: with ifcontext(ifcond, self._genh): self._genh.preamble_add(gen_fwd_object_or_array(name)) self._genh.add(gen_object(name, ifcond, None, @@ -316,7 +362,10 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor): self._gen_type_cleanup(name) -def gen_types(schema, output_dir, prefix, opt_builtins): +def gen_types(schema: QAPISchema, + output_dir: str, + prefix: str, + opt_builtins: bool) -> None: vis = QAPISchemaGenTypeVisitor(prefix) schema.visit(vis) vis.write(output_dir, opt_builtins) diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py index cdabc5fa28..339f152152 100644 --- a/scripts/qapi/visit.py +++ b/scripts/qapi/visit.py @@ -13,22 +13,43 @@ This work is licensed under the terms of the GNU GPL, version 2. See the COPYING file in the top-level directory. """ -from qapi.common import * -from qapi.gen import QAPISchemaModularCVisitor, ifcontext -from qapi.schema import QAPISchemaObjectType - - -def gen_visit_decl(name, scalar=False): +from typing import List, Optional + +from .common import ( + c_enum_const, + c_name, + gen_endif, + gen_if, + indent, + mcgen, +) +from .gen import QAPISchemaModularCVisitor, ifcontext +from .schema import ( + QAPISchema, + QAPISchemaEnumMember, + QAPISchemaEnumType, + QAPISchemaFeature, + QAPISchemaObjectType, + QAPISchemaObjectTypeMember, + QAPISchemaType, + QAPISchemaVariants, +) +from .source import QAPISourceInfo + + +def gen_visit_decl(name: str, scalar: bool = False) -> str: c_type = c_name(name) + ' *' if not scalar: c_type += '*' return mcgen(''' -bool visit_type_%(c_name)s(Visitor *v, const char *name, %(c_type)sobj, Error **errp); + +bool visit_type_%(c_name)s(Visitor *v, const char *name, + %(c_type)sobj, Error **errp); ''', c_name=c_name(name), c_type=c_type) -def gen_visit_members_decl(name): +def gen_visit_members_decl(name: str) -> str: return mcgen(''' bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp); @@ -36,7 +57,10 @@ bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp); c_name=c_name(name)) -def gen_visit_object_members(name, base, members, variants): +def gen_visit_object_members(name: str, + base: Optional[QAPISchemaObjectType], + members: List[QAPISchemaObjectTypeMember], + variants: Optional[QAPISchemaVariants]) -> str: ret = mcgen(''' bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp) @@ -59,7 +83,7 @@ bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp) if (visit_optional(v, "%(name)s", &obj->has_%(c_name)s)) { ''', name=memb.name, c_name=c_name(memb.name)) - push_indent() + indent.increase() ret += mcgen(''' if (!visit_type_%(c_type)s(v, "%(name)s", &obj->%(c_name)s, errp)) { return false; @@ -68,22 +92,24 @@ bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp) c_type=memb.type.c_name(), name=memb.name, c_name=c_name(memb.name)) if memb.optional: - pop_indent() + indent.decrease() ret += mcgen(''' } ''') ret += gen_endif(memb.ifcond) if variants: + tag_member = variants.tag_member + assert isinstance(tag_member.type, QAPISchemaEnumType) + ret += mcgen(''' switch (obj->%(c_name)s) { ''', - c_name=c_name(variants.tag_member.name)) + c_name=c_name(tag_member.name)) for var in variants.variants: - case_str = c_enum_const(variants.tag_member.type.name, - var.name, - variants.tag_member.type.prefix) + case_str = c_enum_const(tag_member.type.name, var.name, + tag_member.type.prefix) ret += gen_if(var.ifcond) if var.type.name == 'q_empty': # valid variant and nothing to do @@ -114,10 +140,11 @@ bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp) return ret -def gen_visit_list(name, element_type): +def gen_visit_list(name: str, element_type: QAPISchemaType) -> str: return mcgen(''' -bool visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error **errp) +bool visit_type_%(c_name)s(Visitor *v, const char *name, + %(c_name)s **obj, Error **errp) { bool ok = false; %(c_name)s *tail; @@ -147,10 +174,11 @@ out_obj: c_name=c_name(name), c_elt_type=element_type.c_name()) -def gen_visit_enum(name): +def gen_visit_enum(name: str) -> str: return mcgen(''' -bool visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s *obj, Error **errp) +bool visit_type_%(c_name)s(Visitor *v, const char *name, + %(c_name)s *obj, Error **errp) { int value = *obj; bool ok = visit_type_enum(v, name, &value, &%(c_name)s_lookup, errp); @@ -161,10 +189,11 @@ bool visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s *obj, Error c_name=c_name(name)) -def gen_visit_alternate(name, variants): +def gen_visit_alternate(name: str, variants: QAPISchemaVariants) -> str: ret = mcgen(''' -bool visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error **errp) +bool visit_type_%(c_name)s(Visitor *v, const char *name, + %(c_name)s **obj, Error **errp) { bool ok = false; @@ -236,10 +265,11 @@ out_obj: return ret -def gen_visit_object(name, base, members, variants): +def gen_visit_object(name: str) -> str: return mcgen(''' -bool visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error **errp) +bool visit_type_%(c_name)s(Visitor *v, const char *name, + %(c_name)s **obj, Error **errp) { bool ok = false; @@ -270,12 +300,12 @@ out_obj: class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor): - def __init__(self, prefix): + def __init__(self, prefix: str): super().__init__( prefix, 'qapi-visit', ' * Schema-defined QAPI visitors', ' * Built-in QAPI visitors', __doc__) - def _begin_system_module(self, name): + def _begin_system_module(self, name: None) -> None: self._genc.preamble_add(mcgen(''' #include "qemu/osdep.h" #include "qapi/error.h" @@ -287,7 +317,7 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor): ''')) - def _begin_user_module(self, name): + def _begin_user_module(self, name: str) -> None: types = self._module_basename('qapi-types', name) visit = self._module_basename('qapi-visit', name) self._genc.preamble_add(mcgen(''' @@ -304,18 +334,34 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor): ''', types=types)) - def visit_enum_type(self, name, info, ifcond, features, members, prefix): + def visit_enum_type(self, + name: str, + info: QAPISourceInfo, + ifcond: List[str], + features: List[QAPISchemaFeature], + members: List[QAPISchemaEnumMember], + prefix: Optional[str]) -> None: with ifcontext(ifcond, self._genh, self._genc): self._genh.add(gen_visit_decl(name, scalar=True)) self._genc.add(gen_visit_enum(name)) - def visit_array_type(self, name, info, ifcond, element_type): + def visit_array_type(self, + name: str, + info: Optional[QAPISourceInfo], + ifcond: List[str], + element_type: QAPISchemaType) -> None: with ifcontext(ifcond, self._genh, self._genc): self._genh.add(gen_visit_decl(name)) self._genc.add(gen_visit_list(name, element_type)) - def visit_object_type(self, name, info, ifcond, features, - base, members, variants): + def visit_object_type(self, + name: str, + info: Optional[QAPISourceInfo], + ifcond: List[str], + features: List[QAPISchemaFeature], + base: Optional[QAPISchemaObjectType], + members: List[QAPISchemaObjectTypeMember], + variants: Optional[QAPISchemaVariants]) -> None: # Nothing to do for the special empty builtin if name == 'q_empty': return @@ -328,15 +374,23 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor): if not name.startswith('q_'): # only explicit types need an allocating visit self._genh.add(gen_visit_decl(name)) - self._genc.add(gen_visit_object(name, base, members, variants)) - - def visit_alternate_type(self, name, info, ifcond, features, variants): + self._genc.add(gen_visit_object(name)) + + def visit_alternate_type(self, + name: str, + info: QAPISourceInfo, + ifcond: List[str], + features: List[QAPISchemaFeature], + variants: QAPISchemaVariants) -> None: with ifcontext(ifcond, self._genh, self._genc): self._genh.add(gen_visit_decl(name)) self._genc.add(gen_visit_alternate(name, variants)) -def gen_visit(schema, output_dir, prefix, opt_builtins): +def gen_visit(schema: QAPISchema, + output_dir: str, + prefix: str, + opt_builtins: bool) -> None: vis = QAPISchemaGenVisitVisitor(prefix) schema.visit(vis) vis.write(output_dir, opt_builtins) |