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