Coverage for src/pyTRLCConverter/trlc_helper.py: 84%

87 statements  

« prev     ^ index     » next       coverage.py v7.10.3, created at 2025-08-14 10:59 +0000

1"""TRLC helper functions. 

2 

3 Author: Andreas Merkle (andreas.merkle@newtec.de) 

4""" 

5 

6# pyTRLCConverter - A tool to convert TRLC files to specific formats. 

7# Copyright (c) 2024 - 2025 NewTec GmbH 

8# 

9# This file is part of pyTRLCConverter program. 

10# 

11# The pyTRLCConverter program is free software: you can redistribute it and/or modify it under 

12# the terms of the GNU General Public License as published by the Free Software Foundation, 

13# either version 3 of the License, or (at your option) any later version. 

14# 

15# The pyTRLCConverter program is distributed in the hope that it will be useful, but 

16# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 

17# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License along with pyTRLCConverter. 

20# If not, see <https://www.gnu.org/licenses/>. 

21 

22# Imports ********************************************************************** 

23import os 

24from typing import Union, Optional 

25from trlc.errors import Message_Handler 

26from trlc.trlc import Source_Manager 

27from trlc.ast import Array_Aggregate, Expression, Record_Object 

28from pyTRLCConverter.logger import log_verbose 

29 

30# Variables ******************************************************************** 

31 

32# Classes ********************************************************************** 

33 

34class TrlcAstWalker(): 

35 # lobster-trace: SwRequirements.sw_req_markdown 

36 # lobster-trace: SwRequirements.sw_req_rst 

37 """ 

38 This class helps to walk through the TRLC AST. It uses a dispatcher map to handle 

39 the different AST nodes. 

40 

41 It contains three dispatcher maps: begin, process and finish. 

42  

43 The begin map is used to handle the node when it is entered, the process map is used to 

44 handle the node when it is processed and the finish map is used to handle the node when 

45 it is finished. 

46  

47 The dispatcher map is a dictionary with the type name as key and the handler as value. 

48 

49 By default the walker contains a dispatcher map only for the Array_Aggregate node. The other 

50 nodes are handled by the other dispatcher. If no other dispatcher is set, the node is 

51 converted to a string. 

52 """ 

53 def __init__(self) -> None: 

54 self._dispatcher_map_begin = {} 

55 self._dispatcher_map_process = { 

56 Array_Aggregate: self._on_array_aggregate 

57 } 

58 self._dispatcher_map_finish = {} 

59 self._other_dispatcher = None 

60 

61 def walk(self, expression: Expression) -> Union[list[str],str]: 

62 """ 

63 Walk through the TRLC AST. 

64 

65 Args: 

66 expression (Expression): The AST node. 

67 

68 Returns: 

69 Union[list[str],str]: The result of the walking as string or list of strings. 

70 """ 

71 return self._on_general(expression) 

72 

73 # pylint: disable=line-too-long 

74 def add_dispatcher(self, type_name: type, begin: Optional[callable], process: Optional[callable], finish: Optional[callable]) -> None: 

75 """ 

76 Add a dispatcher to the walker. 

77 

78 Args: 

79 type_name (type): The type name 

80 begin (Optional[callable]): The begin handler 

81 process (Optional[callable]): The process handler 

82 finish (Optional[callable]): The finish handler 

83 """ 

84 if begin is not None: 

85 self._dispatcher_map_begin[type_name] = begin 

86 

87 if process is not None: 

88 self._dispatcher_map_process[type_name] = process 

89 

90 if finish is not None: 

91 self._dispatcher_map_finish[type_name] = finish 

92 

93 def set_other_dispatcher(self, dispatcher: callable) -> None: 

94 """ 

95 Set the other dispatcher. This dispatcher is called when no dispatcher is found for the node. 

96 

97 Args: 

98 dispatcher (callable): The other dispatcher 

99 """ 

100 self._other_dispatcher = dispatcher 

101 

102 def _dispatch(self, dispatcher_map: dict, expression: Expression, handle_other: bool) -> Union[list[str],str]: 

103 """ 

104 Dispatch the expression to the dispatcher map. 

105 

106 Args: 

107 dispatcher_map (dict): The dispatcher map. 

108 expression (Expression): The AST node. 

109 handle_other (bool): If True, the other dispatcher is called when no dispatcher is found. 

110 

111 Returns: 

112 Union[list[str],str]: The result of the dispatcher. 

113 """ 

114 result = "" 

115 

116 type_name = type(expression) 

117 

118 if type_name in dispatcher_map: 

119 result = dispatcher_map[type_name](expression) 

120 elif handle_other is True: 

121 result = self._on_other(expression) 

122 

123 return result 

124 

125 def _on_array_aggregate(self, array_aggregate: Array_Aggregate) -> list[str]: 

126 """ 

127 Handle the Array_Aggregate node. 

128 

129 Args: 

130 array_aggregate (Array_Aggregate): The AST node. 

131 

132 Returns: 

133 list[str]: The result of the handling. 

134 """ 

135 result = [] 

136 

137 self._dispatch(self._dispatcher_map_begin, array_aggregate, False) 

138 

139 for expression in array_aggregate.value: 

140 value_result = self._dispatch(self._dispatcher_map_process, expression, True) 

141 

142 if isinstance(value_result, list): 

143 result.extend(value_result) 

144 else: 

145 result.append(value_result) 

146 

147 self._dispatch(self._dispatcher_map_finish, array_aggregate, False) 

148 

149 return result 

150 

151 def _on_general(self, expression: Expression) -> Union[list[str],str]: 

152 """ 

153 Handle the general case. 

154 

155 Args: 

156 expression (Expression): The AST node. 

157 

158 Returns: 

159 Union[list[str],str]: The result of the handling. 

160 """ 

161 self._dispatch(self._dispatcher_map_begin, expression, False) 

162 result = self._dispatch(self._dispatcher_map_process, expression, True) 

163 self._dispatch(self._dispatcher_map_finish, expression, False) 

164 

165 return result 

166 

167 def _on_other(self, expression: Expression) -> Union[list[str],str]: 

168 """ 

169 Handle the other case. 

170 

171 Args: 

172 expression (Expression): The AST node. 

173 

174 Returns: 

175 Union[list[str],str]: The result of the handling. 

176 """ 

177 result = "" 

178 

179 if self._other_dispatcher is not None: 

180 result = self._other_dispatcher(expression) 

181 else: 

182 result = expression.to_string() 

183 

184 return result 

185 

186# Functions ******************************************************************** 

187 

188def get_trlc_symbols(source_items, includes): 

189 # lobster-trace: SwRequirements.sw_req_destination_format 

190 """Get the TRLC symbol table by parsing the given folder. 

191 

192 Args: 

193 source_items ([str]|str): One or more paths to folder with TRLC files \ 

194 or a single path to a TRLC file. 

195 includes (str|None): Path for automatically file inclusion. 

196 

197 Returns: 

198 Symbol_Table: TRLC symbol table 

199 """ 

200 symbol_table = None 

201 

202 # Create Source_Manager. 

203 mh = Message_Handler() 

204 sm = Source_Manager(mh) 

205 

206 # Read all .rsl and .trlc files in the given directory. 

207 try: 

208 # Handle first the include folders, because the source folders may depend on them. 

209 if includes is not None: 

210 for folder in includes: 

211 log_verbose(f"Registering include folder: {folder}") 

212 sm.register_include(folder) 

213 

214 for src_item in source_items: 

215 if os.path.isdir(src_item): 

216 log_verbose(f"Registering source folder: {src_item}") 

217 sm.register_directory(src_item) 

218 else: 

219 log_verbose(f"Registering source file: {src_item}") 

220 sm.register_file(src_item) 

221 

222 symbol_table = sm.process() 

223 except AssertionError: 

224 pass 

225 

226 return symbol_table 

227 

228def is_item_file_name(item): 

229 # lobster-trace: SwRequirements.sw_req_destination_format 

230 """Check if the item is a file name. 

231 

232 Args: 

233 item (str|tuple): The item to check. 

234 

235 Returns: 

236 bool: True if the item is a file name, otherwise False. 

237 """ 

238 return isinstance(item, str) 

239 

240def is_item_section(item): 

241 # lobster-trace: SwRequirements.sw_req_destination_format 

242 """Check if the item is a section. 

243 

244 Args: 

245 item (str|tuple): The item to check. 

246 

247 Returns: 

248 bool: True if the item is a section, otherwise False. 

249 """ 

250 return isinstance(item, tuple) and \ 

251 len(item) == 2 and \ 

252 isinstance(item[0], str) and \ 

253 isinstance(item[1], int) 

254 

255def is_item_record(item): 

256 # lobster-trace: SwRequirements.sw_req_destination_format 

257 """Check if the item is a record. 

258 

259 Args: 

260 item (str|tuple): The item to check. 

261 

262 Returns: 

263 bool: True if the item is a record, otherwise False. 

264 """ 

265 return isinstance(item, tuple) and \ 

266 len(item) == 2 and \ 

267 isinstance(item[0], Record_Object) and \ 

268 isinstance(item[1], int) 

269 

270def get_file_dict_from_symbols(symbols): 

271 # lobster-trace: SwRequirements.sw_req_destination_format 

272 """Get a dictionary with the file names and their content. 

273 

274 Args: 

275 symbols (Symbol_Table): The TRLC symbols to dump. 

276 

277 Returns: 

278 dict: A dictionary with the file names and their content. 

279 """ 

280 file_dict = {} 

281 item_list = None 

282 

283 if symbols is not None: 

284 for item in symbols.iter_record_objects_by_section(): 

285 # Is item a file name? 

286 if is_item_file_name(item): 

287 file_dict[item] = [] 

288 item_list = file_dict[item] 

289 

290 else: 

291 item_list.append(item) 

292 

293 return file_dict 

294 

295# Main *************************************************************************