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
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 10:59 +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
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
61 def walk(self, expression: Expression) -> Union[list[str],str]:
62 """
63 Walk through the TRLC AST.
65 Args:
66 expression (Expression): The AST node.
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)
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.
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
87 if process is not None:
88 self._dispatcher_map_process[type_name] = process
90 if finish is not None:
91 self._dispatcher_map_finish[type_name] = finish
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.
97 Args:
98 dispatcher (callable): The other dispatcher
99 """
100 self._other_dispatcher = dispatcher
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.
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.
111 Returns:
112 Union[list[str],str]: The result of the dispatcher.
113 """
114 result = ""
116 type_name = type(expression)
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)
123 return result
125 def _on_array_aggregate(self, array_aggregate: Array_Aggregate) -> list[str]:
126 """
127 Handle the Array_Aggregate node.
129 Args:
130 array_aggregate (Array_Aggregate): The AST node.
132 Returns:
133 list[str]: The result of the handling.
134 """
135 result = []
137 self._dispatch(self._dispatcher_map_begin, array_aggregate, False)
139 for expression in array_aggregate.value:
140 value_result = self._dispatch(self._dispatcher_map_process, expression, True)
142 if isinstance(value_result, list):
143 result.extend(value_result)
144 else:
145 result.append(value_result)
147 self._dispatch(self._dispatcher_map_finish, array_aggregate, False)
149 return result
151 def _on_general(self, expression: Expression) -> Union[list[str],str]:
152 """
153 Handle the general case.
155 Args:
156 expression (Expression): The AST node.
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)
165 return result
167 def _on_other(self, expression: Expression) -> Union[list[str],str]:
168 """
169 Handle the other case.
171 Args:
172 expression (Expression): The AST node.
174 Returns:
175 Union[list[str],str]: The result of the handling.
176 """
177 result = ""
179 if self._other_dispatcher is not None:
180 result = self._other_dispatcher(expression)
181 else:
182 result = expression.to_string()
184 return result
186# Functions ********************************************************************
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.
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.
197 Returns:
198 Symbol_Table: TRLC symbol table
199 """
200 symbol_table = None
202 # Create Source_Manager.
203 mh = Message_Handler()
204 sm = Source_Manager(mh)
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)
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)
222 symbol_table = sm.process()
223 except AssertionError:
224 pass
226 return symbol_table
228def is_item_file_name(item):
229 # lobster-trace: SwRequirements.sw_req_destination_format
230 """Check if the item is a file name.
232 Args:
233 item (str|tuple): The item to check.
235 Returns:
236 bool: True if the item is a file name, otherwise False.
237 """
238 return isinstance(item, str)
240def is_item_section(item):
241 # lobster-trace: SwRequirements.sw_req_destination_format
242 """Check if the item is a section.
244 Args:
245 item (str|tuple): The item to check.
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)
255def is_item_record(item):
256 # lobster-trace: SwRequirements.sw_req_destination_format
257 """Check if the item is a record.
259 Args:
260 item (str|tuple): The item to check.
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)
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.
274 Args:
275 symbols (Symbol_Table): The TRLC symbols to dump.
277 Returns:
278 dict: A dictionary with the file names and their content.
279 """
280 file_dict = {}
281 item_list = None
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]
290 else:
291 item_list.append(item)
293 return file_dict
295# Main *************************************************************************