1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# Copyright (c) 2024 Huawei Device Co., Ltd.
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17import sys
18from queue import Queue
19
20
21class TokenType(object):
22    UNKNOWN = 0
23    END_OF_FILE = 1
24    COMMENT = 2  # comment
25    INCLUDE = 3  # include
26    STRING = 4   # character string
27
28
29class Token(object):
30    def __init__(self, file_name, token_type, value):
31        self.token_type = token_type
32        self.value = value
33        self.row = 1
34        self.col = 1
35        self.file_name = file_name
36
37    def clean(self):
38        self.token_type = TokenType.UNKNOWN
39        self.value = ""
40        self.row = 1
41        self.col = 1
42
43    def dump(self):
44        return "<{}:{}:{}: {},'{}'>".format(self.file_name, self.row, self.col,
45                                            self.token_type, self.value)
46
47    def info(self):
48        return "{}:{}:{}".format(self.file_name, self.row, self.col)
49
50
51class Char(object):
52    def __init__(self, is_eof, char):
53        self.is_eof = is_eof
54        self.char = char
55
56    def dump(self):
57        return "{%s, %s}" % (self.is_eof, self.char)
58
59
60class Lexer(object):
61    def __init__(self, idl_file_path):
62        self.have_peek = False
63        with open(idl_file_path, 'r') as idl_file:
64            file_info = idl_file.read()
65        self.data = file_info
66        self.data_len = len(self.data)
67        self.read_index = 0
68        self.cur_token = Token(os.path.basename(idl_file_path),
69                               TokenType.UNKNOWN, "")
70        self.cur_row = 1
71        self.cur_col = 1
72
73    def peek_char(self, peek_count=0):
74        index = self.read_index + peek_count
75        if index >= self.data_len:
76            return Char(True, '0')
77        return Char(False, self.data[index])
78
79    def get_char(self):
80        if self.read_index >= self.data_len:
81            return Char(True, '0')
82        read_index = self.read_index
83        self.read_index += 1
84        if self.data[read_index] == '\n':
85            self.cur_row += 1
86            self.cur_col = 1
87        else:
88            self.cur_col += 1
89        return Char(False, self.data[read_index])
90
91    def peek_token(self):
92        if not self.have_peek:
93            self.read_token()
94            self.have_peek = True
95        return self.cur_token
96
97    def get_token(self):
98        if not self.have_peek:
99            self.read_token()
100        self.have_peek = False
101        return self.cur_token
102
103    def read_token(self):
104        self.cur_token.clean()
105        while not self.peek_char().is_eof:
106            new_char = self.peek_char()
107            if new_char.char.isspace():
108                self.get_char()
109                continue
110            self.cur_token.row = self.cur_row
111            self.cur_token.col = self.cur_col
112            if new_char.char == '#':
113                self.read_include()
114                return
115            if new_char.char == '"':
116                self.read_string()
117                return
118            if new_char.char == '/':
119                self.read_comment()
120                return
121            self.cur_token.value = new_char.char
122            self.cur_token.token_type = TokenType.UNKNOWN
123            self.get_char()
124            return
125        self.cur_token.token_type = TokenType.END_OF_FILE
126
127    def read_include(self):
128        token_value = []
129        token_value.append(self.get_char().char)
130        while not self.peek_char().is_eof:
131            new_char = self.peek_char()
132            if new_char.char.isalpha():
133                token_value.append(new_char.char)
134                self.get_char()
135                continue
136            break
137        key_str = "".join(token_value)
138        if key_str == "#include":
139            self.cur_token.token_type = TokenType.INCLUDE
140        else:
141            self.cur_token.token_type = TokenType.UNKNOWN
142        self.cur_token.value = key_str
143
144    def read_string(self):
145        token_value = []
146        self.get_char()
147        while not self.peek_char().is_eof and self.peek_char().char != '"':
148            token_value.append(self.get_char().char)
149
150        if self.peek_char().char == '"':
151            self.cur_token.token_type = TokenType.STRING
152            self.get_char()
153        else:
154            self.cur_token.token_type = TokenType.UNKNOWN
155        self.cur_token.value = "".join(token_value)
156
157    def read_comment(self):
158        token_value = []
159        token_value.append(self.get_char().char)
160        new_char = self.peek_char()
161        if not new_char.is_eof:
162            if new_char.char == '/':
163                self.read_line_comment(token_value)
164                return
165            elif new_char.char == '*':
166                self.read_block_comment(token_value)
167                return
168        self.cur_token.token_type = TokenType.UNKNOWN
169        self.cur_token.value = "".join(token_value)
170
171    def read_line_comment(self, token_value):
172        token_value.append(self.get_char().char)
173        while not self.peek_char().is_eof:
174            new_char = self.get_char()
175            if new_char.char == '\n':
176                break
177            token_value.append(new_char.char)
178        self.cur_token.token_type = TokenType.COMMENT
179        self.cur_token.value = "".join(token_value)
180
181    def read_block_comment(self, token_value):
182        # read *
183        token_value.append(self.get_char().char)
184        while not self.peek_char().is_eof:
185            new_char = self.get_char()
186            token_value.append(new_char.char)
187            if new_char.char == '*' and self.peek_char().char == '/':
188                token_value.append(self.get_char().char)
189                break
190        value = "".join(token_value)
191        if value.endswith("*/"):
192            self.cur_token.token_type = TokenType.COMMENT
193        else:
194            self.cur_token.token_type = TokenType.UNKNOWN
195        self.cur_token.value = value
196
197
198class HcsParser(object):
199    def __init__(self):
200        self.all_hcs_files = set()
201        self.src_queue = Queue()
202
203    # get all hcs files by root hcs file
204    def get_hcs_info(self):
205        result_str = ""
206        all_hcs_files_list = sorted(list(self.all_hcs_files))
207        for file_path in all_hcs_files_list:
208            result_str += "{}\n".format(file_path)
209        return result_str
210
211    def parse(self, root_hcs_file):
212        if not os.path.exists(root_hcs_file):
213            return
214        self.src_queue.put(os.path.abspath(root_hcs_file))
215        while not self.src_queue.empty():
216            cur_hcs_file = self.src_queue.get()
217            self.all_hcs_files.add(cur_hcs_file)
218            self.parse_one(cur_hcs_file)
219
220    def parse_one(self, cur_hcs_file_path):
221        hcs_file_dir = os.path.dirname(cur_hcs_file_path)
222        lex = Lexer(cur_hcs_file_path)
223        while lex.peek_token().token_type != TokenType.END_OF_FILE:
224            cur_token_type = lex.peek_token().token_type
225            if cur_token_type == TokenType.INCLUDE:
226                self.parse_include(lex, hcs_file_dir)
227            else:
228                lex.get_token()
229
230    def parse_include(self, lex, hcs_file_dir):
231        lex.get_token()  # include token
232        token = lex.peek_token()
233        if token.token_type == TokenType.STRING:
234            hcs_file_path = os.path.join(hcs_file_dir, token.value)
235            # do not parse the hcs file that does not exist
236            if not os.path.exists(hcs_file_path):
237                return
238            self.src_queue.put(os.path.abspath(hcs_file_path))
239
240
241def check_python_version():
242    if sys.version_info < (3, 0):
243        raise Exception("Please run with python version >= 3.0")
244
245
246if __name__ == "__main__":
247    check_python_version()
248    if len(sys.argv) < 2:
249        raise Exception("No hcs source files, please check input")
250    all_hcs_files = sys.argv[1:]
251    parser = HcsParser()
252    for hcs_file in all_hcs_files:
253        parser.parse(hcs_file)
254
255    sys.stdout.write(parser.get_hcs_info())
256    sys.stdout.flush()
257