refactor: new project structure.

This commit is contained in:
2025-02-27 23:07:54 +08:00
committed by GitHub
parent 44d0911320
commit 5f4a912e30
81 changed files with 816 additions and 842 deletions

View File

@@ -0,0 +1,7 @@
# import WHAT
def main():
pass
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,113 @@
import os
import util.cpp_language as CppUtil
import util.string as StrUtil
# storage
defined_classes = dict()
defined_classes_path = dict()
empty_class_all_names = set()
class ClassDefine:
rpath = str()
is_template = bool()
is_empty = bool()
def __init__(self, rpath: str, is_template: bool, is_empty: bool):
self.rpath = rpath
self.is_template = is_template
self.is_empty = is_empty
def add_class_record(path: str, namespace: str, class_name: str, is_template: bool, is_empty: bool):
assert len(path) > 0 and len(class_name) > 0
if namespace not in defined_classes:
defined_classes[namespace] = {}
assert class_name not in defined_classes[namespace], (
f'path = {path}, ns = {namespace}, cl = {class_name}'
)
rpath = path[path.find('src/') + 4 :]
defined_classes[namespace][class_name] = ClassDefine(rpath, is_template, is_empty)
if rpath not in defined_classes_path:
defined_classes_path[rpath] = {}
if namespace not in defined_classes_path[rpath]:
defined_classes_path[rpath][namespace] = set()
defined_classes_path[rpath][namespace].add(class_name)
if is_empty:
all_name = ''
if namespace != '':
all_name += namespace + '::'
all_name += class_name
empty_class_all_names.add(all_name)
def query_class_record_strict(namespace_decl: str, class_decl: str) -> ClassDefine:
assert namespace_decl in defined_classes, f'namespace = "{namespace_decl}" is not recorded.'
assert class_decl in defined_classes[namespace_decl], (
f'namespace = "{namespace_decl}", class = {class_decl} is not recorded.'
)
return defined_classes[namespace_decl][class_decl]
def process(path_to_file: str):
assert os.path.isfile(path_to_file)
if path_to_file.endswith('_HeaderOutputPredefine.h'):
return
with open(path_to_file, 'r', encoding='utf-8') as file:
# states
in_forward_declaration_list = False
current_namespace = []
current_classes = []
# tmp
content = ''
for line in file.readlines():
stripped_line = line.strip()
# record forward declarations
if stripped_line.startswith('// auto generated forward declare list'):
in_forward_declaration_list = True
if stripped_line.startswith('// clang-format on') and in_forward_declaration_list:
in_forward_declaration_list = False
# record namespace & classes
if not in_forward_declaration_list:
founded_cl = CppUtil.find_class_definition(line)
if founded_cl: # ignore anonymous classes.
is_template = (
content[content.rfind('\n', 0, -1) :].strip().startswith('template ')
)
is_empty = stripped_line.endswith('{};')
nested_cl = ''
if current_classes:
for pair in current_classes:
nested_cl += pair[1] + '::'
add_class_record(
path_to_file,
'::'.join(current_namespace),
nested_cl + founded_cl,
is_template,
is_empty,
)
class_keyword_pos, _ = StrUtil.find_mb(line, 'class ', 'struct ', 'union ')
assert class_keyword_pos != -1, f"path = {path_to_file}, line = '{line}'"
if not is_empty:
current_classes.append([class_keyword_pos, founded_cl])
founded_ns = CppUtil.find_namespace_declaration(line)
if founded_ns:
current_namespace.append(founded_ns)
if '// namespace' in stripped_line:
current_namespace.pop()
if current_classes and line.startswith(' ' * (current_classes[-1][0]) + '};'):
current_classes.pop()
content += line

View File

@@ -0,0 +1,361 @@
import os
import re
import header_preprocessor as HeaderPreProcessor
from options import Options
import util.cpp_language as CppUtil
import util.string as StrUtil
def try_translate_forward_declaration(
decl: str, typeset: list
) -> HeaderPreProcessor.ClassDefine | None:
find_decl = CppUtil.find_class_forward_declaration(decl)
assert find_decl, f'decl = {decl}'
namespace = find_decl.namespace_decl
clazz = find_decl.class_decl
if not CppUtil.is_full_type_required_for_typeset(namespace, clazz, typeset):
return None
return HeaderPreProcessor.query_class_record_strict(namespace, clazz)
def process(path_to_file: str, args: Options):
assert os.path.isfile(path_to_file)
if path_to_file.endswith('_HeaderOutputPredefine.h'):
return
RECORDED_THUNKS = []
if args.remove_constructor_thunk:
RECORDED_THUNKS.append('// constructor thunks')
if args.remove_destructor_thunk:
RECORDED_THUNKS.append('// destructor thunk')
if args.remove_virtual_table_pointer_thunk:
RECORDED_THUNKS.append('// vftables')
if args.remove_virtual_function_thunk:
RECORDED_THUNKS.append('// virtual function thunks')
with open(path_to_file, 'r', encoding='utf-8') as file:
# states
is_modified = False
in_useless_thunk = False
in_static_variable = False
in_member_variable = False
in_forward_declaration_list = False
in_invalid_default_ctors = False
has_typed_storage = False
forward_declarations = []
member_variable_types = []
current_unions = []
current_classes = []
ll_typed_regex = re.compile(r'TypedStorage<(\d+), (\d+), (.*?)> (\w+);')
ll_untyped_regex = re.compile(r'UntypedStorage<(\d+), (\d+)> (\w+);')
# tmp
content = ''
for line in file.readlines():
stripped_line = line.strip()
# restore static member variable:
if args.restore_static_variable and '// static variables' in line:
in_static_variable = True
is_modified = True
if in_static_variable and stripped_line.endswith(';'):
if not stripped_line.startswith('MCAPI'): # declaration may not be on one line
begin_pos = content.rfind('MCAPI')
stripped_line = content[begin_pos:] + stripped_line
content = content[:begin_pos]
stripped_line = stripped_line.strip()
# remove parameter list (convert to static variable)
call_spec_pos = stripped_line.rfind('()')
assert call_spec_pos != -1
stripped_line = stripped_line[:call_spec_pos] + stripped_line[call_spec_pos + 2 :]
# remove reference
refsym_pos = stripped_line.rfind('&') # T&
tlpsym_pos = stripped_line.rfind('>') # ::std::add_lvalue_reference_t<T>
assert refsym_pos != -1 or tlpsym_pos != -1, f'in {path_to_file}'
if tlpsym_pos == -1 or refsym_pos > tlpsym_pos:
stripped_line = stripped_line[:refsym_pos] + stripped_line[refsym_pos + 1 :]
elif refsym_pos == -1 or tlpsym_pos > refsym_pos:
# C-style arrays must have '[]' written after the variable name
stripped_line = stripped_line[:tlpsym_pos] + '>' + stripped_line[tlpsym_pos:]
stripped_line = stripped_line.replace(
'::std::add_lvalue_reference_t<',
'::std::remove_reference_t<::std::add_lvalue_reference_t<',
)
if args.fix_msvc_c2734 and stripped_line.find('static ') == -1:
stripped_line = 'extern ' + stripped_line
content += f'\t{stripped_line}\n'
continue
# restore member variable:
if args.restore_member_variable and stripped_line.startswith(
'::ll::'
): # union { ... };
in_member_variable = True
is_modified = True
if in_member_variable and stripped_line.endswith(';'):
if not stripped_line.startswith('::ll::'):
begin_pos = content.rfind('::ll::')
stripped_line = content[begin_pos:] + stripped_line
content = content[:begin_pos]
stripped_line = stripped_line.strip()
# ::ll::TypedStorage<Alignment, Size, T> mVar;
if 'TypedStorage' in stripped_line:
has_typed_storage = True
matched = ll_typed_regex.search(StrUtil.flatten(stripped_line))
assert matched and matched.lastindex == 4, (
f'in {path_to_file}, line="{stripped_line}"'
)
align = int(matched[1])
size = int(matched[2])
type_name = matched[3]
var_name = matched[4]
if (
not StrUtil.endswith_m(type_name, '&', '*')
and args.fix_size_for_type_with_empty_template_class
):
for empty_class in HeaderPreProcessor.empty_class_all_names:
if empty_class in type_name or (
args.erase_extra_invalid_types
and (
'WeakRef<' in type_name # TODO: remove it.
or '::entt::basic_registry<' in type_name
or '::Bedrock::Application::ThreadOwner<' in type_name
or '::OwnerPtr<::EntityContext>' in type_name
or (
args.add_trivial_dynamic_initializer
and '::std::variant<' in type_name # TODO: ...
)
or (
args.add_trivial_dynamic_initializer
and type_name # unique_ptr with user deleter, TODO: ...
== '::std::unique_ptr<::RakNet::RakPeerInterface, void (*)(::RakNet::RakPeerInterface*)>'
)
)
):
# print(f'erased: {type_name}')
content += (
f'\talignas({align}) std::byte {var_name}_TYPEERASED[{size}];\n'
)
in_member_variable = False
break
if not in_member_variable:
continue
if args.add_trivial_dynamic_initializer:
type_name = type_name.replace(' const', '')
member_variable_types.append(type_name)
security_check = ''
if args.add_sizeof_alignof_static_assertions and not current_unions:
security_check += f'\tstatic_assert(sizeof({var_name}) == {size});\n'
# TODO: ensure alignment requirements.
# security_check += (
# f'\tstatic_assert(alignof(decltype({var_name})) == {align});\n'
# )
if type_name.endswith(']'): # is c-style array
array_defs = type_name[type_name.find('[') : type_name.rfind(']') + 1]
type_name = type_name[: type_name.find('[')]
var_name = var_name + array_defs
if type_name.endswith('&'): # is reference type
type_name = type_name[:-1] + '*'
ptr_id_pos, ptr_id = StrUtil.find_m(type_name, '(*)', '::*)')
if -1 != ptr_id_pos and not CppUtil.find_template_name(
type_name, ptr_id, disable_regex_word_bound=True
): # is c-style function ptr or member ptr
# it's a trick, make sure `ptr` endswith '*)'
ptr_id_len = len(ptr_id) - 1
type_name = (
type_name[: ptr_id_pos + ptr_id_len]
+ var_name
+ type_name[ptr_id_pos + ptr_id_len :]
)
var_name = ''
content += f'\t{type_name} {var_name};\n{security_check}'
in_member_variable = False
continue
# ::ll::UntypedStorage<Alignment, Size> mVar;
if 'UntypedStorage' in stripped_line:
matched = ll_untyped_regex.search(StrUtil.flatten(stripped_line))
assert matched and matched.lastindex == 3, (
f'in {path_to_file}, line="{stripped_line}"'
)
align = matched[1]
size = matched[2]
var_name = matched[3]
content += f'\talignas({align}) std::byte {var_name}[{size}];\n'
in_member_variable = False
continue
assert False, 'unreachable'
union_keyword_pos = line.find('union ')
if not in_forward_declaration_list and union_keyword_pos != -1:
current_unions.append(union_keyword_pos)
if current_unions and line.startswith(' ' * current_unions[-1] + '};'):
current_unions.pop()
if not in_forward_declaration_list:
founded_cl = CppUtil.find_class_definition(line)
if founded_cl:
class_keyword_pos, _ = StrUtil.find_mb(line, 'class ', 'struct ', 'union ')
assert class_keyword_pos != -1, f"path = {path_to_file}, line = '{line}'"
if not stripped_line.endswith('{};'):
current_classes.append([class_keyword_pos, founded_cl])
if current_classes and line.startswith(' ' * (current_classes[-1][0]) + '};'):
current_classes.pop()
# fix forward declarations
if stripped_line.startswith('// auto generated forward declare list'):
in_forward_declaration_list = True
if in_forward_declaration_list and StrUtil.startswith_m(
stripped_line, 'class ', 'struct ', 'union ', 'namespace '
):
forward_declarations.append(stripped_line)
if stripped_line.startswith('// clang-format on') and in_forward_declaration_list:
in_forward_declaration_list = False
# remove useless thunks.
if '// NOLINTEND' in line and (in_useless_thunk or in_static_variable):
in_useless_thunk = False
in_static_variable = False
continue # don't add this line.
for token in RECORDED_THUNKS:
if token in line:
in_useless_thunk = True
is_modified = True
# remove previous access specifier.
content = content[: content.rfind('public:')]
content = content[: content.rfind('\n')] # for nested classes
if args.add_trivial_dynamic_initializer:
if in_invalid_default_ctors:
if 'operator=' in line or 'const&' in line or '()' in line:
content += '\t// ' + stripped_line + '\n'
continue
else:
in_invalid_default_ctors = False
if '// prevent constructor by default' in line:
in_invalid_default_ctors = True
if current_classes and stripped_line.endswith(';'):
begin_pos = None
if not stripped_line.startswith('MCAPI'):
begin_pos = content.rfind('MCAPI')
check_pos = content.rfind(';')
if begin_pos > check_pos:
stripped_line = content[begin_pos:] + stripped_line
stripped_line = StrUtil.flatten(stripped_line)
class_name = current_classes[-1][1]
if (
f' {class_name}()' not in stripped_line
and f' {class_name}(' in stripped_line
):
if begin_pos:
content = content[:begin_pos]
content += '\t// ' + stripped_line + '\n'
continue
if not in_useless_thunk:
content += line
if args.fix_includes_for_member_variables and has_typed_storage:
for decl in forward_declarations:
if args.add_trivial_dynamic_initializer and 'CommandParameterData' in decl:
# see CommandRegistry.h, to resolve circular reference.
continue
class_define = try_translate_forward_declaration(decl, member_variable_types)
if class_define:
is_modified = True
content = content.replace(decl, f'#include "{class_define.rpath}"\n')
continue
if args.add_trivial_dynamic_initializer:
path = path_to_file[path_to_file.find('src/') + 4 :]
if path in HeaderPreProcessor.defined_classes_path:
for ns, classes in HeaderPreProcessor.defined_classes_path[path].items():
for cl in classes:
if HeaderPreProcessor.defined_classes[ns][cl].is_template:
continue
var_name = 'dummy__'
if ns:
var_name += ns.replace('::', '_') + '_'
var_name += cl.replace('::', '_')
full_name = ns
if ns:
full_name += '::'
full_name += cl
if (
full_name
in [
'Json::ValueIteratorBase', # std::vector<T>::iterator
'Json::ValueConstIterator',
'Json::ValueIterator',
'MemoryStream', # std::istream
'Core::FileStream', # ::std::iostream
'CommandRegistry::Overload', # circular references
'Bedrock::UniqueOwnerPointer', # bug?
'MovementDataExtractionUtility::StorageStorage::StorageTupleT', # shit template
]
):
continue
is_modified = True
content += f'\ninline {full_name} {var_name};'
# rm pure virtual funcs.
is_modified = True
content = (
content.replace(' = 0;', ';')
.replace('::std::reference_wrapper<', '::x_std::reference_wrapper<')
.replace('::Bedrock::NotNullNonOwnerPtr<', '::x_Bedrock::NotNullNonOwnerPtr<')
.replace('::gsl::not_null<', '::x_gsl::not_null<')
.replace('::leveldb::EnvWrapper', '::x_leveldb::EnvWrapper')
)
if is_modified:
with open(path_to_file, 'w', encoding='utf-8') as wfile:
wfile.write(content)
def iterate(args: Options):
assert os.path.isdir(args.base_dir)
def _traverse_header(path_to_dir: str, fun):
for root, dirs, files in os.walk(path_to_dir):
for file in files:
if CppUtil.is_header_file(file):
path = os.path.join(root, file)
fun(path)
def _preprocess(path_to_file):
HeaderPreProcessor.process(path_to_file)
def _process(path_to_file):
process(path_to_file, args)
_traverse_header(args.base_dir, _preprocess)
_traverse_header(args.base_dir, _process)

31
scripts/dethunk/main.py Normal file
View File

@@ -0,0 +1,31 @@
import argparse
import header_processor as HeaderProcessor
def main():
parser = argparse.ArgumentParser('dethunk')
parser.add_argument('path', help='Path to header project.')
parser.add_argument('--remove-constructor-thunk', action='store_true')
parser.add_argument('--remove-destructor-thunk', action='store_true')
parser.add_argument('--remove-virtual-table-pointer-thunk', action='store_true')
parser.add_argument('--remove-virtual-function-thunk', action='store_true')
parser.add_argument('--restore-static-variable', action='store_true')
parser.add_argument('--restore-member-variable', action='store_true')
parser.add_argument('--all', action='store_true', help='Apply all remove/restore options.')
parser.add_argument('--preset-extract-names', action='store_true')
parser.add_argument('--preset-extract-types', action='store_true')
args = parser.parse_args()
HeaderProcessor.iterate(HeaderProcessor.Options(args))
print('done.')
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,96 @@
"""
Runtime Options
"""
class Options:
base_dir = str()
# functions
remove_constructor_thunk = bool()
remove_destructor_thunk = bool()
remove_virtual_function_thunk = bool()
# variables
remove_virtual_table_pointer_thunk = bool()
restore_static_variable = bool()
restore_member_variable = bool()
# others (for class member variable)
# * only takes effect for TypedStorage, since the TypedStorage wrapper makes the full type unnecessary.
fix_includes_for_member_variables = True
# * template definitions cannot be generated for headergen and may be wrong. class sizes may be wrong if
# * empty templates are used in TypedStorage.
# * this option will erase the type of the empty template (convert to uchar[size]).
fix_size_for_type_with_empty_template_class = True
# * this option will and add sizeof & alignof static assertions to members. (only takes effect for TypedStorage)
add_sizeof_alignof_static_assertions = True
# * some types of template definitions are not accurate.
erase_extra_invalid_types = True
# * try to initialize each class dynamically to ensure that the generated debug information is complete.
# * this will ENSURE that all classes have a default constructor.
add_trivial_dynamic_initializer = True
# others (for static class member viriable)
# * in msvc, 'const' object must be initialized if not 'extern' (c2734)
# * see also https://reviews.llvm.org/D45978
fix_msvc_c2734 = True
def __init__(self, args):
self.base_dir = args.path
self.remove_constructor_thunk = args.remove_constructor_thunk
self.remove_destructor_thunk = args.remove_destructor_thunk
self.remove_virtual_function_thunk = args.remove_virtual_function_thunk
self.remove_virtual_table_pointer_thunk = args.remove_virtual_table_pointer_thunk
self.restore_static_variable = args.restore_static_variable
self.restore_member_variable = args.restore_member_variable
if args.preset_extract_names:
self.apply_preset_extract_names()
if args.preset_extract_types:
self.apply_preset_extract_types()
if args.all:
self.set_all(True)
# others
self.judge_other_options()
def set_function(self, opt: bool):
self.remove_constructor_thunk = opt
self.remove_destructor_thunk = opt
self.remove_virtual_function_thunk = opt
def set_variable(self, opt: bool):
self.remove_virtual_table_pointer_thunk = opt
self.restore_static_variable = opt
self.restore_member_variable = opt
def set_all(self, opt: bool):
self.set_function(opt)
self.set_variable(opt)
def apply_preset_extract_names(self):
self.remove_constructor_thunk = True
self.remove_destructor_thunk = True
self.remove_virtual_function_thunk = True
self.remove_virtual_table_pointer_thunk = True
self.restore_static_variable = True
def apply_preset_extract_types(self):
self.apply_preset_extract_names()
self.restore_member_variable = True
def judge_other_options(self):
if not self.restore_member_variable:
self.fix_includes_for_member_variables = False
self.fix_size_for_type_with_empty_template_class = False
self.add_sizeof_alignof_static_assertions = False
self.erase_extra_invalid_types = False
self.add_trivial_dynamic_initializer = False
if not self.restore_static_variable:
self.fix_msvc_c2734 = False

View File

@@ -0,0 +1,202 @@
"""
C++ Language Utility
* some methods may not be designed to be universal.
"""
import re
import util.string as StrUtil
def is_header_file(path: str):
return path.endswith('.h') or path.endswith('.hpp')
def find_class_definition(line: str) -> str | None:
# KEYWORD A (no quotation mark)
# KEYWORD A :
# KEYWORD A {
# KEYWORD A { ... }; (in single line)
keyword_pos, keyword = StrUtil.find_mb(line, 'enum class ', 'class ', 'struct ', 'union ')
if keyword_pos == -1 or keyword == 'enum class ':
return None
keyword_size = len(keyword) - 1 # not class defs
left_brace_pos = line.find('{')
semicolon_pos = line.find(';')
if semicolon_pos != -1 and (left_brace_pos == -1 or semicolon_pos < left_brace_pos):
return None # is forward decl
end_pos = len(line)
colon_pos = line.find(':')
l_angle_bracket_pos = line.find('<')
if left_brace_pos != -1:
end_pos = min(end_pos, left_brace_pos)
if colon_pos != -1:
end_pos = min(end_pos, colon_pos)
if l_angle_bracket_pos != -1 and l_angle_bracket_pos < colon_pos:
return None # template specialization (is not supported)
result = line[keyword_pos + keyword_size : end_pos].strip()
if '>' in result:
return None
return result
class ForwardDeclaration:
namespace_decl = str()
class_decl = str()
def __init__(self, namespace_decl: str, class_decl: str):
self.namespace_decl = namespace_decl
self.class_decl = class_decl
def find_class_forward_declaration(line: str) -> ForwardDeclaration | None:
# class A;
# namespace B { class A; }
namespace_decl = ''
class_decl = ''
namespace_pos = line.find('namespace')
left_brace_pos = line.find('{')
if namespace_pos != -1 and left_brace_pos != -1:
namespace_decl = line[namespace_pos + len('namespace') : left_brace_pos].strip()
keyword_pos, keyword = StrUtil.find_mb(line, 'class ', 'struct ', 'union ')
if keyword_pos == -1:
return None
keyword_size = len(keyword) - 1 # not class defs
semicolon_pos = line.find(';')
if semicolon_pos == -1:
return None
class_decl = line[keyword_pos + keyword_size : semicolon_pos].strip()
return ForwardDeclaration(namespace_decl, class_decl)
def find_namespace_declaration(line: str) -> str | None:
namespace_pos = line.find('namespace')
left_brace_pos = line.find('{')
if namespace_pos == -1 or left_brace_pos == -1:
return None
return line[namespace_pos + len('namespace') : left_brace_pos].strip()
def find_template_name(full: str, what: str, disable_regex_word_bound: bool = False):
def _impl(endpos: int):
while True:
r_angle_bracket_pos = full.rfind('>', 0, endpos)
l_angle_bracket_pos = full.rfind('<', 0, endpos)
if l_angle_bracket_pos == -1:
return None
if r_angle_bracket_pos > l_angle_bracket_pos:
endpos = l_angle_bracket_pos
continue
ret = full[:l_angle_bracket_pos]
matched_non_name = list(re.finditer(r'[^a-zA-Z_]', ret))
if len(matched_non_name) > 0:
ret = ret[matched_non_name[-1].start() + 1 :]
assert len(ret) > 0
return ret
if not disable_regex_word_bound:
for matched in re.finditer(rf'\b{re.escape(what)}\b', full):
endpos = matched.start()
result = _impl(endpos)
if result:
return result
else:
endpos = full.find(what)
if endpos == -1:
return None
return _impl(endpos)
def is_full_type_required(namespace_decl: str, class_decl: str, type_decl: str):
"""
Determine whether `class_decl` requires a full type for `type_decl`.
TODO: namespace_decl (currently UNUSED).
"""
# Y: T
# Y: std::optional<T>
# Y: std::variant<T>
# Y: std::array<T, _>
# Y: std::pair<T, T>
# Y: std::unordered_set<T>
# Y: std::unordered_map<T, _>
# Y: std::deque<T> // under msstl only
# Y: std::queue<T> // under msstl only
# N: T&
# N: T*
# N: std::map<T, T>
# N: std::shared_ptr<T>
# N: std::unique_ptr<T>
# N: std::weak_ptr<T>
# N: std::vector<T>
# N: std::set<T>
# N: std::unordered_map<_, T>
# N: std::function<T(T)>
def is_subtk_ends_with(full: str, tk: str, whats: list):
founded = False
for matched in re.finditer(rf'\b{re.escape(tk)}\b', full):
founded = True
if len(full) > matched.end():
for what in whats:
if full[matched.end() : matched.end() + len(what)] == what:
return founded, True
return founded, False
# is reference or pointer type?
founded, is_endswith = is_subtk_ends_with(
type_decl, class_decl, ['&', '*', ' const&', ' const*']
)
if not founded or is_endswith:
return False
# is template params?
template_name = find_template_name(type_decl, class_decl)
if not template_name:
return True # moreover, is not a template parameter
if template_name in [ # forward declarations are allowed.
'map',
'shared_ptr',
'unique_ptr',
'weak_ptr',
# 'vector',
'queue',
'set',
'function',
]:
return False
elif template_name in [ # full type is required.
'optional',
'variant',
'array',
'pair',
'unordered_set',
'deque',
'queue',
]:
return True
else:
return True # by default, we assume that a full type is required.
def is_full_type_required_for_typeset(namespace_decl: str, class_decl: str, typeset: list):
for type_decl in typeset:
if is_full_type_required(namespace_decl, class_decl, type_decl):
return True
return False

View File

@@ -0,0 +1,61 @@
"""
String Utiltiy
"""
import re
def startswith_m(con: str, *args) -> bool:
for arg in args:
if con.startswith(arg):
return True
return False
def endswith_m(con: str, *args) -> bool:
for arg in args:
if con.endswith(arg):
return True
return False
def find_mb(con: str, *args) -> int: # bounded
r_pos = -1
r_arg = None
for arg in args:
matched = re.search(rf'\b{re.escape(arg)}\b', con)
if not matched:
continue
pos = matched.start()
if pos != -1 and (r_pos == -1 or pos < r_pos):
r_pos = pos
r_arg = arg
return r_pos, r_arg
def find_m(con: str, *args) -> int:
r_pos = -1
r_arg = None
for arg in args:
pos = con.find(arg)
if pos != -1 and (r_pos == -1 or pos < r_pos):
r_pos = pos
r_arg = arg
return r_pos, r_arg
def rfind_m(con: str, *args) -> int:
r_pos = -1
r_arg = None
for arg in args:
pos = con.rfind(arg)
if pos > r_pos:
r_pos = pos
r_arg = arg
return r_pos, r_arg
def flatten(con: str):
# typed storage can be very complex, so we need to preprocess it.
# TODO: find a better way.
return re.sub(r'\s+', ' ', con).replace('< ', '<').replace(':: ', '::')