Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codegen serializers #81

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

Daquiver1
Copy link
Contributor

No description provided.

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Nov 15, 2023
from projects.jdwp.defs.schema import Command, Field, PrimitiveType


def generate_deserialization_function(command: Command) -> str:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to generate serialize and parse methods for all generated structs. Once that happens we will make them subclasses of JDWPStruct. For that we need two methods for each struct:

  • def serialize(self, output: JDWPOutputStreamBase)
  • static async def parse(input: JDWPInputStreamBase)

In order to generate both methods we need to iterate through fields defined in the spec and read/write them. Example of parse for __ClassPaths_reply:

@staticmethod
async def parse(input: JDWPInputStreamBase) -> ClassPathsReply:
    baseDir: str = await input.read_string()

    classpaths: int = await input.read_int()

    classpathsArray: List[ClassPathsReplyClasspathsArrayElement] = []
    for _ in range(classpaths):
        classpathsArray.append(await ClassPathsReplyClasspathsArrayElement.parse(input))

    bootclasspaths = await input.read_int()
    
    bootclasspathsArray: List[ClassPathsReplyClasspathsArrayElement] = []
    for _ in range(bootclasspaths):
        bootclasspathsArray.append(await ClassPathsReplyBootclasspathsArrayElement.parse(input))

    return ClassPathsReply(
        baseDir=baseDir,
        classpathsArray=classpathsArray,
        bootclasspathsArray=bootclasspathsArray,
    )

Serialize method for the same struct could look like:

def serialize(self, output: JDWPOutputStreamBase):
    output.write_string(self.baseDir)

    output.write_int(len(self.classpathsArray))

    for element in self.classpathsArray:
        element.serialize(output)
        
    output.write_int(len(self.bootclasspathsArray))

    for element in self.bootclasspathsArray:
        element.serialize(output)

Tags of unions and lengths of arrays are interesting: they don't have corresponding fields in generated structs, but those values. Those values are used only to guide the parsing logic and need to be computed when serializing structs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably want to make generate_deserialization_function and generate_serialization_function methods of StructGenerator so that we can generate parsing and serializing functions as methods of corresponding structs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thing, JDWPStruct.serialize is an async method, but I will be changing it now. Writing to a stream is not async, so we can keep it simple.

projects/jdwp/codegen/deserializer_generator.py Outdated Show resolved Hide resolved
projects/jdwp/codegen/streams.py Outdated Show resolved Hide resolved
@Daquiver1 Daquiver1 force-pushed the codegen_serializers branch from 74e26bc to c6dbe54 Compare December 6, 2023 12:32
Comment on lines 30 to 36
if isinstance(array_type.element_type, Struct):
return (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should always be the case, the type of element_type is Struct: https://github.com/facebookexperimental/ExtendedAndroidTools/blob/main/projects/jdwp/defs/schema.py#L76

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this if, type system enforces that this branch is always taken so let's not consider other options.

projects/jdwp/codegen/streams.py Outdated Show resolved Hide resolved
projects/jdwp/codegen/dataclass_generator.py Show resolved Hide resolved
projects/jdwp/codegen/dataclass_generator.py Outdated Show resolved Hide resolved
projects/jdwp/codegen/dataclass_generator.py Outdated Show resolved Hide resolved
projects/jdwp/codegen/dataclass_generator.py Outdated Show resolved Hide resolved
projects/jdwp/codegen/dataclass_generator.py Outdated Show resolved Hide resolved
projects/jdwp/codegen/dataclass_generator.py Outdated Show resolved Hide resolved
projects/jdwp/codegen/dataclass_generator.py Outdated Show resolved Hide resolved
projects/jdwp/codegen/dataclass_generator.py Outdated Show resolved Hide resolved
@Daquiver1 Daquiver1 force-pushed the codegen_serializers branch from c6dbe54 to 31f4d47 Compare December 6, 2023 13:44
@Daquiver1 Daquiver1 requested a review from michalgr December 6, 2023 17:35
projects/jdwp/codegen/new_type_generator.py Show resolved Hide resolved
projects/jdwp/codegen/dataclass_generator.py Outdated Show resolved Hide resolved
Comment on lines 30 to 36
if isinstance(array_type.element_type, Struct):
return (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this if, type system enforces that this branch is always taken so let's not consider other options.

projects/jdwp/codegen/dataclass_generator.py Outdated Show resolved Hide resolved
projects/jdwp/codegen/dataclass_generator.py Outdated Show resolved Hide resolved
projects/jdwp/codegen/dataclass_generator.py Outdated Show resolved Hide resolved
projects/jdwp/codegen/dataclass_generator.py Outdated Show resolved Hide resolved
projects/jdwp/codegen/dataclass_generator.py Outdated Show resolved Hide resolved
projects/jdwp/codegen/dataclass_generator.py Outdated Show resolved Hide resolved
projects/jdwp/codegen/dataclass_generator.py Outdated Show resolved Hide resolved
@Daquiver1 Daquiver1 requested a review from michalgr December 6, 2023 20:13
parse_code = f"@staticmethod\nasync def parse(input: JDWPInputStreamBase) -> {struct_name}:\n"

for field in struct.fields:
if self.__is_explicit_field(field):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we need to parse every field. We want to parse things like array lengths and union tags because they are necessary for parsing actual arrays and unions.

return self.__generate_serialize_array_code(field)
case TaggedUnion():
return self.__generate_serialize_tagged_union_code(field)
case _:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to handle ArrayLength and UnionTag fields here.

case TaggedUnion():
return self.__generate_serialize_tagged_union_code(field)
case _:
return f"output.write_{field_name}(self.{field_name})"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is write_{field_name} the right method to call here ?

array_length_field_type = field.type.length
field_name = self.__get_field_name(field)
array_code = (
f"output.write_{array_length_field_type.lower()}(len(self.{field_name}))\n"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This we should generate in a function handling ArrayLength type, __generate_serialize_array_length_code or something like that. That function should find the field of the containing struct whose size it is and write the value accordingly.

Comment on lines +89 to +92
union_code = "match self.{0}:\n".format(field_name)
for enum_val, struct_type in union.cases:
union_code += f" case {struct_type.__name__}():\n"
union_code += f" output.write_{union.tag.type.name.lower()}({enum_val.value})\n"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly to the array case the tag should be written in a method handling fields of UnionTag type.

Comment on lines +152 to +161
def __generate_parse_field_code(self, field: Field) -> str:
match field.type:
case Array():
return self.__generate_parse_array_code(field)
case TaggedUnion():
return self.__generate_parse_tagged_union_code(field)
case _:
return f"await input.read_{field.type.name.lower()}()"

def generate(self) -> typing.Generator[str, None, None]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not seem to be correctly indented

return self.__generate_parse_array_code(field)
case TaggedUnion():
return self.__generate_parse_tagged_union_code(field)
case _:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to handle array length and union tag types too.

@@ -11,7 +11,7 @@ def get_type_alias_definition(jdwp_type: IdType) -> str:


def generate_new_types():
print("import typing")
print('import typing')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code formatter complains about this, revert to the original state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants