#!/usr/bin/python3 """Binary message code generator prototype. I was thinking about Golang’s “encoding/binary” standard library module and about how to build a similar facility for C. Generates structs along with some serialization and deserialization functions for them that look like this: bool write_KeyEvent_little_endian(stream out, struct KeyEvent *obj) { if (!write_u8_little_endian(out, &obj.down_flag)) return false; if (!write_dummy_u8_little_endian(out)) return false; if (!write_dummy_u8_little_endian(out)) return false; if (!write_u32_little_endian(out, &obj.keysym)) return false; return true; } See also file `binary-serialization.md` in pavnotes2, binmsg_cpp.h, and binmsg_cpp.c. """ import io, sys example_spec = """ PIXEL_FORMAT bits_per_pixel u8 depth u8 big_endian_flag u8 true_color_flag u8 red_max u16 green_max u16 blue_max u16 red_shift u8 green_shift u8 blue_shift u8 _ u8 _ u8 _ u8 ServerInit width u16 height u16 pixelFormat PIXEL_FORMAT nameLength u32 KeyEvent down_flag u8 _ u8 _ u8 keysym u32 """ def parse(lines): formats = [] current_format = None for line in lines: if not line.strip(): current_format = None continue if current_format is None: current_format = {'name': line.strip(), 'fields': []} formats.append(current_format) continue fieldname, type = line.split() current_format['fields'].append((fieldname, type)) return formats def generate_c(formats): yield '#include \n' yield '#include \n' yield '#include "binmsg.h"\n\n' for format in formats: yield f'typedef struct {format["name"]} {{\n' for field_name, field_type in format['fields']: if field_name != '_': yield f' {field_type} {field_name};\n' yield f'}} {format["name"]};\n\n' for how in ['big_endian', 'little_endian']: for op in ['read', 'write']: for format in formats: yield from generate_c_function(how, op, format) def generate_c_function(how, op, format): yield f'bool {op}_{format["name"]}_{how}(stream out, ' yield f'struct {format["name"]} *obj) {{\n' for field_name, field_type in format['fields']: if field_name == '_': yield (f' if (!{op}_dummy_{field_type}_{how}(out)) ' + 'return false;\n') else: yield (f' if (!{op}_{field_type}_{how}(out, ' + f'&obj->{field_name})) return false;\n') yield ' return true;\n}\n\n' if __name__ == '__main__': infile = (io.StringIO(example_spec) if len(sys.argv) == 1 else open(sys.argv[1])) sys.stdout.writelines(generate_c(parse(infile)))