123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610 |
- import re
- import os
- script_directory = os.path.dirname(os.path.abspath(__file__))
- input_path = os.path.join(script_directory, 'spine-cpp-lite.h')
- with open(input_path, 'r') as file:
- file_contents = file.read()
- supported_types_to_swift_types = {
- 'void *': 'UnsafeMutableRawPointer',
- 'const utf8 *': 'String?',
- 'uint64_t': 'UInt64',
- 'float *': 'Float?',
- 'float': 'Float',
- 'int32_t': 'Int32',
- 'utf8 *': 'String?',
- 'int32_t *': 'Int32?',
- 'uint16_t *': 'UInt16',
- 'spine_bool': 'Bool'
- }
- def read_spine_types(data):
- types_start = data.find('// @start: opaque_types') + len('// @start: opaque_types')
- types_end = data.find('// @end: paque_types')
- types_section = data[types_start:types_end]
- return re.findall(r'SPINE_OPAQUE_TYPE\(([^)]+)\)', types_section)
- def read_spine_function_declarations(data):
- declarations_start = data.find('// @start: function_declarations') + len('// @start: function_declarations')
- declarations_end = data.find('// @end: function_declarations')
- declarations_section = data[declarations_start:declarations_end]
- lines = declarations_section.split('\n')
- filtered_lines = []
- ignore_next = False
- next_returns_optional = False
- for line in lines:
- if ignore_next:
- ignore_next = False
- continue
-
- line = line.strip()
- if next_returns_optional:
- next_returns_optional = False
- line = line + "?"
-
- if not line.strip().startswith('//') and line.strip() != '':
- filtered_lines.append(line)
- if line.startswith('//') and '@ignore' in line:
- ignore_next = True
- elif line.startswith('//') and '@optional' in line:
- next_returns_optional = True
-
- function_declaration = [
- line.replace('SPINE_CPP_LITE_EXPORT', '').strip()
- for line in filtered_lines
- ]
- return function_declaration
- def read_spine_enums(data):
- enums_start = data.find('// @start: enums') + len('// @start: enums')
- enums_end = data.find('// @end: enums')
- enums_section = data[enums_start:enums_end]
- return re.findall(r"typedef enum (\w+) \{", enums_section)
- class SpineObject:
- def __init__(self, name, functions):
- self.name = name
- self.functions = functions
- self.function_names = {function.name for function in functions}
- self.var_name = "wrappee"
- def __str__(self):
- return f"SpineObject: name: {self.name}, functions: {self.functions}"
-
- class SpineFunction:
- def __init__(self, return_type, name, parameters, returns_optional):
- self.return_type = return_type
- self.name = name
- self.parameters = parameters
- self.returns_optional = returns_optional
- def isReturningSpineClass(self):
- return self.return_type.startswith("spine_") and self.return_type != "spine_bool" and self.return_type not in enums
- def __str__(self):
- return f"SpineFunction(return_type: {self.return_type}, name: {self.name}, parameters: {self.parameters}, returns_optional: {self.returns_optional})"
-
- def __repr__(self):
- return self.__str__()
- class SpineParam:
- def __init__(self, type, name):
- self.type = type
- self.name = name
- def isSpineClass(self):
- return self.type.startswith("spine_") and self.type != "spine_bool" and self.type not in enums
- def __str__(self):
- return f"SpineParam(type: {self.type}, name: {self.name})"
-
- def __repr__(self):
- return self.__str__()
- def parse_function_declaration(declaration):
- returns_optional = declaration.endswith("?")
- # Strip semicolon and extra whitespace
- declaration = declaration.strip('?').strip(';').strip()
-
- # Use regex to split the declaration into parts
- # Regex explanation:
- # ^([\w\s\*]+?)\s+ - Capture the return type, possibly including spaces and asterisks (non-greedy)
- # ([\w]+) - Capture the function name (alphanumeric and underscores)
- # \((.*)\) - Capture the argument list in entirety
- match = re.match(r'^(\S.+?\s*\*?\s*)([\w]+)\s*\((.*)\)$', declaration)
- if not match:
- return "Invalid function declaration"
-
- return_type, function_name, params = match.groups()
- params = params.strip()
- parameters = []
- if params:
- # Splitting each argument on comma
- param_list = params.split(',')
- for param in param_list:
-
- param_parts = []
- if '*' in param: # Split at the pointer and add it as a suffix to the type
- param_parts = param.rsplit('*', 1)
- param_parts[0] = param_parts[0] + '*'
- else: # Assuming type and name are separated by space and taking the last space as the separator
- param_parts = param.rsplit(' ', 1)
- param_type, param_name = param_parts
- spine_param = SpineParam(type = param_type.strip(), name = param_name.strip())
- parameters.append(spine_param)
-
- return SpineFunction(
- return_type = return_type.strip(),
- name = function_name.strip(),
- parameters = parameters,
- returns_optional = returns_optional
- )
- types = read_spine_types(file_contents)
- function_declarations = read_spine_function_declarations(file_contents)
- enums = read_spine_enums(file_contents)
- sorted_types = sorted(types, key=len, reverse=True) # Sorted by legth descending so we can match longest prefix.
- spine_functions = [
- parse_function_declaration(function_declaration)
- for function_declaration in function_declarations
- ]
- objects = []
- for type in sorted_types:
- object_functions = []
- hits = set() ## Keep track of hits and remove them for next object
- for function_declaration in function_declarations:
- spine_function = parse_function_declaration(function_declaration)
- if spine_function.name.startswith(type):
- hits.add(function_declaration);
- object_functions.append(spine_function);
-
- object = SpineObject(name = type, functions = object_functions);
- objects.append(object)
- function_declarations = [item for item in function_declarations if item not in hits]
- def snake_to_camel(snake_str):
- # Split the string by underscore
- parts = snake_str.split('_')
- # Return the first part lowercased and concatenate capitalized subsequent parts
- return parts[0] + ''.join(word.capitalize() for word in parts[1:])
- def snake_to_title(snake_str):
- # Split the string at underscores
- words = snake_str.split('_')
- # Capitalize the first letter of each word
- words = [word.capitalize() for word in words]
- # Join the words into a single string without any separator
- title_str = ''.join(words)
- return title_str
- inset = " "
- class SwiftTypeWriter:
- def __init__(self, type):
- self.type = type
-
- def write(self):
- parameter_type = supported_types_to_swift_types.get(self.type)
- if parameter_type is None:
- parameter_type = snake_to_title(self.type.replace("spine_", ""))
-
- if parameter_type.endswith(" *"):
- parameter_type = f"{parameter_type[:-2]}"
-
- return parameter_type
-
- class SwiftParamWriter:
- def __init__(self, param):
- self.param = param
-
- def write(self):
- type = SwiftTypeWriter(type = self.param.type).write()
- return f"{snake_to_camel(self.param.name)}: {type}"
- class SwiftFunctionBodyWriter:
- def __init__(self, spine_object, spine_function, is_setter, is_getter_optional):
- self.spine_object = spine_object
- self.spine_function = spine_function
- self.is_setter = is_setter
- self.is_getter_optional = is_getter_optional
- def write(self):
- body = ""
- num_function_name = self.spine_function.name.replace("get_", "get_num_")
- swift_return_type_is_array = "get_" in self.spine_function.name and num_function_name in self.spine_object.function_names
-
- spine_params = self.spine_function.parameters;
- body = ""
- if "dispose" in self.spine_function.name:
- body += self.write_dispose_call()
-
- function_call = self.write_c_function_call(spine_params)
- if swift_return_type_is_array:
- body += self.write_array_call(num_function_name, function_call)
- else:
- if not self.spine_function.return_type == "void":
- body += "return "
- if self.spine_function.isReturningSpineClass():
- function_prefix = f"{self.spine_object.name}_"
- function_name = self.spine_function.name.replace(function_prefix, "", 1)
- if "find_" in function_name or self.spine_function.returns_optional:
- body += function_call
- body += ".flatMap { .init($0"
- if self.spine_function.return_type in enums:
- body += ".rawValue"
- body += ") }"
- else:
- body += ".init("
- body += function_call
- if self.spine_function.return_type in enums:
- body += ".rawValue"
- body += ")"
-
- else:
- body += function_call
-
- if self.spine_function.return_type == "const utf8 *" or self.spine_function.return_type == "utf8 *":
- body += ".flatMap { String(cString: $0) }"
- if self.spine_function.return_type == "int32_t *" or self.spine_function.return_type == "float *":
- body += ".flatMap { $0.pointee }"
- return body
-
- def write_c_function_call(self, spine_params):
- function_call = ""
- function_call += f"{self.spine_function.name}"
- function_call += "("
- # Replace name with ivar name
- spine_params_with_ivar_name = spine_params
- if spine_params_with_ivar_name and spine_params_with_ivar_name[0].type == self.spine_object.name:
- spine_params_with_ivar_name[0].name = self.spine_object.var_name
-
- if self.is_setter and len(spine_params_with_ivar_name) == 2:
- spine_params_with_ivar_name[1].name = "newValue"
- if self.is_getter_optional:
- spine_params_with_ivar_name[1].name += "?"
-
- swift_param_names = []
- for idx, spine_param in enumerate(spine_params_with_ivar_name):
- if spine_param.isSpineClass() and idx > 0:
- swift_param_names.append(f"{spine_param.name}.wrappee")
- elif spine_param.type == "spine_bool":
- swift_param_names.append(f"{spine_param.name} ? -1 : 0")
- else:
- swift_param_names.append(spine_param.name)
-
- function_call += ", ".join(swift_param_names)
- function_call += ")"
- if self.spine_function.return_type == "spine_bool":
- function_call += " != 0"
- return function_call
-
- def write_array_spine_class(self, num_function_name, function_call):
- array_call = f"let ptr = {function_call}"
- array_call += "\n"
- array_call += inset + inset
- array_call += "guard let validPtr = ptr else { return [] }"
- array_call += "\n"
- array_call += inset + inset
- array_call += f"let num = Int({num_function_name}({self.spine_object.var_name}))"
- array_call += "\n"
- array_call += inset + inset
- array_call += "let buffer = UnsafeBufferPointer(start: validPtr, count: num)"
- array_call += "\n"
- array_call += inset + inset
- array_call += "return buffer.compactMap {"
- array_call += "\n"
- array_call += inset + inset + inset
- array_call += "$0.flatMap { .init($0) }"
- array_call += "\n"
- array_call += inset + inset
- array_call += "}"
- return array_call
-
- def write_array_call(self, num_function_name, function_call):
- if self.spine_function.isReturningSpineClass():
- return self.write_array_spine_class(num_function_name, function_call)
- array_call = f"let ptr = {function_call}"
- array_call += "\n"
- array_call += inset + inset
- array_call += "guard let validPtr = ptr else { return [] }"
- array_call += "\n"
- array_call += inset + inset
- array_call += f"let num = Int({num_function_name}({self.spine_object.var_name}))"
- array_call += "\n"
- array_call += inset + inset
- array_call += "let buffer = UnsafeBufferPointer(start: validPtr, count: num)"
- array_call += "\n"
- array_call += inset + inset
- array_call += "return Array(buffer)"
- return array_call
-
- def write_dispose_call(self):
- dispose_body = "if disposed { return }"
- dispose_body += "\n"
- dispose_body += inset + inset
- dispose_body += "disposed = true"
- dispose_body += "\n"
- dispose_body += inset + inset
- return dispose_body
- class SwiftFunctionWriter:
- def __init__(self, spine_object, spine_function, spine_setter_function):
- self.spine_object = spine_object
- self.spine_function = spine_function
- self.spine_setter_function = spine_setter_function
- def write(self):
- function_prefix = f"{self.spine_object.name}_"
- function_name = self.spine_function.name.replace(function_prefix, "", 1)
- is_getter = (function_name.startswith("get_") or function_name.startswith("is_")) and len(self.spine_function.parameters) < 2
- num_function_name = self.spine_function.name.replace("get_", "get_num_")
- swift_return_type_is_array = "get_" in self.spine_function.name and num_function_name in self.spine_object.function_names
- swift_return_type_writer = SwiftTypeWriter(type = self.spine_function.return_type)
- swift_return_type = swift_return_type_writer.write()
- if swift_return_type_is_array:
- swift_return_type = f"[{swift_return_type}]"
- function_string = inset
- if is_getter:
-
- function_string += self.write_computed_property_signature(function_name, swift_return_type)
- if self.spine_setter_function:
- function_string += " {\n"
- function_string += inset + inset
- function_string += "get"
- else:
- function_string += self.write_method_signature(function_name, swift_return_type)
-
- function_string += " {"
- function_string += "\n"
- function_string += inset + inset
- if self.spine_setter_function:
- function_string += inset
-
- function_string += SwiftFunctionBodyWriter(spine_object = self.spine_object, spine_function = self.spine_function, is_setter=False, is_getter_optional=False).write()
- if self.spine_setter_function:
- function_string += "\n"
- function_string += inset + inset + "}"
- function_string += "\n"
- function_string += inset + inset + "set {"
- function_string += "\n"
- function_string += inset + inset + inset
- function_string += SwiftFunctionBodyWriter(spine_object = self.spine_object, spine_function = self.spine_setter_function, is_setter=True, is_getter_optional=self.spine_function.returns_optional).write()
- function_string += "\n"
- function_string += inset + inset + "}"
- function_string += "\n"
- function_string += inset + "}"
- function_string += "\n"
- return function_string
-
- def write_computed_property_signature(self, function_name, swift_return_type):
- property_name = snake_to_camel(function_name.replace("get_", ""))
- property_string = f"public var {property_name}: {swift_return_type}"
- if self.spine_function.returns_optional:
- property_string += "?"
- return property_string
- def write_method_signature(self, function_name, swift_return_type):
- function_string = ""
-
- if not self.spine_function.return_type == "void":
- function_string += "@discardableResult"
- function_string += "\n"
- function_string += inset
- function_string += f"public func {snake_to_camel(function_name)}"
- function_string += "("
-
- spine_params = self.spine_function.parameters;
- # Filter out ivar
- if spine_params and spine_params[0].type == self.spine_object.name:
- spine_params_without_ivar = spine_params[1:]
- else:
- function_string = function_string.replace("public func ", "public static func ")
- spine_params_without_ivar = spine_params
- swift_params = [
- SwiftParamWriter(param = spine_param).write()
- for spine_param in spine_params_without_ivar
- ]
- function_string += ", ".join(swift_params)
- function_string += ")"
- if not self.spine_function.return_type == "void":
- function_string += f" -> {swift_return_type}"
- if "find_" in function_name or self.spine_function.returns_optional:
- function_string += "?"
- return function_string
- class SwiftObjectWriter:
- def __init__(self, spine_object):
- self.spine_object = spine_object
- def write(self):
- ivar_type = self.spine_object.name
- ivar_name = self.spine_object.var_name
- class_name = snake_to_title(self.spine_object.name.replace("spine_", ""))
-
- object_string = f"@objc(Spine{class_name})"
- object_string += "\n"
- object_string += "@objcMembers"
- object_string += "\n"
- object_string += f"public final class {class_name}: NSObject"
- object_string += " {"
- object_string += "\n"
- object_string += "\n"
- object_string += inset
- object_string += f"internal let {ivar_name}: {ivar_type}"
- object_string += "\n"
- if any("dispose" in function_name for function_name in self.spine_object.function_names):
- object_string += inset
- object_string += f"internal var disposed = false"
- object_string += "\n"
- object_string += "\n"
- object_string += inset
- object_string += f"internal init(_ {ivar_name}: {ivar_type})"
- object_string += " {"
- object_string += "\n"
- object_string += inset + inset
- object_string += f"self.{ivar_name} = {ivar_name}"
- object_string += "\n"
- object_string += inset + inset
- object_string += "super.init()"
- object_string += "\n"
- object_string += inset
- object_string += "}"
- object_string += "\n"
- object_string += "\n"
-
- object_string += inset
- object_string += "public override func isEqual(_ object: Any?) -> Bool"
- object_string += " {"
- object_string += "\n"
- object_string += inset + inset
- object_string += f"guard let other = object as? {class_name} else {{ return false }}"
- object_string += "\n"
- object_string += inset + inset
- object_string += f"return self.{ivar_name} == other.{ivar_name}"
- object_string += "\n"
- object_string += inset
- object_string += "}"
- object_string += "\n"
- object_string += "\n"
-
- object_string += inset
- object_string += "public override var hash: Int"
- object_string += " {"
- object_string += "\n"
- object_string += inset + inset
- object_string += "var hasher = Hasher()"
- object_string += "\n"
- object_string += inset + inset
- object_string += f"hasher.combine(self.{ivar_name})"
- object_string += "\n"
- object_string += inset + inset
- object_string += "return hasher.finalize()"
- object_string += "\n"
- object_string += inset
- object_string += "}"
- object_string += "\n"
- object_string += "\n"
-
- filtered_spine_functions = [spine_function for spine_function in self.spine_object.functions if not "_get_num_" in spine_function.name]
- spine_functions_by_name = {}
- getter_names = []
- setter_names = []
- method_names = []
-
- for spine_function in filtered_spine_functions:
- spine_functions_by_name[spine_function.name] = spine_function
- if ("_get_" in spine_function.name or "_is_" in spine_function.name) and len(spine_function.parameters) == 1:
- getter_names.append(spine_function.name)
- elif "_set_" in spine_function.name and len(spine_function.parameters) == 2:
- setter_names.append(spine_function.name)
- else:
- method_names.append(spine_function.name)
- get_set_pairs = []
- for setter_name in setter_names:
- getter_name_get = setter_name.replace("_set_", "_get_")
- getter_name_is = setter_name.replace("_set_", "_is_")
- if getter_name_get in getter_names:
- getter_names.remove(getter_name_get)
- get_set_pairs.append((getter_name_get, setter_name))
- elif getter_name_is in getter_names:
- getter_names.remove(getter_name_is)
- get_set_pairs.append((getter_name_is, setter_name))
- else:
- method_names.append(setter_name) # Coul not find getter by name. Move to methods
-
- # print(get_set_pairs)
- for getter_name in getter_names:
- spine_function = spine_functions_by_name[getter_name]
- object_string += SwiftFunctionWriter(spine_object = self.spine_object, spine_function = spine_function, spine_setter_function=None).write()
- object_string += "\n"
-
- for get_set_pair in get_set_pairs:
- getter_function = spine_functions_by_name[get_set_pair[0]]
- setter_function = spine_functions_by_name[get_set_pair[1]]
- object_string += SwiftFunctionWriter(spine_object = self.spine_object, spine_function = getter_function, spine_setter_function=setter_function).write()
- object_string += "\n"
- for method_name in method_names:
- spine_function = spine_functions_by_name[method_name]
- object_string += SwiftFunctionWriter(spine_object = self.spine_object, spine_function = spine_function, spine_setter_function=None).write()
- object_string += "\n"
- object_string += "}"
- return object_string
- class SwiftEnumWriter:
- def __init__(self, spine_enum):
- self.spine_enum = spine_enum
- def write(self):
- # TODO: Consider leaving spine prefix (objc) or map whole c enum to swift/objc compatible enum
- return f"public typealias {snake_to_title(self.spine_enum.replace("spine_", ""))} = {self.spine_enum}"
- print("import Foundation")
- print("import SpineCppLite")
- print("")
- for enum in enums:
- print(SwiftEnumWriter(spine_enum=enum).write())
- print("")
-
- for object in objects:
- print(SwiftObjectWriter(spine_object = object).write())
- print("")
|