qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH v1 1/9] qapi: golang: Generate qapi's enum types in Go


From: Markus Armbruster
Subject: Re: [PATCH v1 1/9] qapi: golang: Generate qapi's enum types in Go
Date: Thu, 28 Sep 2023 16:20:55 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/28.2 (gnu/linux)

Daniel P. Berrangé <berrange@redhat.com> writes:

> On Wed, Sep 27, 2023 at 01:25:36PM +0200, Victor Toso wrote:
>> This patch handles QAPI enum types and generates its equivalent in Go.
>> 
>> Basically, Enums are being handled as strings in Golang.
>> 
>> 1. For each QAPI enum, we will define a string type in Go to be the
>>    assigned type of this specific enum.
>> 
>> 2. Naming: CamelCase will be used in any identifier that we want to
>>    export [0], which is everything.
>> 
>> [0] https://go.dev/ref/spec#Exported_identifiers
>> 
>> Example:
>> 
>> qapi:
>>   | { 'enum': 'DisplayProtocol',
>>   |   'data': [ 'vnc', 'spice' ] }
>> 
>> go:
>>   | type DisplayProtocol string
>>   |
>>   | const (
>>   |     DisplayProtocolVnc   DisplayProtocol = "vnc"
>>   |     DisplayProtocolSpice DisplayProtocol = "spice"
>>   | )
>> 
>> Signed-off-by: Victor Toso <victortoso@redhat.com>
>> ---
>>  scripts/qapi/golang.py | 140 +++++++++++++++++++++++++++++++++++++++++
>>  scripts/qapi/main.py   |   2 +
>>  2 files changed, 142 insertions(+)
>>  create mode 100644 scripts/qapi/golang.py
>> 
>> diff --git a/scripts/qapi/golang.py b/scripts/qapi/golang.py
>> new file mode 100644
>> index 0000000000..87081cdd05
>> --- /dev/null
>> +++ b/scripts/qapi/golang.py
>> @@ -0,0 +1,140 @@
>> +"""
>> +Golang QAPI generator
>> +"""
>> +# Copyright (c) 2023 Red Hat Inc.
>> +#
>> +# Authors:
>> +#  Victor Toso <victortoso@redhat.com>
>> +#
>> +# This work is licensed under the terms of the GNU GPL, version 2.
>> +# See the COPYING file in the top-level directory.
>> +
>> +# due QAPISchemaVisitor interface
>> +# pylint: disable=too-many-arguments
>> +
>> +# Just for type hint on self
>> +from __future__ import annotations
>> +
>> +import os
>> +from typing import List, Optional
>> +
>> +from .schema import (
>> +    QAPISchema,
>> +    QAPISchemaType,
>> +    QAPISchemaVisitor,
>> +    QAPISchemaEnumMember,
>> +    QAPISchemaFeature,
>> +    QAPISchemaIfCond,
>> +    QAPISchemaObjectType,
>> +    QAPISchemaObjectTypeMember,
>> +    QAPISchemaVariants,
>> +)
>> +from .source import QAPISourceInfo
>> +
>> +TEMPLATE_ENUM = '''
>> +type {name} string
>> +const (
>> +{fields}
>> +)
>> +'''
>> +
>> +
>> +def gen_golang(schema: QAPISchema,
>> +               output_dir: str,
>> +               prefix: str) -> None:
>> +    vis = QAPISchemaGenGolangVisitor(prefix)
>> +    schema.visit(vis)
>> +    vis.write(output_dir)
>> +
>> +
>> +def qapi_to_field_name_enum(name: str) -> str:
>> +    return name.title().replace("-", "")
>> +
>> +
>> +class QAPISchemaGenGolangVisitor(QAPISchemaVisitor):
>> +
>> +    def __init__(self, _: str):
>> +        super().__init__()
>> +        types = ["enum"]
>> +        self.target = {name: "" for name in types}
>> +        self.schema = None
>> +        self.golang_package_name = "qapi"
>> +
>> +    def visit_begin(self, schema):
>> +        self.schema = schema
>> +
>> +        # Every Go file needs to reference its package name
>> +        for target in self.target:
>> +            self.target[target] = f"package {self.golang_package_name}\n"
>> +
>> +    def visit_end(self):
>> +        self.schema = None
>> +
>> +    def visit_object_type(self: QAPISchemaGenGolangVisitor,
>> +                          name: str,
>> +                          info: Optional[QAPISourceInfo],
>> +                          ifcond: QAPISchemaIfCond,
>> +                          features: List[QAPISchemaFeature],
>> +                          base: Optional[QAPISchemaObjectType],
>> +                          members: List[QAPISchemaObjectTypeMember],
>> +                          variants: Optional[QAPISchemaVariants]
>> +                          ) -> None:
>> +        pass
>> +
>> +    def visit_alternate_type(self: QAPISchemaGenGolangVisitor,
>> +                             name: str,
>> +                             info: Optional[QAPISourceInfo],
>> +                             ifcond: QAPISchemaIfCond,
>> +                             features: List[QAPISchemaFeature],
>> +                             variants: QAPISchemaVariants
>> +                             ) -> None:
>> +        pass
>> +
>> +    def visit_enum_type(self: QAPISchemaGenGolangVisitor,
>> +                        name: str,
>> +                        info: Optional[QAPISourceInfo],
>> +                        ifcond: QAPISchemaIfCond,
>> +                        features: List[QAPISchemaFeature],
>> +                        members: List[QAPISchemaEnumMember],
>> +                        prefix: Optional[str]
>> +                        ) -> None:
>> +
>> +        value = qapi_to_field_name_enum(members[0].name)
>> +        fields = ""
>> +        for member in members:
>> +            value = qapi_to_field_name_enum(member.name)
>> +            fields += f'''\t{name}{value} {name} = "{member.name}"\n'''
>> +
>> +        self.target["enum"] += TEMPLATE_ENUM.format(name=name, 
>> fields=fields[:-1])
>
> Here you are formatting the enums as you visit them, appending to
> the output buffer. The resulting enums appear in whatever order we
> visited them with, which is pretty arbitrary.

We visit in source order, not in arbitrary order.

> Browsing the generated Go code to understand it, I find myself
> wishing that it was emitted in alphabetical order.

If that's easier to read in generated Go, then I suspect it would also
be easier to read in the QAPI schema and in generated C.

> This could be done if we worked in two phase. In the visit phase,
> we collect the bits of data we need, and then add a format phase
> then generates the formatted output, having first sorted by enum
> name.
>
> Same thought for the other types/commands.
>
>> +
>> +    def visit_array_type(self, name, info, ifcond, element_type):
>> +        pass
>> +
>> +    def visit_command(self,
>> +                      name: str,
>> +                      info: Optional[QAPISourceInfo],
>> +                      ifcond: QAPISchemaIfCond,
>> +                      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:
>> +        pass
>> +
>> +    def visit_event(self, name, info, ifcond, features, arg_type, boxed):
>> +        pass
>> +
>> +    def write(self, output_dir: str) -> None:
>> +        for module_name, content in self.target.items():
>> +            go_module = module_name + "s.go"
>> +            go_dir = "go"
>> +            pathname = os.path.join(output_dir, go_dir, go_module)
>> +            odir = os.path.dirname(pathname)
>> +            os.makedirs(odir, exist_ok=True)
>> +
>> +            with open(pathname, "w", encoding="ascii") as outfile:
>
> IIUC, we defacto consider the .qapi json files to be UTF-8, and thus
> in theory we could have non-ascii characters in there somewhere. I'd
> suggest we using utf8 encoding when outputting to avoid surprises.

Seconded.  QAPIGen.write() already uses encoding='utf-8' for writing
generated files.

>> +                outfile.write(content)
>
>
> With regards,
> Daniel




reply via email to

[Prev in Thread] Current Thread [Next in Thread]