mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-29 15:41:59 +00:00
336 lines
12 KiB
Python
336 lines
12 KiB
Python
|
import re
|
||
|
import sys
|
||
|
from pprint import pprint
|
||
|
from io import StringIO
|
||
|
from textwrap import TextWrapper
|
||
|
import os
|
||
|
|
||
|
STRING = r'\s*"((\\.|[^"\\])*)"\s*'
|
||
|
QSTRING = r'\s*("(\\.|[^"\\])*")\s*'
|
||
|
ID = r'([a-zA-Z_][a-zA-Z_0-9]*)'
|
||
|
STORE = r'(VISIBLE\s+|static\s+|extern\s+|)'
|
||
|
FIELD = r'(string|value|int_val|vec)\b'
|
||
|
FLAGS = r'\s*(' + ID + r'\s*(\|\s*' + ID + r')*)\s*'
|
||
|
TYPE = r'(cvar_t|struct cvar_s)\s*\*'
|
||
|
STUFF = r'\s*(.*)\s*'
|
||
|
cvar_decl_re = re.compile(r'^' + STORE + TYPE + ID + r';.*')
|
||
|
cvar_get_re = re.compile(r'.*\bCvar_Get\s*\(\s*"')
|
||
|
cvar_get_join_prev_re = re.compile(r'^\s*Cvar_Get\s*\(\s*".*')
|
||
|
cvar_get_assign_re = re.compile(r'\s*' + ID + r'\s*=\s*$')
|
||
|
cvar_get_incomplete_re = re.compile(r'.*Cvar_Get\s*\(\s*"[^;]*$')
|
||
|
cvar_get_complete_re = re.compile(r'.*Cvar_Get\s*\(\s*".*\);.*$')
|
||
|
cvar_create_re = re.compile(r'\s*(\w+)\s*=\s*Cvar_Get\s*\('
|
||
|
+ STRING
|
||
|
+ ',(' + STRING + '|\s*(.*)\s*),' + FLAGS + r',\s*([^,]+),\s*' + STUFF + r'\);.*')
|
||
|
cvar_use_re = re.compile(ID + r'\s*\->\s*' + FIELD)
|
||
|
cvar_listener_re = re.compile(ID + r'\s*\(\s*cvar_t\s*\*' + ID + '\s*\)\s*')
|
||
|
string_or_id_re = re.compile(f'({ID}|{QSTRING})')
|
||
|
cvar_setrom_re = re.compile(r'\s*Cvar_SetFlags\s*\(\s*' + ID + '.*CVAR_ROM\);.*')
|
||
|
id_re = re.compile(f'\\b{ID}\\b')
|
||
|
|
||
|
#cvar_decl_re = re.compile(r'^cvar_t\s+(\w+)\s*=\s*\{\s*("[^"]*")\s*,\s*("[^"]*")(\s*,\s*(\w+)(\s*,\s*(\w+))?)?\s*\}\s*;(\s*//\s*(.*))?\n')
|
||
|
cvar_set_re = re.compile(r'^(\s+)(Cvar_Set|Cvar_SetValue)\s*\(' + ID + ',\s*(.*)\);(.*)')
|
||
|
|
||
|
wrapper = TextWrapper(width = 69, drop_whitespace = False,
|
||
|
break_long_words = False)
|
||
|
|
||
|
class cvar:
|
||
|
def __init__(self, c_name, name, default, flags, callback, description):
|
||
|
self.c_name = c_name
|
||
|
self.name = name
|
||
|
self.default = default
|
||
|
self.flags = flags
|
||
|
if callback in ['NULL', '0']:
|
||
|
callback = 0
|
||
|
self.callback = callback
|
||
|
if description in ['NULL', '0', '']:
|
||
|
description = 'FIXME no description'
|
||
|
self.desc = description
|
||
|
if self.desc==None:
|
||
|
self.desc = 'None'
|
||
|
self.data = 0
|
||
|
self.files = []
|
||
|
self.type = None
|
||
|
self.c_type = None
|
||
|
self.used_field = None
|
||
|
self.uses = []
|
||
|
def guess_type(self):
|
||
|
if self.used_field == "value":
|
||
|
self.type = "&cexpr_float"
|
||
|
self.c_type = "float "
|
||
|
elif self.used_field == "int_val":
|
||
|
self.type = "&cexpr_int"
|
||
|
self.c_type = "int "
|
||
|
elif self.used_field == "vec":
|
||
|
self.type = "&cexpr_vector"
|
||
|
self.c_type = "vec4f_t "
|
||
|
elif self.used_field == "string":
|
||
|
self.type = 0
|
||
|
self.c_type = "char *"
|
||
|
else:
|
||
|
self.type = "0/* not used */"
|
||
|
self.c_type = "char *"
|
||
|
def add_file(self, file, line):
|
||
|
self.files.append((file, line))
|
||
|
def add_use(self, field, file, line):
|
||
|
if self.used_field and self.used_field != field:
|
||
|
print(f"{file}:{line}: cvar {self.name} used inconsistently")
|
||
|
self.used_field = field
|
||
|
self.uses.append((field, file, line))
|
||
|
def __repr__(self):
|
||
|
return f"cvar(({self.c_name}, {self.name}, {self.default}, {self.flags}, {self.callback}, {self.desc})"
|
||
|
def __str__(self):
|
||
|
return self.__repr__()
|
||
|
def struct(self):
|
||
|
A = [
|
||
|
f'static cvar_t {self.c_name}_cvar = {{\n',
|
||
|
f'\t.name = "{self.name}",\n',
|
||
|
f'\t.description =\n',
|
||
|
]
|
||
|
B = [
|
||
|
f'\t.default_value = "{self.default}",\n',
|
||
|
f'\t.flags = {self.flags},\n',
|
||
|
f'\t.value = {{ .type = {self.type}, .value = &{self.c_name} }},\n',
|
||
|
f'}};\n',
|
||
|
]
|
||
|
desc = []
|
||
|
dstrings = [m[0].strip() for m in string_or_id_re.findall(self.desc)]
|
||
|
def wrap(string, comma = ""):
|
||
|
if string[0] == '"' and string != '""':
|
||
|
string = string[1:-1]
|
||
|
dlines = wrapper.wrap(string)
|
||
|
desc.extend([f'\t\t"{l}"\n' for l in dlines[:-1]])
|
||
|
desc.append(f'\t\t"{dlines[-1]}"{comma}\n')
|
||
|
else:
|
||
|
desc.append(f'\t\t{string}{comma}\n')
|
||
|
for dstring in dstrings[:-1]:
|
||
|
wrap (dstring)
|
||
|
wrap(dstrings[-1], ",")
|
||
|
return A + desc + B
|
||
|
def var(self, storage):
|
||
|
if storage:
|
||
|
storage += " "
|
||
|
return [f'{storage}{self.c_type}{self.c_name};\n']
|
||
|
def set_rom(self):
|
||
|
if self.flags == 'CVAR_NONE':
|
||
|
self.flags = 'CVAR_ROM'
|
||
|
else:
|
||
|
self.flags = '|'.join([self.flags, 'CVAR_ROM'])
|
||
|
def register(self):
|
||
|
return [f'\tCvar_Register (&{self.c_name}_cvar, {self.callback}, {self.data});\n']
|
||
|
|
||
|
|
||
|
cvar_decls = {}
|
||
|
cvar_dict = {}
|
||
|
cvar_sets = []
|
||
|
cvar_rom = set()
|
||
|
cvar_listeners = {}
|
||
|
cvar_listeners_done = set()
|
||
|
cvar_listeners_multi = set()
|
||
|
files = {}
|
||
|
modified = set()
|
||
|
|
||
|
cvar_struct = [
|
||
|
"\tconst char *name;\n",
|
||
|
"\tconst char *description;\n",
|
||
|
"\tconst char *default_value;\n",
|
||
|
"\tunsigned flags;\n",
|
||
|
"\texprval_t value;\n",
|
||
|
"\tint (*validator) (const struct cvar_s *var);\n"
|
||
|
"\tstruct cvar_listener_set_s *listeners;\n"
|
||
|
"\tstruct cvar_s *next;\n"
|
||
|
]
|
||
|
cvar_reg = "void Cvar_Register (cvar_t *var, cvar_listener_t listener, void *data);\n"
|
||
|
cvar_cexpr = '#include "QF/cexpr.h"\n'
|
||
|
cvar_set = 'void Cvar_Set (const char *var, const char *value);\n'
|
||
|
cvar_setvar = 'void Cvar_SetVar (cvar_t *var, const char *value);\n'
|
||
|
cvar_info = 'struct cvar_s;\nvoid Cvar_Info (void *data, const struct cvar_s *cvar);\n'
|
||
|
nq_cl_cvar_info = 'nq/include/client.h'
|
||
|
nq_sv_cvar_info = 'nq/include/server.h'
|
||
|
qw_cl_cvar_info = 'qw/include/client.h'
|
||
|
qw_sv_cvar_info = 'qw/include/server.h'
|
||
|
|
||
|
cvar_file = "include/QF/cvar.h"
|
||
|
line_substitutions = [
|
||
|
(cvar_file, (40, 60), cvar_struct),
|
||
|
(cvar_file, (96, 100), cvar_reg),
|
||
|
(cvar_file, (35, 35), cvar_cexpr),
|
||
|
(cvar_file, (109, 110), cvar_set),
|
||
|
(cvar_file, (110, 111), cvar_setvar),
|
||
|
(nq_cl_cvar_info, (294,295), cvar_info),
|
||
|
(nq_sv_cvar_info, (300,301), cvar_info),
|
||
|
(qw_cl_cvar_info, (296,297), cvar_info),
|
||
|
(qw_sv_cvar_info, (634,635), cvar_info),
|
||
|
]
|
||
|
|
||
|
def get_cvar_defs(fname):
|
||
|
fi = open(fname,'rt')
|
||
|
f = files[fname] = fi.readlines()
|
||
|
i = 0
|
||
|
while i < len(f):
|
||
|
cr = cvar_setrom_re.match(f[i])
|
||
|
if cr:
|
||
|
cvar_rom.add(cr[1])
|
||
|
del(f[i])
|
||
|
continue
|
||
|
if cvar_get_join_prev_re.match(f[i]):
|
||
|
if cvar_get_assign_re.match(f[i - 1]):
|
||
|
f[i - 1] = f"{f[i - 1].rstrip()} {f[i].lstrip()}"
|
||
|
del(f[i])
|
||
|
i -= 1
|
||
|
if cvar_get_incomplete_re.match(f[i]):
|
||
|
while not cvar_get_complete_re.match(f[i]):
|
||
|
f[i + 1] = f[i + 1].lstrip()
|
||
|
f[i] = f[i].rstrip()
|
||
|
if f[i][-1] == '"' and f[i + 1][0] == '"':
|
||
|
#string concatentation
|
||
|
f[i] = f[i][:-1] + f[i + 1][1:]
|
||
|
else:
|
||
|
f[i] = f"{f[i]} {f[i + 1]}"
|
||
|
del(f[i + 1])
|
||
|
cd = cvar_decl_re.match(f[i])
|
||
|
if cd:
|
||
|
if cd[3] not in cvar_decls:
|
||
|
cvar_decls[cd[3]] = []
|
||
|
cvar_decls[cd[3]].append((fname, i))
|
||
|
cl = cvar_listener_re.match(f[i])
|
||
|
if cl:
|
||
|
if cl[1] not in cvar_listeners:
|
||
|
cvar_listeners[cl[1]] = []
|
||
|
cvar_listeners[cl[1]].append((fname, i, cl[2]))
|
||
|
cc = cvar_create_re.match(f[i])
|
||
|
if cc:
|
||
|
if cc.group(7):
|
||
|
default = 7
|
||
|
else:
|
||
|
default = 5
|
||
|
cc = cc.group(1, 2, default, 8, 12, 13)
|
||
|
if cc[0] not in cvar_dict:
|
||
|
cvar_dict[cc[0]] = cvar(*cc)
|
||
|
cvar_dict[cc[0]].add_file(fname, i)
|
||
|
cs = cvar_set_re.match(f[i])
|
||
|
if cs:
|
||
|
cvar_sets.append((fname, i))
|
||
|
i += 1
|
||
|
fi.close()
|
||
|
|
||
|
for f in sys.argv[1:]:
|
||
|
get_cvar_defs(f)
|
||
|
#for cv in cvar_decls.keys():
|
||
|
# if cv not in cvar_dict:
|
||
|
# print(f"cvar {cv} declared but never created")
|
||
|
for file in files.items():
|
||
|
fname,f = file
|
||
|
for i in range(len(f)):
|
||
|
for use in cvar_use_re.finditer(f[i]):
|
||
|
if use[1] in cvar_dict and cvar_dict[use[1]]:
|
||
|
cvar_dict[use[1]].add_use(use[2], fname, i)
|
||
|
keys = list(cvar_dict.keys())
|
||
|
for cv in keys:
|
||
|
var = cvar_dict[cv]
|
||
|
if cv not in cvar_decls or var.callback not in cvar_listeners:
|
||
|
continue
|
||
|
if var.callback in cvar_listeners:
|
||
|
if var.callback in cvar_listeners_done:
|
||
|
if not var.callback in cvar_listeners_multi:
|
||
|
print(f"WARNING: {var.callback} has multiple uses")
|
||
|
cvar_listeners_multi.add (var.callback)
|
||
|
else:
|
||
|
cvar_listeners_done.add (var.callback);
|
||
|
cvar_listeners_done.clear()
|
||
|
for cv in keys:
|
||
|
var = cvar_dict[cv]
|
||
|
var.guess_type()
|
||
|
if var.c_name in cvar_rom:
|
||
|
print(var.c_name, 'rom')
|
||
|
var.set_rom()
|
||
|
if cv in cvar_decls:
|
||
|
if var.callback in cvar_listeners_multi:
|
||
|
var.data = f'&{var.c_name}'
|
||
|
for d in cvar_decls[cv]:
|
||
|
decl = cvar_decl_re.match(files[d[0]][d[1]])
|
||
|
storage = decl[1].strip()
|
||
|
if storage == "extern":
|
||
|
subst = var.var(storage)
|
||
|
else:
|
||
|
subst = var.var(storage)+var.struct()
|
||
|
line_substitutions.append((d[0], (d[1], d[1] + 1), subst))
|
||
|
for c in var.files:
|
||
|
line_substitutions.append((c[0], (c[1], c[1] + 1), var.register()))
|
||
|
for u in var.uses:
|
||
|
# use substitutions are done immediately because they never
|
||
|
# change the number of lines
|
||
|
field, fname, line = u
|
||
|
file = files[fname]
|
||
|
places = []
|
||
|
for use in cvar_use_re.finditer(file[line]):
|
||
|
cv, field = use.group(1, 2)
|
||
|
if cv == var.c_name:
|
||
|
places.append((use.start(), use.end()))
|
||
|
places.reverse()
|
||
|
for p in places:
|
||
|
file[line] = file[line][:p[0]] + var.c_name + file[line][p[1]:]
|
||
|
modified.add(fname)
|
||
|
else:
|
||
|
#for f in cvar_dict[cv].files:
|
||
|
# print(f"{f[0]}:{f[1]}: {cv} created but not kept")
|
||
|
pass
|
||
|
for cv in keys:
|
||
|
var = cvar_dict[cv]
|
||
|
if cv not in cvar_decls or var.callback not in cvar_listeners:
|
||
|
continue
|
||
|
if var.callback in cvar_listeners_done:
|
||
|
continue
|
||
|
for listener in cvar_listeners[var.callback]:
|
||
|
fname, line, c_name = listener
|
||
|
file = files[fname]
|
||
|
file[line] = f"{var.callback} (void *data," " const cvar_t *cvar)\n"
|
||
|
modified.add(fname)
|
||
|
cvar_listeners_done.add (var.callback);
|
||
|
multi = var.callback in cvar_listeners_multi
|
||
|
line += 1
|
||
|
while line < len(file) and file[line] != '}\n':
|
||
|
line += 1
|
||
|
places = []
|
||
|
for use in cvar_use_re.finditer(file[line]):
|
||
|
cv, field = use.group(1, 2)
|
||
|
if cv == c_name:
|
||
|
subst = f'*({var.c_type}*)data' if multi else var.c_name
|
||
|
places.append((use.start(), use.end(), subst))
|
||
|
places.reverse()
|
||
|
for p in places:
|
||
|
file[line] = file[line][:p[0]] + p[2] + file[line][p[1]:]
|
||
|
modified.add(fname)
|
||
|
places = []
|
||
|
for v in id_re.finditer(file[line]):
|
||
|
if v[1] == c_name:
|
||
|
places.append((v.start(), v.end(), "cvar"))
|
||
|
for p in places:
|
||
|
file[line] = file[line][:p[0]] + p[2] + file[line][p[1]:]
|
||
|
modified.add(fname)
|
||
|
for s in cvar_sets:
|
||
|
cs = cvar_set_re.match(files[s[0]][s[1]])
|
||
|
subst = None
|
||
|
val = cs[4]
|
||
|
if cs[2] == "Cvar_SetValue" and cs[3] in cvar_dict:
|
||
|
if val[0] == '(':
|
||
|
#strip off a cast
|
||
|
val = val[val.index(')') + 1:].strip()
|
||
|
subst = f'{cs[1]}{cs[3]} = {val};{cs[5]}\n'
|
||
|
elif cs[2] == "Cvar_Set" and cs[3] in cvar_dict:
|
||
|
subst = f'{cs[1]}{cs[2]} ("{cvar_dict[cs[3]].name}", {val});{cs[5]}\n'
|
||
|
elif cs[2] == "Cvar_Set":
|
||
|
subst = f'{cs[1]}Cvar_SetVar ({cs[3]}, {val});{cs[5]}\n'
|
||
|
if subst:
|
||
|
line_substitutions.append((s[0], (s[1], s[1] + 1), subst))
|
||
|
line_substitutions.sort(reverse=True, key=lambda s: s[1][1])
|
||
|
for substitution in line_substitutions:
|
||
|
file, lines, subst = substitution
|
||
|
modified.add(file)
|
||
|
files[file][lines[0]:lines[1]] = subst
|
||
|
for f in files.keys():
|
||
|
if f in modified:
|
||
|
file = open(f, "wt")
|
||
|
file.writelines(files[f])
|
||
|
file.close ()
|