Files
openttd-cmclient/gen_commands.py
T
2022-12-02 19:55:21 +04:00

138 lines
4.6 KiB
Python

import re
from pathlib import Path
from pprint import pprint
RX_COMMAND = re.compile(r'(?P<returns>CommandCost|std::tuple<CommandCost, [^>]*>) (?P<name>Cmd\w*)\((?P<args>[^)]*)\);')
RX_DEF_TRAIT = re.compile(r'DEF_CMD_TRAIT\((?P<constant>\w+),\s+(?P<function>\w+),\s+[^,]*,\s+(?P<category>\w+)\)')
RX_ARG = re.compile(r'(?P<type>(:?const |)[\w:]* &?)(?P<name>\w*)')
RX_CAMEL_TO_SNAKE = re.compile(r'(?<!^)(?=[A-Z])')
FILES = [
'src/misc_cmd.h',
'src/object_cmd.h',
'src/order_cmd.h',
'src/rail_cmd.h',
'src/road_cmd.h',
'src/station_cmd.h',
'src/town_cmd.h',
'src/tunnelbridge_cmd.h',
]
BASE_DIR = Path(__file__).parent
OUTPUT = BASE_DIR / 'src/citymania/generated/cm_gen_commands'
def parse_commands():
res = []
for f in FILES:
data = open(BASE_DIR / f).read()
traits = {}
for constant, name, category in RX_DEF_TRAIT.findall(data):
traits[name] = constant
for returns, name, args_str in RX_COMMAND.findall(data):
if returns.startswith('std::tuple'):
ret_type = returns[24: -1]
else:
ret_type = None
args = [RX_ARG.fullmatch(x).group('type', 'name') for x in args_str.split(', ')]
args = args[1:] # flags
first_tile_arg = (args[0][0].strip() == 'TileIndex')
if first_tile_arg:
args = args[1:]
print(name, traits[name], args)
res.append({
'name': name[3:],
'constant': traits[name],
'args': args,
'first_tile_arg': first_tile_arg,
'returns': ret_type,
})
return res
def run():
commands = 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'
'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'
f' {name}({args_list})\n'
f' :{args_init} {{}}\n'
)
if cmd.get('first_tile_arg'):
f.write(
f' {name}(TileIndex tile, {args_list})\n'
f' :Command{{tile}}, {args_init} {{}}\n'
)
f.write(
f' ~{name}() override {{}}\n'
f'\n'
f' bool DoPost() override;\n'
f' bool DoTest() 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'
)
for cmd in commands:
name = cmd['name']
constant = cmd['constant']
# constant = 'CMD_' + RX_CAMEL_TO_SNAKE.sub('_', name).upper()
args_list = ', '.join(f'this->{an}' for _, an in cmd['args'])
test_args_list = args_list
if cmd.get('first_tile_arg'):
test_args_list = f'this->tile, ' + args_list
cost_getter = '' if cmd['returns'] is None else 'std::get<0>'
f.write(
f'bool {name}::DoPost() {{\n'
f' return ::Command<{constant}>::Post(this->error, this->tile, {args_list});\n'
'}\n'
)
f.write(
f'bool {name}::DoTest() {{\n'
f' return {cost_getter}(::Command<{constant}>::Do(DC_NONE, {test_args_list})).Succeeded();\n'
'}\n'
)
f.write('\n')
f.write(
'} // namespace cmd\n'
'} // namespace citymania\n'
)
if __name__ == "__main__":
run()