import glob import re from pathlib import Path from pprint import pprint RX_COMMAND = re.compile(r'(?PCommandCost|std::tuple]*>) (?PCmd\w*)\((?P[^)]*)\);') RX_DEF_TRAIT = re.compile(r'DEF_CMD_TRAIT\((?P\w+),\s+(?P\w+),\s+[^,]*,\s+(?P\w+)\)') RX_ARG = re.compile(r'(?P(:?const |)[\w:]* &?)(?P\w*)') RX_CALLBACK = re.compile(r'void\s+(?PCc\w+)\(Commands') RX_CALLBACK_REF = re.compile(r'CommandCallback\s+(?PCc\w+);') RX_CAMEL_TO_SNAKE = re.compile(r'(?; #ifdef SILENCE_GCC_FUNCTION_POINTER_CAST # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wcast-function-type" #endif template inline auto MakeCallbackTable(std::index_sequence) noexcept { return std::array<::CommandCallback *, sizeof...(i)>{{ reinterpret_cast<::CommandCallback *>(reinterpret_cast(std::get(_callback_tuple)))... }}; // MingW64 fails linking when casting a pointer to its own type. To work around, cast it to some other type first. } /** Type-erased table of callbacks. */ static auto _callback_table = MakeCallbackTable(std::make_index_sequence<_callback_tuple_size>{});\n template struct CallbackArgsHelper; template struct CallbackArgsHelper { using Args = std::tuple...>; }; #ifdef SILENCE_GCC_FUNCTION_POINTER_CAST # pragma GCC diagnostic pop #endif static size_t FindCallbackIndex(::CommandCallback *callback) { if (auto it = std::find(std::cbegin(_callback_table), std::cend(_callback_table), callback); it != std::cend(_callback_table)) { return static_cast(std::distance(std::cbegin(_callback_table), it)); } return std::numeric_limits::max(); } template bool _DoPost(StringID err_msg, TileIndex tile, Targs... args) { return ::Command::Post(err_msg, std::get(_callback_tuple), tile, std::forward(args)...); } template constexpr auto MakeCallback() noexcept { /* Check if the callback matches with the command arguments. If not, don''t generate an Unpack proc. */ using Tcallback = std::tuple_element_t; if constexpr (std::is_same_v || std::is_same_v || std::is_same_v::CbArgs, typename CallbackArgsHelper::Args> || (!std::is_void_v::RetTypes> && std::is_same_v::RetCallbackProc const>::Args, typename CallbackArgsHelper::Args>)) { return &_DoPost; } else { return nullptr; } } template inline constexpr auto MakeDispatchTableHelper(std::index_sequence) noexcept { return std::array{MakeCallback()... }; } template inline constexpr auto MakeDispatchTable() noexcept { return MakeDispatchTableHelper(std::make_index_sequence<_callback_tuple_size>{}); } ''' def run(): commands, includes, callbacks = parse_commands() with open(OUTPUT.with_suffix('.hpp'), 'w') as f: f.write( '// This file is generated by gen_commands.py, do not edit\n\n' '#ifndef CM_GEN_COMMANDS_HPP\n' '#define CM_GEN_COMMANDS_HPP\n' '#include "../cm_command_type.hpp"\n' ) for i in includes: f.write(f'#include "../../{i}"\n') f.write('\n') f.write( 'namespace citymania {\n' 'namespace cmd {\n\n' ) for cmd in commands: name = cmd['name'] args_list = ', '.join(f'{at}{an}' for at, an in cmd['args']) args_init = ', '.join(f'{an}{{{an}}}' for _, an in cmd['args']) f.write( f'class {name}: public Command {{\n' f'public:\n' ) for at, an in cmd['args']: f.write(f' {at}{an};\n') f.write(f'\n') if args_init: f.write( f' {name}({args_list})\n' f' :{args_init} {{}}\n' ) else: f.write(f' {name}({args_list}) {{}}\n') if cmd.get('first_tile_arg'): separator = ', ' if args_list else '' f.write( f' {name}(TileIndex tile{separator}{args_list})\n' f' :Command{{tile}}{separator}{args_init} {{}}\n' ) f.write( f' ~{name}() override {{}}\n' f'\n' f' bool _post(::CommandCallback * callback) override;\n' f' CommandCost _do(DoCommandFlag flags) override;\n' f' Commands get_command() override;\n' f'}};\n\n' ) f.write( '} // namespace cmd\n' '} // namespace citymania\n' '#endif\n' ) with open(OUTPUT.with_suffix('.cpp'), 'w') as f: f.write( '// This file is generated by gen_commands.py, do not edit\n\n' '#include "../../stdafx.h"\n' '#include "cm_gen_commands.hpp"\n' ) for fn in FILES: f.write(f'#include "../../{fn}"\n') f.write( 'namespace citymania {\n' 'namespace cmd {\n\n' ) f.write( '/*\n' ' * The code is mostly copied from network_command.cpp\n' ' * but the table is not the same.\n' ' */\n' 'static constexpr auto _callback_tuple = std::make_tuple(\n' ' (::CommandCallback *)nullptr, // Make sure this is actually a pointer-to-function.\n' ) for i, cb in enumerate(callbacks): comma = ',' if i != len(callbacks) - 1 else '' f.write(f' &{cb}{comma}\n') f.write(');\n\n') f.write(CPP_TEMPLATES) for cmd in commands: name = cmd['name'] constant = cmd['constant'] this_args_list = ', '.join(f'this->{an}' for _, an in cmd['args']) args_list = ', '.join(f'{an}' for _, an in cmd['args']) args_type_list = ', '.join(f'{at}' for at, an in cmd['args']) test_args_list = args_list if cmd.get('first_tile_arg'): if args_list: test_args_list = f'this->tile, ' + args_list else: test_args_list = f'this->tile' cost_getter = '' if cmd['returns'] is None else 'std::get<0>' sep_args_list = sep_args_type_list = sep_this_args_list = '' if args_list: sep_args_list = ', ' + args_list sep_args_type_list = ', ' + args_type_list sep_this_args_list = ', ' + this_args_list f.write( f'Commands {name}::get_command() {{ return {constant}; }}\n' f'static constexpr auto _{name}_dispatch = MakeDispatchTable<{constant}{sep_args_type_list}>();\n' f'bool {name}::_post(::CommandCallback *callback) {{\n' f' return _{name}_dispatch[FindCallbackIndex(callback)](this->error, this->tile{sep_this_args_list});\n' '}\n' f'CommandCost {name}::_do(DoCommandFlag flags) {{\n' f' return {cost_getter}(::Command<{constant}>::Do(flags, {test_args_list}));\n' '}\n' ) f.write('\n') f.write( '} // namespace cmd\n' '} // namespace citymania\n' ) if __name__ == "__main__": run()