1#!/usr/bin/env python
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
10import json
11import os
12import re
13import stat
14from shutil import copyfile, move
15
16import CppHeaderParser
17
18
19class HeaderParser:
20    """
21    Extract file path, file name, includes, enums, unions, structs, interfaces and callbacks
22    from CppHeaderParser
23    """
24
25    def __init__(self):
26        self._header_dict = {
27            "name": "",
28            "path": "",
29            "import": [],
30            "enum": [],
31            "union": [],
32            "struct": [],
33            "typedef": [],
34            "interface": [],
35            "callback": []
36        }
37        self._rand_name_count = 0
38
39    def parse(self, header_file):
40        try:
41            hjson = json.loads(CppHeaderParser.CppHeader(header_file).toJSON())
42        except CppHeaderParser.CppParseError:
43            back_file = self._pre_handle(header_file)
44            hjson = json.loads(CppHeaderParser.CppHeader(header_file).toJSON())
45            move(back_file, header_file)
46        finally:
47            pass
48
49        try:
50            self._extract_path_and_file(header_file)
51            self._extract_include(hjson["includes"])
52            self._extract_enum(hjson["enums"])
53            for i in hjson["classes"]:
54                self._extract_union(hjson["classes"][i])
55                self._extract_struct(hjson["classes"][i])
56                self._extract_interface(hjson["classes"][i])
57            self._extract_typedef(hjson["typedefs"])
58            return self._header_dict
59        except KeyError:
60            pass
61        finally:
62            pass
63
64    @staticmethod
65    def _pre_handle(header_file):
66        back_file = header_file + ".back"
67        copyfile(header_file, back_file)
68        with open(header_file, 'r') as f:
69            new_lines = ""
70            for line in f:
71                tt = re.match(r".*enum *((\w+) *\*) *\w* *;", line)
72                if tt:
73                    new_line = line.replace(tt[1], tt[2] + "_ENUM_POINTER ")
74                    new_lines += new_line
75                    continue
76                tt = re.match(r".*(\[\w* *\(.*\) *]) *", line)
77                if tt:
78                    new_line = line.replace(tt[1], "[]")
79                    new_lines += new_line
80                    continue
81                else:
82                    new_lines += line
83        flags = os.O_RDWR | os.O_CREAT
84        modes = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH
85        with os.fdopen(os.open(header_file, flags, modes),
86                       "w", encoding="utf-8") as file:
87            file.write(new_lines)
88        return back_file
89
90    @staticmethod
91    def _has_function_pointer(jvs):
92        for jv in jvs:
93            if jv["function_pointer"] > 0:
94                return True
95        return False
96
97    def _checkout_function_pointer_param(self, params):
98        if params == '':
99            return []
100        tt = re.match(r"(typedef )* *\w+ \( \* \) \(( .* )\)", params)
101        if tt:
102            params = tt[2].strip() + ","
103        else:
104            print("error cant analyze function pointer params: ", params)
105            return []
106        ret = []
107        while params:
108            tt = re.match(" *(const )*(struct |enum |union )* *", params)
109
110            if not tt:
111                print("error cant analyze param :[%s]" % params)
112                break
113
114            params = params[tt.regs[0][1]:]
115            tt = re.match(r"((unsigned )*[a-zA-Z0-9_:]+( *\** *\**)) *", params)
116            if not tt:
117                ret.append({"name": '', "type": params.strip(',').strip()})
118                break
119
120            param_type = params[tt.regs[1][0]:tt.regs[1][1]]  # 参数类型
121            params = params[tt.regs[0][1]:]
122            tt = re.match("([a-zA-Z0-9_]+) *(,)", params)
123            if tt:
124                param_name = params[tt.regs[1][0]:tt.regs[1][1]]
125                params = params[tt.regs[0][1]:]
126            else:  # 没有定义变量名的,设置一个默认变量名
127                param_name = "rand_name_%d" % self._rand_name_count
128                self._rand_name_count += 1
129                params = params[params.index(",") + 1:]
130            ret.append({"name": param_name, "type": param_type.strip()})
131        return ret
132
133    def _extract_path_and_file(self, path):
134        self._header_dict["path"], self._header_dict["name"] = os.path.split(path)
135
136    def _extract_include(self, includes):
137        """
138        Extract imports from includes
139        """
140        for include in includes:
141            if '<' not in include:  # ignore system files
142                self._header_dict.get("import").append(include[1:-1])
143
144    def _extract_enum(self, enums):
145        """
146        Extract enums from enums
147        """
148        for enm in enums:
149            if "name" in enm:
150                enum_dict = {"name": enm["name"], "members": []}
151            else:
152                enum_dict = {"name": "LostName_%d" % self._rand_name_count, "members": []}
153                self._rand_name_count += 1
154            for value in enm["values"]:
155                v_value = value["value"]
156                if isinstance(v_value, int):
157                    enum_dict["members"].append({"name": value["name"], "value": v_value})
158                    continue
159                tt = re.match(r"0x|0X|\(|-", v_value)
160                if tt:
161                    errmsg = "unexpected '%s'" % tt[0]
162                    v_value = v_value + " // " + errmsg
163                    print("[HeaderParser]: %s[line %d] " % (enm["filename"], enm["line_number"]), errmsg)
164                enum_dict["members"].append({"name": value["name"], "value": v_value})
165            self._header_dict.get("enum").append(enum_dict)
166
167    def _extract_union(self, stack):
168        """
169        Extract unions from classes
170        """
171        union_dict = {}
172        if stack["declaration_method"] == "union":
173            union_dict["name"] = stack["name"].split(" ")[-1]
174            union_dict["type"] = "union"
175            union_dict["members"] = []
176            file_name = self._header_dict.get("path") + "/" + self._header_dict.get("name")
177            for mb in stack["members"]:
178                union_dict.get("members").append({
179                    "file_name": file_name,
180                    "line_number": mb["line_number"],
181                    "name": mb["name"], "type": mb["type"]
182                })
183            self._header_dict.get("union").append(union_dict)
184
185    def _extract_struct(self, stack):
186        """
187        Extract structs from structs or classes that have no methods
188        """
189        struct_dict = {}
190        if stack["declaration_method"] not in ["struct", "class"]:
191            return
192        if len(stack["methods"]["public"]) != 0:
193            return
194        if self._has_function_pointer(stack["properties"]["public"]):
195            return
196        struct_dict["name"] = stack["name"]
197        struct_dict["type"] = "struct"
198        struct_dict["members"] = []
199        file_name = self._header_dict.get("path") + "/" + self._header_dict.get("name")
200        for mb in stack["properties"]["public"]:
201            if "enum_type" in mb:
202                struct_dict.get("members").append({
203                    "file_name": file_name,
204                    "line_number": mb["line_number"],
205                    "name": mb["name"],
206                    "type": mb["enum_type"]["name"]
207                })
208            elif mb["array"] == 1:
209                struct_dict.get("members").append({
210                    "file_name": file_name,
211                    "line_number": mb["line_number"],
212                    "name": mb["name"],
213                    "type": mb["type"] + " *"
214                })
215            else:
216                struct_dict.get("members").append({
217                    "file_name": file_name,
218                    "line_number": mb["line_number"],
219                    "name": mb["name"],
220                    "type": mb["type"]
221                })
222        self._header_dict.get("struct").append(struct_dict)
223
224    def _extract_interface(self, stack):
225        """
226        Extract interfaces from structs or classes which have some methods
227        """
228        interface_dict = {}
229        if stack["declaration_method"] not in ["struct", "class"]:
230            return
231        # 带函数,或变量中包含函数指针
232        if len(stack["methods"]["public"]) <= 0 and not self._has_function_pointer(stack["properties"]["public"]):
233            return
234        interface_dict["name"] = stack["name"]
235        interface_dict["members"] = []
236        for mb in stack["methods"]["public"]:
237            if mb["name"] in (stack["name"], "DECLARE_INTERFACE_DESCRIPTOR"):
238                continue
239            params = []
240            for param in mb["parameters"]:
241                para_name = param["name"]
242                if para_name == '':
243                    para_name = "rand_name_%d" % self._rand_name_count
244                    self._rand_name_count += 1
245                params.append({"name": para_name, "type": param["type"]})
246            interface_dict.get("members").append({
247                "name": mb["name"],
248                "params": params,
249                "file_name": self._header_dict.get("path") + "/" + self._header_dict.get("name"),
250                "line_number": mb["line_number"]})
251        for mb in stack["properties"]["public"]:
252            if mb["function_pointer"] <= 0:
253                continue
254            interface_dict.get("members").append({
255                "name": mb["name"],
256                "params": self._checkout_function_pointer_param(mb["type"]),
257                "file_name": self._header_dict.get("path") + "/" + self._header_dict.get("name"),
258                "line_number": mb["line_number"]})
259        self._header_dict.get("interface").append(interface_dict)
260
261    def _extract_typedef(self, typedefs):
262        """
263        Extract typedef from global typedefs
264        e.g.
265        "typedefs": {
266            "HotPlugCallback": "typedef void ( * ) ( uint32_t devId , bool connected , void * data )",
267            "AudioHandle": "void *"
268        }
269        """
270        for td in typedefs:
271            if "typedef" in typedefs[td]:
272                error_comment = "/* unsupported function pointer type: %s */" % (td)
273                self._header_dict.get("typedef").append({"name": td, "type": error_comment})
274            else:
275                self._header_dict.get("typedef").append({"name": td, "type": typedefs[td]})
276