diff --git a/scripts/buildcommands.py b/scripts/buildcommands.py index b41ac58b..1de62d75 100644 --- a/scripts/buildcommands.py +++ b/scripts/buildcommands.py @@ -120,64 +120,117 @@ Handlers.append(HandleConstants()) ###################################################################### -# Command and output parser generation +# Wire protocol commands and responses ###################################################################### -def build_parser(parser, iscmd, all_param_types): - if parser.name == "#output": - comment = "Output: " + parser.msgformat - else: - comment = parser.msgformat - params = '0' - types = tuple([t.__class__.__name__ for t in parser.param_types]) - if types: - paramid = all_param_types.get(types) - if paramid is None: - paramid = len(all_param_types) - all_param_types[types] = paramid - params = 'command_parameters%d' % (paramid,) - out = """ +# Dynamic command and response registration +class HandleCommandGeneration: + def __init__(self): + self.commands = {} + self.encoders = [] + self.msg_to_id = { m: i for i, m in msgproto.DefaultMessages.items() } + self.messages_by_name = { m.split()[0]: m for m in self.msg_to_id } + self.all_param_types = {} + self.ctr_dispatch = { + '_DECL_COMMAND': self.decl_command, + '_DECL_ENCODER': self.decl_encoder, + '_DECL_OUTPUT': self.decl_output + } + def decl_command(self, req): + funcname, flags, msgname = req.split()[1:4] + if msgname in self.commands: + error("Multiple definitions for command '%s'" % msgname) + self.commands[msgname] = (funcname, flags, msgname) + msg = req.split(None, 3)[3] + m = self.messages_by_name.get(msgname) + if m is not None and m != msg: + error("Conflicting definition for command '%s'" % msgname) + self.messages_by_name[msgname] = msg + def decl_encoder(self, req): + msg = req.split(None, 1)[1] + msgname = msg.split()[0] + m = self.messages_by_name.get(msgname) + if m is not None and m != msg: + error("Conflicting definition for message '%s'" % msgname) + self.messages_by_name[msgname] = msg + self.encoders.append((msgname, msg)) + def decl_output(self, req): + msg = req.split(None, 1)[1] + self.encoders.append((None, msg)) + def create_message_ids(self): + # Create unique ids for each message type + msgid = max(self.msg_to_id.values()) + for msgname in self.commands.keys() + [m for n, m in self.encoders]: + msg = self.messages_by_name.get(msgname, msgname) + if msg not in self.msg_to_id: + msgid += 1 + self.msg_to_id[msg] = msgid + def update_data_dictionary(self, data): + self.create_message_ids() + messages = { msgid: msg for msg, msgid in self.msg_to_id.items() } + data['messages'] = messages + commands = [self.msg_to_id[msg] + for msgname, msg in self.messages_by_name.items() + if msgname in self.commands] + data['commands'] = sorted(commands) + responses = [self.msg_to_id[msg] + for msgname, msg in self.messages_by_name.items() + if msgname not in self.commands] + data['responses'] = sorted(responses) + def build_parser(self, parser, iscmd): + if parser.name == "#output": + comment = "Output: " + parser.msgformat + else: + comment = parser.msgformat + params = '0' + types = tuple([t.__class__.__name__ for t in parser.param_types]) + if types: + paramid = self.all_param_types.get(types) + if paramid is None: + paramid = len(self.all_param_types) + self.all_param_types[types] = paramid + params = 'command_parameters%d' % (paramid,) + out = """ // %s .msg_id=%d, .num_params=%d, .param_types = %s, """ % (comment, parser.msgid, len(types), params) - if iscmd: - num_args = (len(types) + types.count('PT_progmem_buffer') - + types.count('PT_buffer')) - out += " .num_args=%d," % (num_args,) - else: - max_size = min(msgproto.MESSAGE_MAX, - (msgproto.MESSAGE_MIN + 1 - + sum([t.max_length for t in parser.param_types]))) - out += " .max_size=%d," % (max_size,) - return out - -def build_encoders(encoders, msg_to_id, all_param_types): - encoder_defs = [] - output_code = [] - encoder_code = [] - did_output = {} - for msgname, msg in encoders: - msgid = msg_to_id[msg] - if msgid in did_output: - continue - s = msg - did_output[msgid] = True - code = (' if (__builtin_strcmp(str, "%s") == 0)\n' - ' return &command_encoder_%s;\n' % (s, msgid)) - if msgname is None: - parser = msgproto.OutputFormat(msgid, msg) - output_code.append(code) + if iscmd: + num_args = (len(types) + types.count('PT_progmem_buffer') + + types.count('PT_buffer')) + out += " .num_args=%d," % (num_args,) else: - parser = msgproto.MessageFormat(msgid, msg) - encoder_code.append(code) - parsercode = build_parser(parser, 0, all_param_types) - encoder_defs.append( - "const struct command_encoder command_encoder_%s PROGMEM = {" - " %s\n};\n" % ( - msgid, parsercode)) - fmt = """ + max_size = min(msgproto.MESSAGE_MAX, + (msgproto.MESSAGE_MIN + 1 + + sum([t.max_length for t in parser.param_types]))) + out += " .max_size=%d," % (max_size,) + return out + def generate_responses_code(self): + encoder_defs = [] + output_code = [] + encoder_code = [] + did_output = {} + for msgname, msg in self.encoders: + msgid = self.msg_to_id[msg] + if msgid in did_output: + continue + s = msg + did_output[msgid] = True + code = (' if (__builtin_strcmp(str, "%s") == 0)\n' + ' return &command_encoder_%s;\n' % (s, msgid)) + if msgname is None: + parser = msgproto.OutputFormat(msgid, msg) + output_code.append(code) + else: + parser = msgproto.MessageFormat(msgid, msg) + encoder_code.append(code) + parsercode = self.build_parser(parser, 0) + encoder_defs.append( + "const struct command_encoder command_encoder_%s PROGMEM = {" + " %s\n};\n" % ( + msgid, parsercode)) + fmt = """ %s const __always_inline struct command_encoder * @@ -194,39 +247,32 @@ ctr_lookup_output(const char *str) return NULL; } """ - return fmt % ("".join(encoder_defs).strip(), "".join(encoder_code).strip(), - "".join(output_code).strip()) - -def build_param_types(all_param_types): - sorted_param_types = sorted([(i, a) for a, i in all_param_types.items()]) - params = [''] - for paramid, argtypes in sorted_param_types: - params.append( - 'static const uint8_t command_parameters%d[] PROGMEM = {\n' - ' %s };' % ( - paramid, ', '.join(argtypes),)) - params.append('') - return "\n".join(params) - -def build_commands(cmd_by_id, messages_by_name, all_param_types): - max_cmd_msgid = max(cmd_by_id.keys()) - index = [] - externs = {} - for msgid in range(max_cmd_msgid+1): - if msgid not in cmd_by_id: - index.append(" {\n},") - continue - funcname, flags, msgname = cmd_by_id[msgid] - msg = messages_by_name[msgname] - externs[funcname] = 1 - parser = msgproto.MessageFormat(msgid, msg) - parsercode = build_parser(parser, 1, all_param_types) - index.append(" {%s\n .flags=%s,\n .func=%s\n}," % ( - parsercode, flags, funcname)) - index = "".join(index).strip() - externs = "\n".join(["extern void "+funcname+"(uint32_t*);" - for funcname in sorted(externs)]) - fmt = """ + return fmt % ("".join(encoder_defs).strip(), + "".join(encoder_code).strip(), + "".join(output_code).strip()) + def generate_commands_code(self): + cmd_by_id = { + self.msg_to_id[self.messages_by_name.get(msgname, msgname)]: cmd + for msgname, cmd in self.commands.items() + } + max_cmd_msgid = max(cmd_by_id.keys()) + index = [] + externs = {} + for msgid in range(max_cmd_msgid+1): + if msgid not in cmd_by_id: + index.append(" {\n},") + continue + funcname, flags, msgname = cmd_by_id[msgid] + msg = self.messages_by_name[msgname] + externs[funcname] = 1 + parser = msgproto.MessageFormat(msgid, msg) + parsercode = self.build_parser(parser, 1) + index.append(" {%s\n .flags=%s,\n .func=%s\n}," % ( + parsercode, flags, funcname)) + index = "".join(index).strip() + externs = "\n".join(["extern void "+funcname+"(uint32_t*);" + for funcname in sorted(externs)]) + fmt = """ %s const struct command_parser command_index[] PROGMEM = { @@ -235,22 +281,35 @@ const struct command_parser command_index[] PROGMEM = { const uint8_t command_index_size PROGMEM = ARRAY_SIZE(command_index); """ - return fmt % (externs, index) + return fmt % (externs, index) + def generate_param_code(self): + sorted_param_types = sorted( + [(i, a) for a, i in self.all_param_types.items()]) + params = [''] + for paramid, argtypes in sorted_param_types: + params.append( + 'static const uint8_t command_parameters%d[] PROGMEM = {\n' + ' %s };' % ( + paramid, ', '.join(argtypes),)) + params.append('') + return "\n".join(params) + def generate_code(self): + parsercode = self.generate_responses_code() + cmdcode = self.generate_commands_code() + paramcode = self.generate_param_code() + return paramcode + parsercode + cmdcode + +Handlers.append(HandleCommandGeneration()) ###################################################################### # Identify data dictionary generation ###################################################################### -def build_identify(cmd_by_id, msg_to_id, responses, version, toolstr): - #commands, messages - messages = dict((msgid, msg) for msg, msgid in msg_to_id.items()) +def build_identify(version, toolstr): data = {} for h in Handlers: h.update_data_dictionary(data) - data['messages'] = messages - data['commands'] = sorted(cmd_by_id.keys()) - data['responses'] = sorted(responses) data['version'] = version data['build_versions'] = toolstr @@ -371,11 +430,6 @@ def main(): if options.verbose: logging.basicConfig(level=logging.DEBUG) - # Setup - commands = {} - messages_by_name = dict((m.split()[0], m) - for m in msgproto.DefaultMessages.values()) - encoders = [] # Parse request file ctr_dispatch = { k: v for h in Handlers for k, v in h.ctr_dispatch.items() } f = open(incmdfile, 'rb') @@ -383,62 +437,21 @@ def main(): f.close() for req in data.split('\0'): req = req.lstrip() - parts = req.split() - if not parts: + if not req: continue - cmd = parts[0] - msg = req[len(cmd)+1:] - if cmd in ctr_dispatch: - ctr_dispatch[cmd](req) - elif cmd == '_DECL_COMMAND': - funcname, flags, msgname = parts[1:4] - if msgname in commands: - error("Multiple definitions for command '%s'" % msgname) - commands[msgname] = (funcname, flags, msgname) - msg = req.split(None, 3)[3] - m = messages_by_name.get(msgname) - if m is not None and m != msg: - error("Conflicting definition for command '%s'" % msgname) - messages_by_name[msgname] = msg - elif cmd == '_DECL_ENCODER': - msgname = parts[1] - m = messages_by_name.get(msgname) - if m is not None and m != msg: - error("Conflicting definition for message '%s'" % msgname) - messages_by_name[msgname] = msg - encoders.append((msgname, msg)) - elif cmd == '_DECL_OUTPUT': - encoders.append((None, msg)) - else: + cmd = req.split()[0] + if cmd not in ctr_dispatch: error("Unknown build time command '%s'" % cmd) - # Create unique ids for each message type - msgid = max(msgproto.DefaultMessages.keys()) - msg_to_id = dict((m, i) for i, m in msgproto.DefaultMessages.items()) - for msgname in commands.keys() + [m for n, m in encoders]: - msg = messages_by_name.get(msgname, msgname) - if msg not in msg_to_id: - msgid += 1 - msg_to_id[msg] = msgid - # Create message definitions - all_param_types = {} - parsercode = build_encoders(encoders, msg_to_id, all_param_types) - # Create command definitions - cmd_by_id = dict((msg_to_id[messages_by_name.get(msgname, msgname)], cmd) - for msgname, cmd in commands.items()) - cmdcode = build_commands(cmd_by_id, messages_by_name, all_param_types) - paramcode = build_param_types(all_param_types) + ctr_dispatch[cmd](req) # Create identify information cleanbuild, toolstr = tool_versions(options.tools) version = build_version(options.extra) sys.stdout.write("Version: %s\n" % (version,)) - responses = [msg_to_id[msg] for msgname, msg in messages_by_name.items() - if msgname not in commands] - datadict, icode = build_identify( - cmd_by_id, msg_to_id, responses, version, toolstr) + datadict, icode = build_identify(version, toolstr) # Write output f = open(outcfile, 'wb') f.write(FILEHEADER + "".join([h.generate_code() for h in Handlers]) - + paramcode + parsercode + cmdcode + icode) + + icode) f.close() # Write data dictionary