• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# !/usr/bin/env python3
2# coding=utf-8
3"""
4* Copyright (c) 2022 Shenzhen Kaihong Digital Industry Development Co., Ltd.
5*
6* HDF is dual licensed: you can use it either under the terms of
7* the GPL, or the BSD license, at your option.
8* See the LICENSE file in the root of this repository for complete details.
9"""
10
11import argparse
12import os
13import re
14
15import pip
16
17
18def lost_module(module_name):
19    print("""
20need %s module, try install first:
21
22    pip install %s""" % (module_name, module_name))
23    exit()
24
25
26try:
27    import CppHeaderParser
28    from _header_parser import HeaderParser
29except ImportError:
30    pip.main(["--disable-pip-version-check", "install", "robotpy-cppheaderparser"])
31    try:
32        import CppHeaderParser
33        from _header_parser import HeaderParser
34    except ImportError:
35        HeaderParser = None
36        lost_module("robotpy-cppheaderparser")
37
38
39class IDLGenerator:
40    def __init__(self):
41        self._idl = ""
42        self._output_path = ""
43        self._file_list = []
44        self._key_list = {}
45        self._typedef_list = {}
46        self._parse_results = {}
47
48    def generate(self):
49        try:
50            self._parse_option()
51            self._parse_header()
52            for i in self._parse_results:
53                self._generate_type(self._parse_results[i])
54                self._generate_interface(self._parse_results[i])
55        except KeyError:
56            pass
57
58    def _parse_option(self):
59        parser = argparse.ArgumentParser(description="Compile C/C++ header files and generate .idl files.")
60        parser.add_argument("-v", "--version", help="Display version information", action="store_true")
61        parser.add_argument("-f", "--file", metavar="<*.h>", required=True, action='append',
62                            help="Compile the C/C++ header file")
63        parser.add_argument("-o", "--out", metavar="<directory>", default=".", required=True,
64                            help="Place generated .idl files into the <directory>")
65        args = parser.parse_args()
66
67        self._file_list = args.file
68        self._output_path = args.out
69
70    def _search_file(self, root_path, file_name):
71        file_list = os.listdir(root_path)
72        for file in file_list:
73            path = os.path.join(root_path, file)
74            if os.path.isdir(path):
75                file_path = self._search_file(path, file_name)
76                if file_path is not None:
77                    return file_path
78            if os.path.isfile(path):
79                if os.path.split(path)[1] == file_name:
80                    return path
81
82    def _parse_header(self):
83        try:
84            for file in self._file_list:
85                root_path, _ = os.path.split(file)  # 输入文件所在目录
86                file_list = [file]  # 需要解析的头文件列表,包括include包含的头文件
87                while len(file_list) > 0:
88                    include_file = []
89                    for header_file in file_list:
90                        result = HeaderParser().parse(header_file)
91                        if result is not None:
92                            self._parse_results[result.get("name")] = result
93                            for file_name in result["import"]:  # 把include的文件加入列表
94                                if file_name not in self._parse_results:  # 解析过的不重复解析
95                                    file_path = self._search_file(root_path, file_name)
96                                    if file_path is not None:
97                                        include_file.append(file_path)
98                    file_list = include_file
99
100            for i in self._parse_results:
101                for enum in self._parse_results[i]["enum"]:
102                    self._key_list[enum["name"]] = "enum"
103                for union in self._parse_results[i]["union"]:
104                    self._key_list[union["name"]] = "union"
105                for struct in self._parse_results[i]["struct"]:
106                    self._key_list[struct["name"]] = "struct"
107                for clas in self._parse_results[i]["interface"]:
108                    self._key_list[clas["name"]] = ""
109                for cb in self._parse_results[i]["callback"]:
110                    self._key_list[cb["name"]] = ""
111                for td in self._parse_results[i]["typedef"]:
112                    self._typedef_list[td["name"]] = td["type"]
113        except KeyError:
114            pass
115
116    def _generate_type(self, header):
117        if self._has_user_define_type(header):
118            self._install_package(header["path"])
119            self._install_import(header)
120            self._install_enum(header["enum"])
121            self._install_stack(header["union"])
122            self._install_stack(header["struct"])
123
124            self._write_file(header["path"], "Types.idl")
125
126    def _generate_interface(self, header):
127        for iface in header["interface"]:
128            self._install_package(header["path"])
129            self._install_import(header)
130            self._install_interface(iface)
131
132            self._write_file(header["path"], iface["name"] + ".idl")
133
134    def _install_package(self, file_path):
135        self._idl += "package " + self._get_package(file_path) + ";\n\n"
136
137    def _install_import(self, header):
138        try:
139            original_idl = self._idl
140            for file_name in header["import"]:
141                if file_name in self._parse_results:
142                    include_file = self._parse_results[file_name]
143                    if self._has_user_define_type(include_file):
144                        tt = re.search("import\\s+\\S+.Types", self._idl)
145                        if tt is None:
146                            self._idl += "import " + self._get_package(include_file['path']) + ".Types;\n"
147                    for iface in include_file["interface"]:
148                        self._idl += "import " + self._get_package(include_file['path']) + ".%s;\n" % iface["name"]
149                    for cb in include_file["callback"]:
150                        self._idl += "import " + self._get_package(include_file['path']) + ".%s;\n" % cb["name"]
151                else:
152                    self._idl += "// can't import %s\n" % file_name
153                    print("[IDLGenerator]: %s[line ?] can't find %s"
154                          % (os.path.normpath(header["path"] + "/" + header["name"]), file_name))
155
156            for cb in header['callback']:
157                self._idl += "import " + self._get_package(header['path']) + ".%s;\n" % cb["name"]
158
159            if original_idl != self._idl:
160                self._idl += "\n"
161        except KeyError:
162            pass
163
164    def _install_enum(self, enums):
165        for enum in enums:
166            self._idl += "enum %s {\n" % enum["name"]
167            for member in enum["members"]:
168                self._idl += "    %s = %s,\n" % (member["name"], member["value"])
169            self._idl += "};\n"
170
171    def _install_stack(self, stacks):
172        for stack in stacks:
173            self._idl += stack["type"] + " %s {\n" % stack["name"]
174            for member in stack["members"]:
175                param_type = self._swap_type_c2idl(member["type"])
176                if "unknown type" in param_type:
177                    print("[IDLGenerator]: %s[line %d] %s" % (
178                        os.path.normpath(member["file_name"]), member["line_number"], param_type))
179                self._idl += "    %s %s;\n" % (param_type, member["name"])
180            self._idl += "};\n"
181
182    def _install_interface(self, iface):
183        if re.search("[Cc]all[Bb]ack", iface["name"]):
184            self._idl += "[callback] "
185        self._idl += "interface %s {\n" % iface["name"]
186        for member in iface["members"]:
187            self._install_function(iface["name"], member)
188        self._idl += "}\n"
189
190    def _install_function(self, iface_name, member):
191        self._idl += "    %s(" % member["name"]
192        for i, param in enumerate(member["params"]):
193            tt = re.fullmatch(r"(enum)*(union)*(struct)* *%s *\** * *\**" % iface_name, param["type"])
194            if tt:
195                continue
196            param_type = self._swap_type_c2idl(param["type"])
197            if "unknown type" in param_type:
198                print("[IDLGenerator]: %s[line %d] %s" % (
199                    os.path.normpath(member["file_name"]), member["line_number"], param_type))
200            self._idl += "%s %s %s," % (
201                '* *' in param["type"] and "[out]" or "[in]",
202                param_type,
203                param["name"])
204        if self._idl.endswith(','):
205            self._idl = self._idl[:-1] + ");\n"
206        else:
207            self._idl += ");\n"
208
209    @staticmethod
210    def _convert_basic_type(c_type):
211        type_c2idl = [
212            [["bool"], "boolean"],
213            [["int8_t", "char"], "byte"],
214            [["int16_t"], "short"],
215            [["int32_t", "int"], "int"],
216            [["int64_t"], "long"],
217            [["float"], "float"],
218            [["double"], "double"],
219            [["std::string", "string", "cstring"], "String"],
220            [["uint8_t", "unsigned char"], "unsigned char"],
221            [["uint16_t", "unsigned short"], "unsigned short"],
222            [["uint32_t", "unsigned int"], "unsigned int"],
223            [["uint64_t", "unsigned long"], "unsigned long"],
224            [["void"], "void"]
225        ]
226        for cti in type_c2idl:
227            for ct in cti[0]:
228                tt = re.match(r"(const )* *%s *(\**) *" % ct, c_type)
229                if tt:
230                    idl_type = cti[1]
231                    if c_type.count('*') == 1:
232                        idl_type += '[]'
233                    return idl_type
234        return ""
235
236    def _convert_structure(self, c_type):
237        for type_name in self._key_list:
238            if "_ENUM_POINTER" in c_type:
239                c_type = c_type.replace("_ENUM_POINTER", " * ")
240            tt = re.fullmatch(r"(const )* *(enum)*(union)*(struct)* *%s *[*&]* * *[*&]*" % type_name, c_type)
241            if tt:
242                if len(self._key_list.get(type_name)) > 0:
243                    idl_type = self._key_list.get(type_name) + ' ' + type_name
244                else:
245                    idl_type = type_name
246                if c_type.count('*') == 1:
247                    idl_type += '[]'
248                return idl_type
249        return ""
250
251    def _convert_typedef(self, c_type):
252        for type_name in self._typedef_list:
253            tt = re.match(r"(const )* *%s *\** *" % type_name, c_type)
254            if tt:
255                if self._typedef_list.get(type_name).count('*') == 1:
256                    idl_type = self._typedef_list.get(type_name).split(' ')[0] + '[]'
257                else:
258                    idl_type = self._typedef_list.get(type_name)
259                return idl_type
260        return ""
261
262    def _convert_container_type(self, c_type):
263        type_pattern = " *(std::)*(struct *)*((unsigned *)*\\w+) *"
264        tt = re.match("(const *)* *(std::)*map *<%s, %s>" % (type_pattern, type_pattern), c_type)
265        if tt:
266            key_type = self._convert_basic_type(tt[5])
267            value_type = self._convert_basic_type(tt[9])
268            return "Map<%s, %s>" % (key_type == "" and tt[5] or key_type, value_type == "" and tt[9] or value_type)
269
270        tt = re.match("(const *)* *(std::)*vector *<%s>" % type_pattern, c_type)
271        if tt:
272            v_type = self._convert_basic_type(tt[5])
273            return "List<%s>" % (v_type == "" and tt[5] or v_type)
274        return ""
275
276    def _swap_type_c2idl(self, c_type):
277        idl_type = self._convert_basic_type(c_type)
278        if idl_type != "":
279            return idl_type
280
281        idl_type = self._convert_structure(c_type)
282        if idl_type != "":
283            return idl_type
284
285        idl_type = self._convert_typedef(c_type)
286        if idl_type != "":
287            return idl_type
288
289        idl_type = self._convert_container_type(c_type)
290        if idl_type != "":
291            return idl_type
292
293        idl_type = "/* unknown type: [%s] */" % c_type
294        return idl_type
295
296    def _write_file(self, file_path, file_name):
297        file = os.path.join(self._make_output_dir(file_path), file_name).replace('\\', '/')
298        with open(file, "w", encoding="utf8") as fp:
299            fp.write(self._idl)
300        print("Generate: --------------------- %s ---------------------\n" % os.path.normpath(file))
301        self._idl = ""
302
303    @staticmethod
304    def _split_path(file_path):
305        normpath = os.path.normpath(file_path)
306        drive, out_path = os.path.splitdrive(normpath)
307        out_path = out_path.replace('\\', '/')
308        new_path = out_path.strip('.').strip('/')
309        while new_path != out_path:
310            out_path = new_path
311            new_path = out_path.strip('.').strip('/')
312
313        return new_path
314
315    def _make_output_dir(self, file_path):
316        output_dir = self._output_path + '/' + self._split_path(file_path)
317        if not os.path.exists(output_dir):
318            os.makedirs(output_dir)
319        return output_dir
320
321    @staticmethod
322    def _has_user_define_type(h):
323        if len(h["enum"]) > 0 or len(h["union"]) > 0 or len(h["struct"]) > 0:
324            return True
325        return False
326
327    def _get_package(self, file_path):
328        path_name = re.split(r'[/\\]', self._split_path(file_path))
329        out_path = ""
330        for name in path_name:
331            out_path += name + '.'
332        return out_path.strip('.')
333
334
335if __name__ == "__main__":
336    generator = IDLGenerator()
337    generator.generate()
338