Project

General

Profile

Feature #528 ยป 0002-generate_packets.py-make-Location-class-insert-packe.patch

main - Alina Lenk, 05/03/2024 04:13 PM

View differences:

common/generate_packets.py
outside of recursive field types like arrays, this will usually just be
a field of a packet, but it serves to concisely handle the recursion."""
# placeholder that will clearly be an error if it accidentally
# shows up in generated code
_PACKET = "#error gen_packet$"
_INDICES = "ijk"
name: str
"""The name associated with this location; used in log messages."""
location: str
"""The actual location as used in code"""
_location: str
"""The actual location as used in code, including placeholders for
where the packet name goes"""
depth: int
"""The array nesting depth of this location; used to determine index
variable names."""
......
"""The total sub-location nesting depth of the JSON field address
for this location"""
def __init__(self, name: str, location: "str | None" = None,
def __init__(self, name: str, *, location: "str | None" = None,
depth: int = 0, json_depth: "int | None" = None):
self.name = name
self.location = location if location is not None else name
self._location = location if location is not None else self._PACKET + name
self.depth = depth
self.json_depth = json_depth if json_depth is not None else depth
def replace(self, new_location: str) -> "Location":
"""Return the given string as a new Location with the same metadata
as self"""
return type(self)(
name = self.name,
location = new_location,
depth = self.depth,
json_depth = self.json_depth,
)
def deeper(self, new_location: str, json_step: int = 1) -> "Location":
"""Return the given string as a new Location with the same name as
self and incremented depth"""
return type(self)(self.name, new_location,
self.depth + 1, self.json_depth + json_step)
return type(self)(
name = self.name,
location = new_location,
depth = self.depth + 1,
json_depth = self.json_depth + json_step,
)
def sub_full(self, json_step: int = 1) -> "Location":
"""Like self.sub, but with the option to step the JSON nesting
......
of this location's corresponding field address"""
return "field_addr.sub_location" + self.json_depth * "->sub_location"
def __matmul__(self, packet: str | None) -> str:
"""self @ packet
Code fragment of this location in the given packet, or in local
variables if packet is None"""
packet = f"{packet}->" if packet is not None else ""
return self._location.replace(self._PACKET, packet)
def __str__(self) -> str:
return self.location
return self._location
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.name!r}, {self.location!r}, {self.depth!r}, {self.json_depth!r})"
return f"<{type(self).__name__} {self.name}(depth={self.depth}, json_depth={self.json_depth}) {self @ 'PACKET'}>"
#################### Components of a packets definition ####################
......
handle function.
See also self.get_code_param()"""
return f"{packet}->{location}"
return f"{location @ packet}"
def get_code_init(self, location: Location, packet: str) -> str:
"""Generate a code snippet initializing a field of this type in the
......
if self.complex:
raise ValueError(f"default get_code_copy implementation called for field {location.name} with complex type {self!r}")
return f"""\
{dest}->{location} = {src}->{location};
{location @ dest} = {location @ src};
"""
def get_code_fill(self, location: Location, packet: str) -> str:
"""Generate a code snippet shallow-copying a value of this type from
dsend arguments into a packet struct."""
return f"""\
{packet}->{location} = {location};
{location @ packet} = {location @ None};
"""
def get_code_free(self, location: Location, packet: str) -> str:
......
def get_code_declaration(self, location: Location) -> str:
return f"""\
{self.public_type} {location};
{self.public_type} {location @ None};
"""
def get_code_param(self, location: Location) -> str:
return f"{self.public_type} {location}"
return f"{self.public_type} {location @ None}"
def get_code_hash(self, location: Location, packet: str) -> str:
raise ValueError(f"hash not supported for type {self} in field {location.name}")
def get_code_cmp(self, location: Location, new: str, old: str) -> str:
return f"""\
differ = ({old}->{location} != {new}->{location});
differ = ({location @ old} != {location @ new});
"""
def get_code_put(self, location: Location, packet: str, diff_packet: "str | None" = None) -> str:
return f"""\
e |= DIO_PUT({self.dataio_type}, &dout, &field_addr, {packet}->{location});
e |= DIO_PUT({self.dataio_type}, &dout, &field_addr, {location @ packet});
"""
def get_code_get(self, location: Location, packet: str, deep_diff: bool = False) -> str:
return f"""\
if (!DIO_GET({self.dataio_type}, &din, &field_addr, &{packet}->{location})) {{
if (!DIO_GET({self.dataio_type}, &din, &field_addr, &{location @ packet})) {{
RECEIVE_PACKET_FIELD_ERROR({location.name});
}}
"""
......
def get_code_hash(self, location: Location, packet: str) -> str:
return f"""\
result += {packet}->{location};
result += {location @ packet};
"""
def get_code_get(self, location: Location, packet: str, deep_diff: bool = False) -> str:
......
if (!DIO_GET({self.dataio_type}, &din, &field_addr, &readin)) {{
RECEIVE_PACKET_FIELD_ERROR({location.name});
}}
{packet}->{location} = readin;
{location @ packet} = readin;
}}
"""
......
def get_code_cmp(self, location: Location, new: str, old: str) -> str:
return f"""\
differ = ((int) ({old}->{location} * {self.float_factor}) != (int) ({new}->{location} * {self.float_factor}));
differ = ((int) ({location @ old} * {self.float_factor}) != (int) ({location @ new} * {self.float_factor}));
"""
def get_code_put(self, location: Location, packet: str, diff_packet: "str | None" = None) -> str:
return f"""\
e |= DIO_PUT({self.dataio_type}, &dout, &field_addr, {packet}->{location}, {self.float_factor:d});
e |= DIO_PUT({self.dataio_type}, &dout, &field_addr, {location @ packet}, {self.float_factor:d});
"""
def get_code_get(self, location: Location, packet: str, deep_diff: bool = False) -> str:
return f"""\
if (!DIO_GET({self.dataio_type}, &din, &field_addr, &{packet}->{location}, {self.float_factor:d})) {{
if (!DIO_GET({self.dataio_type}, &din, &field_addr, &{location @ packet}, {self.float_factor:d})) {{
RECEIVE_PACKET_FIELD_ERROR({location.name});
}}
"""
......
def get_code_cmp(self, location: Location, new: str, old: str) -> str:
return f"""\
differ = !BV_ARE_EQUAL({old}->{location}, {new}->{location});
differ = !BV_ARE_EQUAL({location @ old}, {location @ new});
"""
def get_code_put(self, location: Location, packet: str, diff_packet: "str | None" = None) -> str:
return f"""\
e |= DIO_BV_PUT(&dout, &field_addr, {packet}->{location});
e |= DIO_BV_PUT(&dout, &field_addr, {location @ packet});
"""
def get_code_get(self, location: Location, packet: str, deep_diff: bool = False) -> str:
return f"""\
if (!DIO_BV_GET(&din, &field_addr, {packet}->{location})) {{
if (!DIO_BV_GET(&din, &field_addr, {location @ packet})) {{
RECEIVE_PACKET_FIELD_ERROR({location.name});
}}
"""
......
def get_code_param(self, location: Location) -> str:
if not location.depth:
# top level: pass by-reference
return "const " + super().get_code_param(location.deeper(f"*{location}"))
return "const " + super().get_code_param(location.replace(f"*{location}"))
return super().get_code_param(location)
def get_code_handle_arg(self, location: Location, packet: str) -> str:
......
def get_code_cmp(self, location: Location, new: str, old: str) -> str:
return f"""\
differ = !are_{self.dataio_type}s_equal(&{old}->{location}, &{new}->{location});
differ = !are_{self.dataio_type}s_equal(&{location @ old}, &{location @ new});
"""
def get_code_put(self, location: Location, packet: str, diff_packet: "str | None" = None) -> str:
return f"""\
e |= DIO_PUT({self.dataio_type}, &dout, &field_addr, &{packet}->{location});
e |= DIO_PUT({self.dataio_type}, &dout, &field_addr, &{location @ packet});
"""
DEFAULT_REGISTRY.public_patterns[StructType.TYPE_PATTERN] = StructType
......
def get_code_cmp(self, location: Location, new: str, old: str) -> str:
return f"""\
differ = !cm_are_parameter_equal(&{old}->{location}, &{new}->{location});
differ = !cm_are_parameter_equal(&{location @ old}, &{location @ new});
"""
DEFAULT_REGISTRY.dataio_types["cm_parameter"] = CmParameterType
......
def get_code_copy(self, location: Location, dest: str, src: str) -> str:
return f"""\
worklist_copy(&{dest}->{location}, &{src}->{location});
worklist_copy(&{location @ dest}, &{location @ src});
"""
def get_code_fill(self, location: Location, packet: str) -> str:
return f"""\
worklist_copy(&{packet}->{location}, {location});
worklist_copy(&{location @ packet}, {location @ None});
"""
DEFAULT_REGISTRY.dataio_types["worklist"] = WorklistType
......
def get_code_declaration(self, location: Location) -> str:
return super().get_code_declaration(
location.deeper(f"{location}[{self.size.declared}]")
location.replace(f"{location}[{self.size.declared}]")
)
def get_code_param(self, location: Location) -> str:
# add "const" if top level
pre = "" if location.depth else "const "
return pre + super().get_code_param(location.deeper(f"*{location}"))
# see ArrayType.get_code_param() for explanation
if not location.depth:
return "const " + super().get_code_param(location.replace(f"*{location}"))
else:
return super().get_code_param(location.replace(f"*const {location}"))
@abstractmethod
def get_code_fill(self, location: Location, packet: str) -> str:
......
def get_code_fill(self, location: Location, packet: str) -> str:
return f"""\
sz_strlcpy({packet}->{location}, {location});
sz_strlcpy({location @ packet}, {location @ None});
"""
def get_code_copy(self, location: Location, dest: str, src: str) -> str:
return f"""\
sz_strlcpy({dest}->{location}, {src}->{location});
sz_strlcpy({location @ dest}, {location @ src});
"""
def get_code_cmp(self, location: Location, new: str, old: str) -> str:
return f"""\
differ = (strcmp({old}->{location}, {new}->{location}) != 0);
differ = (strcmp({location @ old}, {location @ new}) != 0);
"""
def get_code_get(self, location: Location, packet: str, deep_diff: bool = False) -> str:
return f"""\
if (!DIO_GET({self.dataio_type}, &din, &field_addr, {packet}->{location}, sizeof({packet}->{location}))) {{
if (!DIO_GET({self.dataio_type}, &din, &field_addr, {location @ packet}, sizeof({location @ packet}))) {{
RECEIVE_PACKET_FIELD_ERROR({location.name});
}}
"""
......
def get_code_copy(self, location: Location, dest: str, src: str) -> str:
return f"""\
memcpy({dest}->{location}, {src}->{location}, {self.size.actual_for(src)});
memcpy({location @ dest}, {location @ src}, {self.size.actual_for(src)});
"""
def get_code_cmp(self, location: Location, new: str, old: str) -> str:
if self.size.constant:
return f"""\
differ = (memcmp({old}->{location}, {new}->{location}, {self.size.declared}) != 0);
differ = (memcmp({location @ old}, {location @ new}, {self.size.declared}) != 0);
"""
return f"""\
differ = (({self.size.actual_for(old)} != {self.size.actual_for(new)})
|| (memcmp({old}->{location}, {new}->{location}, {self.size.actual_for(new)}) != 0));
|| (memcmp({location @ old}, {location @ new}, {self.size.actual_for(new)}) != 0));
"""
def get_code_put(self, location: Location, packet: str, diff_packet: "str | None" = None) -> str:
return f"""\
e |= DIO_PUT({self.dataio_type}, &dout, &field_addr, &{packet}->{location}, {self.size.actual_for(packet)});
e |= DIO_PUT({self.dataio_type}, &dout, &field_addr, &{location @ packet}, {self.size.actual_for(packet)});
"""
def get_code_get(self, location: Location, packet: str, deep_diff: bool = False) -> str:
return f"""\
{self.size.size_check_get(location.name, packet)}\
if (!DIO_GET({self.dataio_type}, &din, &field_addr, {packet}->{location}, {self.size.actual_for(packet)})) {{
if (!DIO_GET({self.dataio_type}, &din, &field_addr, {location @ packet}, {self.size.actual_for(packet)})) {{
RECEIVE_PACKET_FIELD_ERROR({location.name});
}}
"""
......
)
def get_code_param(self, location: Location) -> str:
# When changing this, update SizedType.get_code_param() accordingly
# Note: If we're fine with writing `foo_t const *fieldname`,
# we'd only need one case, .deeper(f"const *{location}")
if not location.depth:
......
return "const " + self.elem.get_code_param(location.deeper(f"*{location}"))
else:
# const foo_t *fieldname ~> const foo_t *const *fieldname
# the final * is already part of {location}
# the final * is already part of the location
return self.elem.get_code_param(location.deeper(f"*const {location}"))
def get_code_init(self, location: Location, packet: str) -> str:
......
class _VecSize(SizeInfo):
"""Helper class to make SizeInfo methods work with strvec sizes"""
_actual_loc: Location
def __init__(self, location: Location):
super().__init__("GENERATE_PACKETS_ERROR", str(location))
self._actual_loc = location.replace(f"strvec_size({location})")
def actual_for(self, packet: str) -> str:
return f"strvec_size({packet}->{self._actual})"
return self._actual_loc @ packet
def __str__(self) -> str:
return "*"
......
def get_code_declaration(self, location: Location) -> str:
return f"""\
{self.public_type} *{location};
{self.public_type} *{location @ None};
"""
def get_code_param(self, location: Location) -> str:
if not location.depth:
return f"const {self.public_type} *{location}"
return f"const {self.public_type} *{location @ None}"
else:
# const struct strvec *const *fieldname
# the final * is already part of {location}
# the final * is already part of the location
# initial const gets added from outside
return f"{self.public_type} *const {location}"
return f"{self.public_type} *const {location @ None}"
def get_code_init(self, location: Location, packet: str) -> str:
# we're always allocating our vectors, even if they're empty
return f"""\
{packet}->{location} = strvec_new();
{location @ packet} = strvec_new();
"""
def get_code_fill(self, location: Location, packet: str) -> str:
......
# safety: the packet's contents will not be modified without cloning
# it first, so discarding 'const' qualifier here is safe
return f"""\
{packet}->{location} = (struct strvec *) {location};
{location @ packet} = (struct strvec *) {location @ None};
"""
def get_code_copy(self, location: Location, dest: str, src: str) -> str:
# dest is initialized by us ~> not null
# src might be a packet passed in from outside ~> could be null
return f"""\
if ({src}->{location}) {{
strvec_copy({dest}->{location}, {src}->{location});
if ({location @ src}) {{
strvec_copy({location @ dest}, {location @ src});
}} else {{
strvec_clear({dest}->{location});
strvec_clear({location @ dest});
}}
"""
def get_code_free(self, location: Location, packet: str) -> str:
return f"""\
if ({packet}->{location}) {{
strvec_destroy({packet}->{location});
{packet}->{location} = nullptr;
if ({location @ packet}) {{
strvec_destroy({location @ packet});
{location @ packet} = nullptr;
}}
"""
......
def get_code_cmp(self, location: Location, new: str, old: str) -> str:
# "new" packet passed in from outside might have null vector
return f"""\
if ({new}->{location}) {{
differ = !are_strvecs_equal({old}->{location}, {new}->{location});
if ({location @ new}) {{
differ = !are_strvecs_equal({location @ old}, {location @ new});
}} else {{
differ = (strvec_size({old}->{location}) > 0);
differ = (strvec_size({location @ old}) > 0);
}}
"""
......
# which we're a long way from
size = __class__._VecSize(location)
return f"""\
if (!{packet}->{location}) {{
if (!{location @ packet}) {{
/* Transmit null vector as empty vector */
e |= DIO_PUT(arraylen, &dout, &field_addr, 0);
}} else {{
......
#endif /* FREECIV_JSON_CONNECTION */
for ({location.index} = 0; {location.index} < {size.actual_for(packet)}; {location.index}++) {{
const char *pstr = strvec_get({packet}->{location}, {location.index});
const char *pstr = strvec_get({location @ packet}, {location.index});
if (!pstr) {{
/* Transmit null strings as empty strings */
......
index_put = prefix(" ", size.index_put(packet, location.index))
index_put_sentinel = prefix(" ", size.index_put(packet, size.actual_for(packet)))
return f"""\
if (!{packet}->{location} || 0 == {size.actual_for(packet)}) {{
if (!{location @ packet} || 0 == {size.actual_for(packet)}) {{
/* Special case for empty vector. */
#ifdef FREECIV_JSON_CONNECTION
......
#endif /* FREECIV_JSON_CONNECTION */
for ({location.index} = 0; {location.index} < {size.actual_for(packet)}; {location.index}++) {{
const char *pstr = strvec_get({packet}->{location}, {location.index});
const char *pstr = strvec_get({location @ packet}, {location.index});
if (!pstr) {{
/* Transmit null strings as empty strings */
......
}}
if ({location.index} < {size.actual_for(diff_packet)}) {{
const char *pstr_old = strvec_get({diff_packet}->{location}, {location.index});
const char *pstr_old = strvec_get({location @ diff_packet}, {location.index});
differ = (strcmp(pstr_old ? pstr_old : "", pstr) != 0);
}} else {{
......
if (!DIO_GET(arraylen, &din, &field_addr, &{location.index})) {{
RECEIVE_PACKET_FIELD_ERROR({location.name});
}}
strvec_reserve({packet}->{location}, {location.index});
strvec_reserve({location @ packet}, {location.index});
#ifdef FREECIV_JSON_CONNECTION
{location.json_subloc} = plocation_elem_new(0);
......
#endif /* FREECIV_JSON_CONNECTION */
if (!DIO_GET({self.dataio_type}, &din, &field_addr, readin, sizeof(readin))
|| !strvec_set({packet}->{location}, {location.index}, readin)) {{
|| !strvec_set({location @ packet}, {location.index}, readin)) {{
RECEIVE_PACKET_FIELD_ERROR({location.name});
}}
}}
......
if (!DIO_GET(uint16, &din, &field_addr, &readin)) {{
RECEIVE_PACKET_FIELD_ERROR({location.name});
}}
strvec_reserve({packet}->{location}, readin);
strvec_reserve({location @ packet}, readin);
}}
#ifdef FREECIV_JSON_CONNECTION
......
#endif /* FREECIV_JSON_CONNECTION */
if (!DIO_GET({self.dataio_type}, &din, &field_addr, readin, sizeof(readin))
|| !strvec_set({packet}->{location}, {location.index}, readin)) {{
|| !strvec_set({location @ packet}, {location.index}, readin)) {{
RECEIVE_PACKET_FIELD_ERROR({location.name});
}}
    (1-1/1)