diff options
-rwxr-xr-x | tools/template_convert.py | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/tools/template_convert.py b/tools/template_convert.py new file mode 100755 index 0000000000..edc81135da --- /dev/null +++ b/tools/template_convert.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python3 + +import os +import re +from argparse import ArgumentParser + +template_keywords = [ + "GR_EXPAND_X_H", "GR_EXPAND_CC_H", "GR_EXPAND_X_CC_H_IMPL", "GR_EXPAND_X_CC_H" +] +template_regex = re.compile( + "^(?P<template_type>" + "|".join(template_keywords) + ")\(" + + "(?P<category>\w+)\s+(?P<name>\w+_XX?X?)(_impl)?\s+(?P<types>[\w\s]+)" + "\)$") + +cpp_keywords = ["abs", "add", "and", "max", "min" "not" "xor"] +types = {"s": "std::int16_t", "i": "std::int32_t", "b": "std::uint8_t", "c": "gr_complex", "f": "float"} + + +def get_real_name(block_definition): + """ + Return the base name of the template blocks (e.g. foo_XXX) + """ + return "_".join(block_definition.get("name").split("_")[:-1]) + + +def rewrite_cmakelists(block_definition): + """ + Remove gengen template invokations from CMakeLists.txt + """ + with open( + os.path.join(block_definition.get("path"), "CMakeLists.txt"), + "r") as f: + cmakelists = f.readlines() + cmakelists_new = [] + for line in cmakelists: + if line.startswith( + block_definition.get("template_type") + "(" + block_definition. + get("category") + " " + block_definition.get("name")): + continue + cmakelists_new.append(line) + with open( + os.path.join(block_definition.get("path"), "CMakeLists.txt"), + "w") as f: + f.writelines(cmakelists_new) + + +def convert_public_header(block_definition): + """ + Replace template arguments with the correct C++ template + expressions + """ + real_name = get_real_name(block_definition) + original_name = real_name + cpp_keyword = False + if real_name in cpp_keywords: + real_name += "_blk" + cpp_keyword = True + target_file = os.path.join(block_definition.get("path"), real_name + ".h") + source_file = os.path.join( + block_definition.get("path"), + block_definition.get("name") + ".h.t") + os.rename(source_file, target_file) + + with open(target_file, "r") as f: + content = f.readlines() + + with open(target_file, "w") as f: + new_content = [] + typedefs = False + for line in content: + line = line.replace("@GUARD_NAME@", "_".join([real_name, + "h"]).upper()) + if "typedef" in line: + line = line.replace("@NAME@", " " + real_name + "<T> ") + line = line.replace("@BASE_NAME@", " " + real_name + "<T> ") + else: + line = line.replace("@NAME@", real_name) + line = line.replace("@BASE_NAME@", real_name) + + line = line.replace("@I_TYPE@", "T") + line = line.replace("@O_TYPE@", "T") + line = line.replace("@TYPE@", "T") + if "@WARNING@" in line: + continue + if "class" in line: + new_content.append("template<class T>\n") + if not typedefs and "} /* namespace" in line: + for t in block_definition.get("types"): + new_content.append("typedef " + real_name + "<" + + types[t[0]] + "> " + original_name + "_" + t + ";\n") + typedefs = True + new_content.append(line) + f.writelines(new_content) + + +def convert_impl_header(block_definition): + """ + Replace template arguments with the correct C++ template + expressions + """ + real_name = get_real_name(block_definition) + cpp_keyword = False + if real_name in cpp_keywords: + real_name += "_blk" + cpp_keyword = True + target_header = os.path.join( + block_definition.get("path"), real_name + "_impl.h") + source_header = os.path.join( + block_definition.get("path"), + block_definition.get("name") + "_impl.h.t") + os.rename(source_header, target_header) + with open(target_header, "r") as f: + content = f.readlines() + + with open(target_header, "w") as f: + new_content = [] + for line in content: + line = line.replace("@GUARD_NAME_IMPL@", + "_".join([real_name, "impl_h"]).upper()) + line = line.replace("@GUARD_NAME@", + "_".join([real_name, "impl_h"]).upper()) + if "typedef" in line or "class" in line: + line = line.replace("@NAME@", " " + real_name + "<T> ") + line = line.replace("@BASE_NAME@", " " + real_name + "<T> ") + line = line.replace("@NAME_IMPL@", real_name + "_impl<T> ") + line = line.replace("@IMPL_NAME@", real_name + "_impl<T> ") + else: + line = line.replace("@NAME@", real_name) + line = line.replace("@BASE_NAME@", real_name) + line = line.replace("@NAME_IMPL@", real_name + "_impl ") + line = line.replace("@IMPL_NAME@", real_name + "_impl") + + line = line.replace("@I_TYPE@", "T") + line = line.replace("@O_TYPE@", "T") + line = line.replace("@TYPE@", "T") + if "@WARNING@" in line: + continue + if "class" in line: + new_content.append("template<class T>\n") + new_content.append(line) + f.writelines(new_content) + + +def convert_impl_impl(block_definition): + """ + Replace template arguments with the correct C++ template + expressions + """ + real_name = get_real_name(block_definition) + cpp_keyword = False + if real_name in cpp_keywords: + real_name += "_blk" + cpp_keyword = True + + target_impl = os.path.join( + block_definition.get("path"), real_name + "_impl.cc") + source_impl = os.path.join( + block_definition.get("path"), + block_definition.get("name") + "_impl.cc.t") + + os.rename(source_impl, target_impl) + with open(target_impl, "r") as f: + content = f.readlines() + + with open(target_impl, "w") as f: + new_content = [] + instantiated = False + for line in content: + line = line.replace("@GUARD_NAME_IMPL@", + "_".join([real_name, "impl_h"]).upper()) + if "typedef" in line or "class" in line: + line = line.replace("@NAME@", " " + real_name + "<T> ") + line = line.replace("@BASE_NAME@", " " + real_name + "<T> ") + else: + line = line.replace("@NAME@", real_name) + line = line.replace("@BASE_NAME@", real_name) + line = line.replace("@IMPL_NAME@", real_name + "_impl<T>") + line = line.replace("@NAME_IMPL@", real_name + "_impl<T> ") + line = line.replace("@I_TYPE@", "T") + line = line.replace("@O_TYPE@", "T") + line = line.replace("@TYPE@", "T") + if "@WARNING@" in line: + continue + if "class" in line: + new_content.append("template<class T>\n") + if not instantiated and "} /* namespace" in line: + for t in block_definition.get("types"): + new_content.append("template class " + real_name + "<" + + types[t[0]] + ">;\n") + instantiated = True + new_content.append(line) + f.writelines(new_content) + + +def convert_impl(block_definition): + """ + Convert the impl header and implementation + """ + convert_impl_header(block_definition) + convert_impl_impl(block_definition) + + +def handle_template_conversion(block_definition): + """ + Convert gengen templates to C++ templates for simple cases + which only have one type for input and output + """ + if block_definition.get("single_type", None) == True: + if block_definition.get("template_type") == "GR_EXPAND_X_H": + convert_public_header(block_definition) + elif block_definition.get( + "template_type" + ) == "GR_EXPAND_X_CC_H_IMPL" or block_definition.get( + "template_type") == "GR_EXPAND_X_CC_H": + convert_impl(block_definition) + rewrite_cmakelists(block_definition) + + +def find_template_blocks(cmake_file): + """ + Match every line in a CMakeLists.txt file with a template regex + """ + blocks = [] + with open(cmake_file, "r") as f: + for line in f: + result = re.match(template_regex, line) + if result is not None: + r = result.groupdict() + r["types"] = r.get("types", "").split(" ") + if all([(t[1:] == t[:-1] or len(t) == 1) for t in r["types"]]): + r["single_type"] = True + else: + r["single_type"] = False + blocks.append(r) + return blocks + + +def walk_and_find(root): + """ + Identify templated blocks by looking in the CMakeLists + """ + all_blocks = [] + for (dirpath, dirnames, filenames) in os.walk(root): + if "CMakeLists.txt" in filenames: + blocks = find_template_blocks( + os.path.join(dirpath, "CMakeLists.txt")) + all_blocks.extend([{**block, "path": dirpath} for block in blocks]) + return all_blocks + + +def parse_args(): + parser = ArgumentParser() + parser.add_argument( + '--directory_root', + "-d", + help="Root directory to start search", + default=os.environ.get("PWD")) + parser.add_argument( + "--no_changes", + "-n", + dest="no_changes", + default=False, + action="store_true", + help="Only show found templated blocks, don't apply changes") + return parser.parse_args() + + +def main(): + """ + Run this if the program was invoked on the commandline + """ + args = parse_args() + all_blocks = walk_and_find(args.directory_root) + for block in all_blocks: + if not args.no_changes: + handle_template_conversion(block) + else: + print(block) + return True + + +if __name__ == "__main__": + exit(not (main())) |