diff --git a/convert_to_cpp.py b/convert_to_cpp.py new file mode 100755 index 0000000..c4a6c0e --- /dev/null +++ b/convert_to_cpp.py @@ -0,0 +1,240 @@ +#!/bin/python3 +""" A python script to transform a vulkan project to use vulkan.hpp instead of the c api +Copyright © 2022 Matthias Quintern. +This software comes with no warranty. +This software is licensed under the GPL3 +""" +from os import makedirs, mkdir, path, listdir, chdir +from sys import argv, exit +import re +from bs4 import BeautifulSoup as bs + + +filetypes = [ ".hpp", ".cpp" ] +no_delete = False + +def error(s): + print(s) + exit(1) + +def case_to_camel(s: str) -> str: + """ + turn all letters following a uppercase letter to lowercase + turn letter following " _0-9" to uppercase + remove " " and "_" + """ + ret = "" + make_lower = False + make_upper = False + for c in s: + if c in "_ 0123456789": + make_lower = False + make_upper = True + if c in "_ ": + continue + else: + ret += c + continue + + if make_lower: + ret += c.lower() + elif make_upper: + ret += c.upper() + else: + ret += c + make_upper = False + make_lower = True + return ret + +def case_first_letter_low(s: str) -> str: + return s[0].lower() + s[1:] + +def delete(s) -> str: + global no_delete + if no_delete: + return "// no-delete " + s + else: + return "" + +enum_suffixes = [ "KHR", "EXT", "AMD", "NV"] +vk_soup = bs() +def init_enum_names(): + global vk_soup + with open("vk.xml", "r") as vk: + spec = vk.read() + vk_soup = bs(spec, features="lxml") + +new_token_begin_chars = r"[<>()\[\]{} ,;=&|\t\n]" + + +# functions that take the line and a token and return the transformed line +re_vk_function: str = new_token_begin_chars + r"(vk[A-Z][a-zA-Z]+)\(" +re_vk_function_device:str = r"vk[a-zA-Z]+\(((?:device)|(?:vk.getDevice\(\))), " +def vk_function(line:str, function:str) -> str: + match = re.search(re_vk_function_device, line) + if match: + new_function:str = match.groups()[0] + "." + case_first_letter_low(function.removeprefix("vk")) + return line.replace(match.groups()[0] + ", ", "").replace(function, new_function) + else: + new_function = "vk::" + case_first_letter_low(function.removeprefix("vk")) + return line.replace(function, new_function) + +re_vk_type: str = new_token_begin_chars + r"(Vk[A-Z][a-zA-Z]+)" +def vk_type(line:str, type_:str) -> str: + new_type = "vk::" + type_.removeprefix("Vk") + return line.replace(type_, new_type) + +# 0 -> name, 1 -> suffixes, 2 -> iteration +re_vk_enum_name: str = "Vk([a-zA-Z]+?)(?:FlagBits)?((?:KHR)|(?:EXT)|(?:AMD)|(?:NV))*([2-9]?)$" +re_vk_enum_val: str = new_token_begin_chars + r"(VK_(?:[A-Z0-9]+_)+([A-Z0-9]+))" +def vk_enum_val(line: str, enum_val: str) -> str: + new_enum_val = "vk::" + + # name is VkName[FlagBits][Suffix][2] -> VK_NAME[_BIT][_SUFFIX][2] + + tag = vk_soup.find(attrs={"name":enum_val}) + if not tag: + print("skipping", enum_val, "(not found)") + return line + enum_val_2 = case_to_camel(enum_val) + parent = tag.parent + + if not parent.attrs.__contains__("name"): + print("skipping", enum_val, "(no parent name)") + return line + + # print(enum_val_2) + # print(parent["name"], parent["type"]) + + match = re.search(re_vk_enum_name, parent["name"]) + if match: + name = match.groups()[0] + suffixes = match.groups()[1] + iteration = match.groups()[2] + # enum name + new_enum_val += parent["name"].removeprefix("Vk") + "::" + # enum val name + print("\tenum_val_2", enum_val_2) + print("\tname", name) + print("\tparent['name']", parent['name']) + print("\tsuffixes", suffixes) + print("\titeration", iteration) + new_enum_val += "e" + enum_val_2.removeprefix("Vk" + name) + if suffixes: + new_enum_val = new_enum_val.removesuffix(case_to_camel(suffixes)) + new_enum_val = new_enum_val.removesuffix("Bit") + else: + print("skipping", enum_val, "(no parent name match)") + return line + print(new_enum_val, "\n") + return line.replace(enum_val, new_enum_val) + +def vk_str(line, m): + # same match with different group + match = re.search(r"STR_VK_[A-Z]+\((.+)\)", line) + if match: + return line.replace(m, match.groups()[0]) + else: + return line + + +# f = f(line, match_group) -> line +re_and_f = { + re_vk_function: vk_function, + re_vk_type: vk_type, + re_vk_enum_val: vk_enum_val, + r"(STR_VK_[A-Z]+\(.+\))": vk_str, +} + +re_vk_struct_init: str = r"vk::[a-zA-Z2]+ [a-zA-Z]+ ?\{\};" +def vk_struct_init(s): + return s.replace("};", "").replace("{", " {").replace(" {", " {") + +re_vk_struct_member_init: str = r"([a-zA-Z]+(?:(?:CI)|(?:AI)|(?:I)|(?:MI)))\.[a-zA-Z0-9]+ = .+;" +def vk_struct_member_init(s): + match= re.search(re_vk_struct_member_init, s) + if match: + return s.replace(match.groups()[0], "\t").replace(";", ",") + else: + return s + +# f = f(line) -> line +re_no_group_and_f = { + "#include ": lambda l : "#define VULKAN_HPP_NO_CONSTRUCTORS\n" + l.replace("vulkan_core.h>", "vulkan.hpp>"), + r"\.sType ?=": lambda l : delete(l), + "VK_SUCCESS": lambda l : l.replace("VK_SUCCESS", "vk::Result::eSuccess"), + re_vk_struct_init: vk_struct_init, + re_vk_struct_member_init: vk_struct_member_init, +} + + +def transform_file(filename:str, outfilename): + """ + """ + + print("transform_file:", filename) + if not path.isfile(filename): error("File does not exist:" + filename) + + with open(filename, "r") as file: + lines = file.readlines() + + for i in range(len(lines)): + line = lines[i] + for reg, f in re_and_f.items(): + for m in re.finditer(reg, line): + line = f(line, m.groups()[0]) + + line = line + for reg, f in re_no_group_and_f.items(): + if re.search(reg, line): + line = f(line) + lines[i] = line + + with open(outfilename, "w") as file: + file.writelines(lines) + + + + # with open(filename, "w") as file: + # file.writelines(lines) + + +def process_path(path_): + # print("Processing path:", path_) + if path.isfile(path_) and path.splitext(path_)[-1] in filetypes: + makedirs("new/" + path.dirname(path_), exist_ok=True) + transform_file(path_, "new/" + path_) + else: + if path.isdir(path_): + for p in listdir(path_): + process_path(path_ + "/" + p) + + + + +def print_help(): + print(""" +-h --help help +--no-delete comment lines instead of deleting them""" +) + +def missing_arg(arg): + print("Missing argument for", arg) + exit(1) + +if __name__ == "__main__": + i = 1 + while i in range(1, len(argv)): + if argv[i] == "--help" or argv[i] == "-h": + print_help() + exit(0) + elif argv[i] == "--no-delete": + no_delete = True + i += 1 + + init_enum_names() + process_path(path.curdir) + + + +