#!/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)