399
tools/cpp_to_python_converter.py
Normal file
399
tools/cpp_to_python_converter.py
Normal file
@@ -0,0 +1,399 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert C++ game code (int main / void functions) to Python style."""
|
||||
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def _remove_comments(code: str) -> str:
|
||||
return re.sub(r'//[^\n]*', '', code)
|
||||
|
||||
|
||||
def _find_matching_brace(code: str, open_pos: int) -> int:
|
||||
depth = 0
|
||||
for i in range(open_pos, len(code)):
|
||||
if code[i] == '{':
|
||||
depth += 1
|
||||
elif code[i] == '}':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return i
|
||||
return -1
|
||||
|
||||
|
||||
def _find_matching_paren(code: str, open_pos: int) -> int:
|
||||
depth = 0
|
||||
for i in range(open_pos, len(code)):
|
||||
if code[i] == '(':
|
||||
depth += 1
|
||||
elif code[i] == ')':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return i
|
||||
return -1
|
||||
|
||||
|
||||
def _parse_for_header(header: str) -> Optional[str]:
|
||||
header = re.sub(r'\s+', ' ', header.strip())
|
||||
|
||||
m = re.match(
|
||||
r'for\s*\(\s*int\s+(\w+)\s*=\s*([^;]+);\s*(\w+)\s*([<>=!]+)\s*([^;]+);\s*(\w+)\+\+\s*\)',
|
||||
header,
|
||||
)
|
||||
if m:
|
||||
var, start, _, op, end, _ = m.groups()
|
||||
return _range_for(var, start.strip(), op, end.strip(), 1)
|
||||
|
||||
m = re.match(
|
||||
r'for\s*\(\s*int\s+(\w+)\s*=\s*([^;]+);\s*(\w+)\s*([<>=!]+)\s*([^;]+);\s*(\w+)\s*=\s*(\w+)\s*([+\-])\s*(\d+)\s*\)',
|
||||
header,
|
||||
)
|
||||
if m:
|
||||
var, start, _, op, end, _, _, sign, step = m.groups()
|
||||
step_n = int(step) if sign == '+' else -int(step)
|
||||
return _range_for(var, start.strip(), op, end.strip(), step_n)
|
||||
|
||||
m = re.match(
|
||||
r'for\s*\(\s*int\s+(\w+)\s*=\s*([^;]+);\s*(\w+)\s*([<>=!]+)\s*([^;]+);\s*(\w+)\+=\s*(\d+)\s*\)',
|
||||
header,
|
||||
)
|
||||
if m:
|
||||
var, start, _, op, end, _, step = m.groups()
|
||||
return _range_for(var, start.strip(), op, end.strip(), int(step))
|
||||
|
||||
m = re.match(
|
||||
r'for\s*\(\s*int\s+(\w+)\s*=\s*([^;]+);\s*(\w+)\s*([<>=!]+)\s*([^;]+);\s*(\w+)-=\s*(\d+)\s*\)',
|
||||
header,
|
||||
)
|
||||
if m:
|
||||
var, start, _, op, end, _, step = m.groups()
|
||||
return _range_for(var, start.strip(), op, end.strip(), -int(step))
|
||||
|
||||
m = re.match(
|
||||
r'for\s*\(\s*int\s+(\w+)\s*=\s*([^;]+);\s*(\w+)\s*([<>=!]+)\s*([^;]+);\s*(\w+)\s*=\s*(\w+)\s*-\s*(\d+)\s*\)',
|
||||
header,
|
||||
)
|
||||
if m:
|
||||
var, start, _, op, end, _, _, step = m.groups()
|
||||
return _range_for(var, start.strip(), op, end.strip(), -int(step))
|
||||
|
||||
m = re.match(
|
||||
r'for\s*\(\s*int\s+(\w+)\s*=\s*([^;]+);\s*(\w+)\s*([<>=!]+)\s*([^;]+);\s*(\w+)--\s*\)',
|
||||
header,
|
||||
)
|
||||
if m:
|
||||
var, start, _, op, end, _ = m.groups()
|
||||
return _range_for(var, start.strip(), op, end.strip(), -1)
|
||||
|
||||
m = re.match(
|
||||
r'for\s*\(\s*int\s+(\w+)\s*=\s*([^;]+);\s*(\w+)\s*([<>=!]+)\s*([^;]+);\s*(\w+)\s*\*=\s*(\d+)\s*\)',
|
||||
header,
|
||||
)
|
||||
if m:
|
||||
var, start, _, op, end, _, mult = m.groups()
|
||||
return f'WHILE|{var}|{start.strip()}|{op}|{end.strip()}|{var} *= {mult}'
|
||||
|
||||
m = re.match(
|
||||
r'for\s*\(\s*int\s+(\w+)\s*=\s*([^;]+);\s*(\w+)\s*([<>=!]+)\s*([^;]+);\s*(\w+)\s*=\s*\1\s*\*\s*(\d+)\s*\)',
|
||||
header,
|
||||
)
|
||||
if m:
|
||||
var, start, _, op, end, _, mult = m.groups()
|
||||
return f'WHILE|{var}|{start.strip()}|{op}|{end.strip()}|{var} *= {mult}'
|
||||
|
||||
m = re.match(
|
||||
r'for\s*\(\s*int\s+(\w+)\s*=\s*([^;]+);\s*(\w+)\s*([<>=!]+)\s*([^;]+);\s*(\w+)\s*/=\s*(\d+)\s*\)',
|
||||
header,
|
||||
)
|
||||
if m:
|
||||
var, start, _, op, end, _, div = m.groups()
|
||||
return f'WHILE|{var}|{start.strip()}|{op}|{end.strip()}|{var} //= {div}'
|
||||
|
||||
m = re.match(
|
||||
r'for\s*\(\s*int\s+(\w+)\s*=\s*([^;]+);\s*(\w+)\s*([<>=!]+)\s*([^;]+);\s*(\w+)\s*=\s*\1\s*/\s*(\d+)\s*\)',
|
||||
header,
|
||||
)
|
||||
if m:
|
||||
var, start, _, op, end, _, div = m.groups()
|
||||
return f'WHILE|{var}|{start.strip()}|{op}|{end.strip()}|{var} //= {div}'
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _add_one(expr: str) -> str:
|
||||
expr = expr.strip()
|
||||
if re.fullmatch(r'-?\d+', expr):
|
||||
return str(int(expr) + 1)
|
||||
return f'{expr} + 1'
|
||||
|
||||
|
||||
def _sub_one(expr: str) -> str:
|
||||
expr = expr.strip()
|
||||
if re.fullmatch(r'-?\d+', expr):
|
||||
return str(int(expr) - 1)
|
||||
return f'{expr} - 1'
|
||||
|
||||
|
||||
def _range_for(var: str, start: str, op: str, end: str, step: int) -> str:
|
||||
if step == 1:
|
||||
if op == '<':
|
||||
if start == '0':
|
||||
return f'for {var} in range({end}):'
|
||||
return f'for {var} in range({start}, {end}):'
|
||||
if op == '<=':
|
||||
return f'for {var} in range({start}, {_add_one(end)}):'
|
||||
if step == -1:
|
||||
if op == '>':
|
||||
return f'for {var} in range({start}, {end}, -1):'
|
||||
if op == '>=':
|
||||
return f'for {var} in range({start}, {_sub_one(end)}, -1):'
|
||||
if step > 1 and op == '<':
|
||||
return f'for {var} in range({start}, {end}, {step}):'
|
||||
if step < -1:
|
||||
if op == '>=':
|
||||
return f'for {var} in range({start}, {_sub_one(end)}, {step}):'
|
||||
if op == '>':
|
||||
return f'for {var} in range({start}, {end}, {step}):'
|
||||
return f'# UNCONVERTED FOR: {var} {start} {op} {end} step={step}'
|
||||
|
||||
|
||||
def _split_args(s: str) -> list[str]:
|
||||
parts, cur, depth = [], [], 0
|
||||
for c in s:
|
||||
if c in '([':
|
||||
depth += 1
|
||||
elif c in ')]':
|
||||
depth -= 1
|
||||
elif c == ',' and depth == 0:
|
||||
parts.append(''.join(cur).strip())
|
||||
cur = []
|
||||
continue
|
||||
cur.append(c)
|
||||
if cur:
|
||||
parts.append(''.join(cur).strip())
|
||||
return parts
|
||||
|
||||
|
||||
def _format_rhs(expr: str) -> str:
|
||||
expr = expr.strip().rstrip(';')
|
||||
m = re.match(r'(\w+)\((.*)\)$', expr)
|
||||
if m:
|
||||
fn, args = m.group(1), m.group(2)
|
||||
if not args.strip():
|
||||
return f'{fn}()'
|
||||
parts = _split_args(args)
|
||||
return f'{fn}({", ".join(_format_rhs(p) for p in parts)})'
|
||||
|
||||
expr = re.sub(r'\s+', '', expr)
|
||||
tokens = re.split(r'(==|!=|<=|>=|[+\-*/=<>])', expr)
|
||||
parts = []
|
||||
for t in tokens:
|
||||
if not t:
|
||||
continue
|
||||
if t in '+-*/=<>!':
|
||||
parts.append(f' {t} ')
|
||||
elif t in ('==', '!=', '<=', '>='):
|
||||
parts.append(f' {t} ')
|
||||
else:
|
||||
parts.append(t)
|
||||
result = ''.join(parts)
|
||||
result = re.sub(r'\s+', ' ', result).strip()
|
||||
result = re.sub(r'^-\s+(\d)', r'-\1', result)
|
||||
result = re.sub(r'-\s+(\w)', r'-\1', result)
|
||||
result = re.sub(r'\(\s*-\s+(\w)', r'(-\1', result)
|
||||
return result
|
||||
|
||||
|
||||
def _format_stmt(stmt: str) -> str:
|
||||
stmt = stmt.strip().rstrip(';')
|
||||
m = re.match(r'int\s+(\w+)\s*=\s*(.+)', stmt)
|
||||
if m:
|
||||
return f'{m.group(1)} = {_format_rhs(m.group(2))}'
|
||||
m = re.match(r'(\w+)\s*=\s*(.+)', stmt)
|
||||
if m and not stmt.startswith(('if ', 'for ', 'void ', 'return')):
|
||||
return f'{m.group(1)} = {_format_rhs(m.group(2))}'
|
||||
return _format_rhs(stmt)
|
||||
|
||||
|
||||
def _skip_ws(code: str) -> str:
|
||||
return code.lstrip(' \t\n\r')
|
||||
|
||||
|
||||
def _convert_block(code: str, indent: int = 0) -> list[str]:
|
||||
lines: list[str] = []
|
||||
code = code.strip()
|
||||
pad = ' ' * indent
|
||||
|
||||
while code:
|
||||
code = _skip_ws(code)
|
||||
if not code:
|
||||
break
|
||||
|
||||
# void function
|
||||
m = re.match(r'void\s+(\w+)\s*\(([^)]*)\)\s*\{', code)
|
||||
if m:
|
||||
fn, params = m.group(1), m.group(2).strip()
|
||||
if params:
|
||||
names = [p.strip().split()[-1] for p in params.split(',') if p.strip()]
|
||||
lines.append(f'def {fn}({", ".join(names)}):')
|
||||
else:
|
||||
lines.append(f'def {fn}():')
|
||||
ob = code.index('{', m.start())
|
||||
cb = _find_matching_brace(code, ob)
|
||||
lines.extend(_convert_block(code[ob + 1:cb], indent + 1))
|
||||
lines.append('')
|
||||
code = code[cb + 1:]
|
||||
continue
|
||||
|
||||
# int main()
|
||||
m = re.match(r'int\s+main\s*\(\s*\)\s*\{', code)
|
||||
if m:
|
||||
ob = code.index('{', m.start())
|
||||
cb = _find_matching_brace(code, ob)
|
||||
lines.extend(_convert_block(code[ob + 1:cb], indent))
|
||||
code = code[cb + 1:]
|
||||
continue
|
||||
|
||||
# for loop
|
||||
m = re.match(r'for\s*\(', code)
|
||||
if m:
|
||||
cp = _find_matching_paren(code, m.end() - 1)
|
||||
header = code[m.start():cp + 1]
|
||||
rest = _skip_ws(code[cp + 1:])
|
||||
if rest.startswith('{'):
|
||||
cb = _find_matching_brace(rest, 0)
|
||||
body = rest[1:cb]
|
||||
py_for = _parse_for_header(header)
|
||||
if py_for and py_for.startswith('WHILE|'):
|
||||
_, var, start, op, end, inc = py_for.split('|')
|
||||
lines.append(pad + f'{var} = {start}')
|
||||
lines.append(pad + f'while {var} {op} {end}:')
|
||||
lines.extend(_convert_block(body, indent + 1))
|
||||
lines.append(pad + f' {inc}')
|
||||
elif py_for:
|
||||
lines.append(pad + py_for)
|
||||
lines.extend(_convert_block(body, indent + 1))
|
||||
else:
|
||||
lines.append(pad + (py_for or f'# {header}'))
|
||||
lines.extend(_convert_block(body, indent + 1))
|
||||
code = rest[cb + 1:]
|
||||
continue
|
||||
|
||||
# if / else if / else
|
||||
m = re.match(r'if\s*\(', code)
|
||||
if m:
|
||||
cp = _find_matching_paren(code, m.end() - 1)
|
||||
cond = code[m.end():cp].strip()
|
||||
rest = _skip_ws(code[cp + 1:])
|
||||
if rest.startswith('{'):
|
||||
cb = _find_matching_brace(rest, 0)
|
||||
if_body = rest[1:cb]
|
||||
lines.append(pad + f'if {_format_rhs(cond)}:')
|
||||
lines.extend(_convert_block(if_body, indent + 1))
|
||||
code = _skip_ws(rest[cb + 1:])
|
||||
if code.startswith('else if'):
|
||||
sub_lines, code = _convert_if_chain(code, indent, is_elif=True)
|
||||
lines.extend(sub_lines)
|
||||
elif code.startswith('else'):
|
||||
if code.startswith('else {'):
|
||||
ecb = _find_matching_brace(code, code.index('{'))
|
||||
lines.append(pad + 'else:')
|
||||
lines.extend(_convert_block(code[code.index('{') + 1:ecb], indent + 1))
|
||||
code = code[ecb + 1:]
|
||||
elif code.startswith('else if'):
|
||||
pass # handled above
|
||||
continue
|
||||
|
||||
# return
|
||||
m = re.match(r'return\s+0\s*;', code)
|
||||
if m:
|
||||
code = code[m.end():]
|
||||
continue
|
||||
|
||||
# statement until ;
|
||||
semi = -1
|
||||
depth = 0
|
||||
for j, c in enumerate(code):
|
||||
if c in '{(':
|
||||
depth += 1
|
||||
elif c in '})':
|
||||
depth -= 1
|
||||
elif c == ';' and depth == 0:
|
||||
semi = j
|
||||
break
|
||||
|
||||
if semi >= 0:
|
||||
stmt = code[:semi].strip()
|
||||
if stmt and not re.match(r'return\s+0', stmt):
|
||||
lines.append(pad + _format_stmt(stmt))
|
||||
code = code[semi + 1:]
|
||||
continue
|
||||
|
||||
# trailing statement without semicolon
|
||||
stmt = code.strip()
|
||||
if stmt and not re.match(r'return\s+0', stmt):
|
||||
lines.append(pad + _format_stmt(stmt))
|
||||
break
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def _convert_if_chain(code: str, indent: int, is_elif: bool = False) -> tuple[list[str], str]:
|
||||
lines: list[str] = []
|
||||
pad = ' ' * indent
|
||||
prefix = 'elif' if is_elif else 'if'
|
||||
|
||||
while True:
|
||||
code = _skip_ws(code)
|
||||
if code.startswith('else if'):
|
||||
code = code[7:].lstrip()
|
||||
prefix = 'elif'
|
||||
elif code.startswith('if'):
|
||||
code = code[2:].lstrip()
|
||||
elif code.startswith('else'):
|
||||
if code.startswith('else {'):
|
||||
cb = _find_matching_brace(code, code.index('{'))
|
||||
lines.append(pad + 'else:')
|
||||
lines.extend(_convert_block(code[code.index('{') + 1:cb], indent + 1))
|
||||
return lines, code[cb + 1:]
|
||||
break
|
||||
else:
|
||||
break
|
||||
|
||||
if not code.startswith('('):
|
||||
break
|
||||
cp = _find_matching_paren(code, 0)
|
||||
cond = code[1:cp].strip()
|
||||
rest = _skip_ws(code[cp + 1:])
|
||||
if not rest.startswith('{'):
|
||||
break
|
||||
cb = _find_matching_brace(rest, 0)
|
||||
lines.append(pad + f'{prefix} {_format_rhs(cond)}:')
|
||||
lines.extend(_convert_block(rest[1:cb], indent + 1))
|
||||
code = _skip_ws(rest[cb + 1:])
|
||||
if code.startswith('else if'):
|
||||
prefix = 'elif'
|
||||
code = code[4:].lstrip() # 'else if' -> ' if' wait
|
||||
code = 'if' + code[2:] if code.startswith(' if') else code
|
||||
continue
|
||||
if code.startswith('else {'):
|
||||
cb2 = _find_matching_brace(code, code.index('{'))
|
||||
lines.append(pad + 'else:')
|
||||
lines.extend(_convert_block(code[code.index('{') + 1:cb2], indent + 1))
|
||||
return lines, code[cb2 + 1:]
|
||||
return lines, code
|
||||
|
||||
return lines, code
|
||||
|
||||
|
||||
def cpp_to_python(cpp_code: str) -> str:
|
||||
if not cpp_code or not str(cpp_code).strip():
|
||||
return ''
|
||||
code = _remove_comments(str(cpp_code))
|
||||
lines = _convert_block(code, 0)
|
||||
return '\n'.join(lines).strip()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
print(cpp_to_python(sys.stdin.read()))
|
||||
Reference in New Issue
Block a user