1#!/usr/bin/env python3
2# coding=utf-8
3#
4# Copyright (c) 2024 Huawei Device Co., Ltd.
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from typing import Any, Dict, Union
18from line_iterator import LineIterator
19from runtime_collections import add_to_statistics, add_to_custom_yamls
20from parse_namespace import parse_namespace
21from parse_enum import parse_enum_class
22from parse_struct import parse_struct
23from parse_using import parse_using
24from parse_define import parse_define_macros
25from parse_class import parse_class, parse_friend_class, parse_template_prefix
26from parse_method import parse_method_or_constructor
27from parse_arguments import parse_argument
28from log_tools import warning_log
29
30
31def deep_copy(data: Any) -> Any:
32    if isinstance(data, (dict, list)):
33        return data.copy()
34    return data
35
36
37class CppParser:
38    def __init__(self, data: str, namespace: str = "", parent_class_name: str = ""):
39        self.it = LineIterator(data)
40        self.parsed: Any = None
41        self.res: Dict[str, Any] = {}
42        self.template: Union[str, None] = None
43
44        self.parent_class_name = parent_class_name
45        self.namespace = namespace
46        self.current_modifier = ""
47
48    def parse(self) -> Dict[str, Any]:  # pylint: disable=R0912
49
50        while self.it.next_line():
51            # Skip "#include", "#ifndef", "#undef", "template"
52
53            if self.it.is_skip_line():
54                add_to_statistics("skip", self.it.current_line)
55
56            elif self.it.is_template():
57                self.it.end, self.template = parse_template_prefix(self.it.data, self.it.start)
58
59            # Namespaces
60            elif self.it.is_namespace():
61                self.it.end, self.parsed = parse_namespace(self.it.data, self.it.start)
62                self.res_update()
63
64            # Enum class
65            elif self.it.is_enum():
66                self.it.end, self.parsed = parse_enum_class(self.it.data, self.it.start)
67                self.res_append_namespace()
68
69            # Struct
70            elif self.it.is_struct():
71                self.it.end, self.parsed = parse_struct(self.it.data, self.it.start)
72                self.res_append("structs")
73
74            # Using
75            elif self.it.is_using():
76                self.it.end, self.parsed = parse_using(self.it.data, self.it.start)
77                self.res_append_in_modifier("usings")
78
79            # define macros (from class parser)
80            elif self.it.is_define_macro():
81                self.it.end, self.parsed = parse_define_macros(self.it.data, self.it.start)
82                self.res_append("macros")
83
84            # Known macroses (from class parser)
85            elif self.it.is_known_macros():
86                self.parsed = self.it.current_line
87                self.res_append("known_macroses")
88
89            # Private, public, protected modifier (from class parser)
90            elif self.it.is_access_modifier():
91                self.update_access_modifier()
92
93            # Friend class
94            elif self.it.is_firend_class():
95                self.it.end, self.parsed = parse_friend_class(self.it.data, self.it.start)
96                self.res_append("friends")
97
98            # Class forward declaration
99            elif self.it.is_class_forward_decl():
100                self.parsed = self.it.current_line.replace("class", "").strip(" ;")
101                self.res_append("class_forward_declaration")
102
103            # Class definition
104            elif self.it.is_class_definition():
105                self.it.end, self.parsed = parse_class(
106                    self.it.data, self.it.start, self.namespace, self.parent_class_name
107                )
108                self.res_append_class_definition()
109
110            # Function, method or constructor
111            elif self.it.is_method_or_constructor():
112                self.it.end, self.parsed = parse_method_or_constructor(self.it.data, self.it.start)
113                self.res_append_method_or_constructor()
114
115            # Field
116            elif self.it.is_field():
117                self.parsed = parse_argument(self.it.data[self.it.start : self.it.next_semicolon])
118                self.it.end = self.it.next_semicolon
119                self.res_append_field()
120
121            else:
122                add_to_statistics("unreachable", self.it.current_line)
123
124        return self.res
125
126    def res_append(self, key: str) -> None:
127        if not self.parsed:
128            return
129
130        self.parsed_update_template()
131        if key not in self.res:
132            self.res[key] = []
133        self.res[key].append(deep_copy(self.parsed))
134
135    def res_append_in_modifier(self, key: str) -> None:
136        if not self.parsed:
137            return
138
139        self.parsed_update_template()
140        if self.current_modifier == "":
141            if key == "usings":
142                self.res_append("usings")
143                return
144            raise RuntimeError("Unreachable")
145
146        if key not in self.res[self.current_modifier]:
147            self.res[self.current_modifier][key] = []
148
149        self.res[self.current_modifier][key].append(deep_copy(self.parsed))
150
151    def res_update(self) -> None:
152        if self.parsed:
153            self.parsed_update_template()
154            self.res.update(self.parsed)
155
156    def res_append_namespace(self) -> None:
157        self.parsed["namespace"] = self.namespace
158
159        if self.parent_class_name != "":
160            self.parsed["parent_class_name"] = self.parent_class_name
161
162        if "flags" in self.parsed or "flag_unions" in self.parsed:
163            self.res_append("enums")
164            add_to_custom_yamls("allEnums", "enums", self.parsed)
165
166    def update_access_modifier(self) -> None:
167        if self.parent_class_name == "":
168            raise RuntimeError("Found modifier not in class")
169        self.current_modifier = self.it.current_line.strip(" :")
170        if self.current_modifier not in self.res:
171            self.res[self.current_modifier] = {}
172
173    def res_append_method_or_constructor(self) -> None:
174        # Constructor
175        if self.parsed["name"] == self.parent_class_name:
176            self.res_append_in_modifier("constructors")
177
178        # Destructor
179        elif self.parsed["name"] == "~" + self.parent_class_name:
180            self.res_append_in_modifier("destructors")
181
182        # Method
183        elif self.current_modifier != "":
184            self.res_append_in_modifier("methods")
185
186        # Function
187        else:
188            self.res_append("functions")
189
190    def res_append_field(self) -> None:
191        # Class field
192        if self.current_modifier != "":
193            self.res_append_in_modifier("fields")
194
195        # Top level variable
196        else:
197            self.res_append("vars")
198
199    def res_append_class_definition(self) -> None:
200        self.res_append("class_definitions")
201
202    def parsed_update_template(self) -> None:
203        if self.template and self.parsed:
204            if isinstance(self.parsed, dict):
205                self.parsed["template"] = self.template
206                self.template = None
207            else:
208                warning_log("Skipping template for '" + self.parsed + "'")
209