-
Notifications
You must be signed in to change notification settings - Fork 63
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
base: main
Are you sure you want to change the base?
Codegen serializers #81
Conversation
from projects.jdwp.defs.schema import Command, Field, PrimitiveType | ||
|
||
|
||
def generate_deserialization_function(command: Command) -> str: |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
74e26bc
to
c6dbe54
Compare
if isinstance(array_type.element_type, Struct): | ||
return ( |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
c6dbe54
to
31f4d47
Compare
if isinstance(array_type.element_type, Struct): | ||
return ( |
There was a problem hiding this comment.
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.
parse_code = f"@staticmethod\nasync def parse(input: JDWPInputStreamBase) -> {struct_name}:\n" | ||
|
||
for field in struct.fields: | ||
if self.__is_explicit_field(field): |
There was a problem hiding this comment.
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 _: |
There was a problem hiding this comment.
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})" |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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.
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" |
There was a problem hiding this comment.
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.
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]: |
There was a problem hiding this comment.
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 _: |
There was a problem hiding this comment.
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') |
There was a problem hiding this comment.
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.
No description provided.