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

93 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-21 12:06 +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, Any, Callable 

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 self._list_item_dispatcher = None 

61 

62 def walk(self, expression: Expression) -> Union[list[Any],Any]: 

63 """ 

64 Walk through the TRLC AST. 

65 

66 Args: 

67 expression (Expression): The AST node. 

68 

69 Returns: 

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

71 """ 

72 return self._on_general(expression) 

73 

74 # pylint: disable-next=line-too-long 

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

76 """ 

77 Add a dispatcher to the walker. 

78 

79 Args: 

80 type_name (type): The type name 

81 begin (Optional[Callable]): The begin handler 

82 process (Optional[Callable]): The process handler 

83 finish (Optional[Callable]): The finish handler 

84 """ 

85 if begin is not None: 

86 self._dispatcher_map_begin[type_name] = begin 

87 

88 if process is not None: 

89 self._dispatcher_map_process[type_name] = process 

90 

91 if finish is not None: 

92 self._dispatcher_map_finish[type_name] = finish 

93 

94 def set_other_dispatcher(self, dispatcher: Callable) -> None: 

95 """ 

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

97 

98 Args: 

99 dispatcher (Callable): The other dispatcher 

100 """ 

101 self._other_dispatcher = dispatcher 

102 

103 def set_list_item_dispatcher(self, dispatcher: Callable) -> None: 

104 """ 

105 Set the list item dispatcher. This dispatcher is called when a list item is processed. 

106 

107 Args: 

108 dispatcher (Callable): The list item dispatcher. 

109 """ 

110 self._list_item_dispatcher = dispatcher 

111 

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

113 """ 

114 Dispatch the expression to the dispatcher map. 

115 

116 Args: 

117 dispatcher_map (dict): The dispatcher map. 

118 expression (Expression): The AST node. 

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

120 

121 Returns: 

122 Union[list[Any],Any]: The result of the dispatcher. 

123 """ 

124 result = "" 

125 

126 type_name = type(expression) 

127 

128 if type_name in dispatcher_map: 

129 result = dispatcher_map[type_name](expression) 

130 elif handle_other is True: 

131 result = self._on_other(expression) 

132 

133 return result 

134 

135 def _on_array_aggregate(self, array_aggregate: Array_Aggregate) -> list[Any]: 

136 """ 

137 Handle the Array_Aggregate node. 

138 

139 Args: 

140 array_aggregate (Array_Aggregate): The AST node. 

141 

142 Returns: 

143 list[Any]: The result of the handling. 

144 """ 

145 result = [] 

146 

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

148 

149 for expression in array_aggregate.value: 

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

151 

152 if self._list_item_dispatcher is not None: 

153 value_result = self._list_item_dispatcher(expression, value_result) 

154 

155 if isinstance(value_result, list): 

156 result.extend(value_result) 

157 else: 

158 result.append(value_result) 

159 

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

161 

162 return result 

163 

164 def _on_general(self, expression: Expression) -> Union[list[Any],Any]: 

165 """ 

166 Handle the general case. 

167 

168 Args: 

169 expression (Expression): The AST node. 

170 

171 Returns: 

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

173 """ 

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

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

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

177 

178 return result 

179 

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

181 """ 

182 Handle the other case. 

183 

184 Args: 

185 expression (Expression): The AST node. 

186 

187 Returns: 

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

189 """ 

190 result = "" 

191 

192 if self._other_dispatcher is not None: 

193 result = self._other_dispatcher(expression) 

194 else: 

195 result = expression.to_string() 

196 

197 return result 

198 

199# Functions ******************************************************************** 

200 

201def get_trlc_symbols(source_items, includes): 

202 # lobster-trace: SwRequirements.sw_req_destination_format 

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

204 

205 Args: 

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

207 or a single path to a TRLC file. 

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

209 

210 Returns: 

211 Symbol_Table: TRLC symbol table 

212 """ 

213 symbol_table = None 

214 

215 # Create Source_Manager. 

216 mh = Message_Handler() 

217 sm = Source_Manager(mh) 

218 

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

220 try: 

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

222 if includes is not None: 

223 for folder in includes: 

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

225 sm.register_include(folder) 

226 

227 for src_item in source_items: 

228 if os.path.isdir(src_item): 

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

230 sm.register_directory(src_item) 

231 else: 

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

233 sm.register_file(src_item) 

234 

235 symbol_table = sm.process() 

236 except AssertionError: 

237 pass 

238 

239 return symbol_table 

240 

241def is_item_file_name(item): 

242 # lobster-trace: SwRequirements.sw_req_destination_format 

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

244 

245 Args: 

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

247 

248 Returns: 

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

250 """ 

251 return isinstance(item, str) 

252 

253def is_item_section(item): 

254 # lobster-trace: SwRequirements.sw_req_destination_format 

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

256 

257 Args: 

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

259 

260 Returns: 

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

262 """ 

263 return isinstance(item, tuple) and \ 

264 len(item) == 2 and \ 

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

266 isinstance(item[1], int) 

267 

268def is_item_record(item): 

269 # lobster-trace: SwRequirements.sw_req_destination_format 

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

271 

272 Args: 

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

274 

275 Returns: 

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

277 """ 

278 return isinstance(item, tuple) and \ 

279 len(item) == 2 and \ 

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

281 isinstance(item[1], int) 

282 

283def get_file_dict_from_symbols(symbols): 

284 # lobster-trace: SwRequirements.sw_req_destination_format 

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

286 

287 Args: 

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

289 

290 Returns: 

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

292 """ 

293 file_dict = {} 

294 item_list = None 

295 

296 if symbols is not None: 

297 for item in symbols.iter_record_objects_by_section(): 

298 # Is item a file name? 

299 if is_item_file_name(item): 

300 file_dict[item] = [] 

301 item_list = file_dict[item] 

302 

303 else: 

304 assert item_list is not None 

305 item_list.append(item) 

306 

307 return file_dict 

308 

309# Main *************************************************************************