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
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-21 12:06 +0000
1"""TRLC helper functions.
3 Author: Andreas Merkle (andreas.merkle@newtec.de)
4"""
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/>.
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
30# Variables ********************************************************************
32# Classes **********************************************************************
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.
41 It contains three dispatcher maps: begin, process and finish.
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.
47 The dispatcher map is a dictionary with the type name as key and the handler as value.
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
62 def walk(self, expression: Expression) -> Union[list[Any],Any]:
63 """
64 Walk through the TRLC AST.
66 Args:
67 expression (Expression): The AST node.
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)
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.
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
88 if process is not None:
89 self._dispatcher_map_process[type_name] = process
91 if finish is not None:
92 self._dispatcher_map_finish[type_name] = finish
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.
98 Args:
99 dispatcher (Callable): The other dispatcher
100 """
101 self._other_dispatcher = dispatcher
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.
107 Args:
108 dispatcher (Callable): The list item dispatcher.
109 """
110 self._list_item_dispatcher = dispatcher
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.
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.
121 Returns:
122 Union[list[Any],Any]: The result of the dispatcher.
123 """
124 result = ""
126 type_name = type(expression)
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)
133 return result
135 def _on_array_aggregate(self, array_aggregate: Array_Aggregate) -> list[Any]:
136 """
137 Handle the Array_Aggregate node.
139 Args:
140 array_aggregate (Array_Aggregate): The AST node.
142 Returns:
143 list[Any]: The result of the handling.
144 """
145 result = []
147 self._dispatch(self._dispatcher_map_begin, array_aggregate, False)
149 for expression in array_aggregate.value:
150 value_result = self._dispatch(self._dispatcher_map_process, expression, True)
152 if self._list_item_dispatcher is not None:
153 value_result = self._list_item_dispatcher(expression, value_result)
155 if isinstance(value_result, list):
156 result.extend(value_result)
157 else:
158 result.append(value_result)
160 self._dispatch(self._dispatcher_map_finish, array_aggregate, False)
162 return result
164 def _on_general(self, expression: Expression) -> Union[list[Any],Any]:
165 """
166 Handle the general case.
168 Args:
169 expression (Expression): The AST node.
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)
178 return result
180 def _on_other(self, expression: Expression) -> Union[list[Any],str]:
181 """
182 Handle the other case.
184 Args:
185 expression (Expression): The AST node.
187 Returns:
188 Union[list[Any],str]: The result of the handling.
189 """
190 result = ""
192 if self._other_dispatcher is not None:
193 result = self._other_dispatcher(expression)
194 else:
195 result = expression.to_string()
197 return result
199# Functions ********************************************************************
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.
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.
210 Returns:
211 Symbol_Table: TRLC symbol table
212 """
213 symbol_table = None
215 # Create Source_Manager.
216 mh = Message_Handler()
217 sm = Source_Manager(mh)
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)
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)
235 symbol_table = sm.process()
236 except AssertionError:
237 pass
239 return symbol_table
241def is_item_file_name(item):
242 # lobster-trace: SwRequirements.sw_req_destination_format
243 """Check if the item is a file name.
245 Args:
246 item (str|tuple): The item to check.
248 Returns:
249 bool: True if the item is a file name, otherwise False.
250 """
251 return isinstance(item, str)
253def is_item_section(item):
254 # lobster-trace: SwRequirements.sw_req_destination_format
255 """Check if the item is a section.
257 Args:
258 item (str|tuple): The item to check.
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)
268def is_item_record(item):
269 # lobster-trace: SwRequirements.sw_req_destination_format
270 """Check if the item is a record.
272 Args:
273 item (str|tuple): The item to check.
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)
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.
287 Args:
288 symbols (Symbol_Table): The TRLC symbols to dump.
290 Returns:
291 dict: A dictionary with the file names and their content.
292 """
293 file_dict = {}
294 item_list = None
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]
303 else:
304 assert item_list is not None
305 item_list.append(item)
307 return file_dict
309# Main *************************************************************************