2021-06-08 12:37:10 +02:00
|
|
|
import re
|
|
|
|
import sys
|
|
|
|
from typing import List, Tuple
|
|
|
|
|
2021-08-02 12:49:10 +02:00
|
|
|
from fsfwgen.parserbase.parser import FileParser, VerbosityLevels
|
2021-06-08 17:10:24 +02:00
|
|
|
from fsfwgen.utility.printer import PrettyPrinter
|
2021-08-02 12:49:10 +02:00
|
|
|
from fsfwgen.core import get_console_logger
|
|
|
|
|
|
|
|
LOGGER = get_console_logger()
|
2021-06-08 12:37:10 +02:00
|
|
|
|
|
|
|
# Intermediate solution
|
|
|
|
MAX_STRING_LEN = 80
|
|
|
|
|
|
|
|
PRINT_TRUNCATED_ENTRIES = False
|
|
|
|
DEBUG_FOR_FILE_NAME = False
|
|
|
|
|
|
|
|
CLASS_ID_NAMESPACE = "CLASS_ID"
|
|
|
|
DEFAULT_MOVING_WINDOWS_SIZE = 7
|
2021-08-02 12:49:10 +02:00
|
|
|
INVALID_IF_ID = -1
|
2021-06-08 12:37:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
class InterfaceParser(FileParser):
|
|
|
|
|
|
|
|
def __init__(self, file_list: list, print_table: bool = False):
|
|
|
|
super().__init__(file_list)
|
|
|
|
self.print_table = print_table
|
|
|
|
self.file_table_list = []
|
|
|
|
self.file_name_table = []
|
|
|
|
self.start_name_list = []
|
|
|
|
self.end_name_list = []
|
2021-08-02 12:49:10 +02:00
|
|
|
self._debug_mode = False
|
|
|
|
|
|
|
|
def enable_debug_mode(self, enable: bool):
|
|
|
|
self._debug_mode = enable
|
2021-06-08 12:37:10 +02:00
|
|
|
|
2021-06-21 12:52:01 +02:00
|
|
|
def _handle_file_parsing_moving_window(
|
|
|
|
self, file_name: str, current_line: int, moving_window_size: int, moving_window: list,
|
|
|
|
*args, **kwargs
|
|
|
|
):
|
2021-06-08 12:37:10 +02:00
|
|
|
pass
|
|
|
|
|
|
|
|
def _handle_file_parsing(self, file_name: str, *args, **kwargs):
|
|
|
|
self.file_name_table.append(file_name)
|
|
|
|
try:
|
|
|
|
file = open(file_name, 'r', encoding='utf-8')
|
|
|
|
all_lines = file.readlines()
|
|
|
|
except UnicodeDecodeError:
|
|
|
|
file = open(file_name, 'r', encoding='cp1252')
|
|
|
|
all_lines = file.readlines()
|
|
|
|
self.__handle_regular_class_id_parsing(file_name=file_name, all_lines=all_lines)
|
|
|
|
|
|
|
|
def __handle_regular_class_id_parsing(self, file_name: str, all_lines: List[str]):
|
|
|
|
count = 0
|
|
|
|
current_file_table = dict()
|
|
|
|
start_matched = False
|
|
|
|
end_matched = False
|
|
|
|
start_name = ""
|
|
|
|
target_end_name = ""
|
|
|
|
for line in all_lines:
|
|
|
|
if not start_matched:
|
|
|
|
match = re.search(r'[\s]*([\w]*) = [\s]*([\w]*)', line)
|
|
|
|
if match:
|
|
|
|
# current_file_table.update({count: [match.group(1), match.group(2)]})
|
|
|
|
start_name = match.group(1)
|
|
|
|
target_end_name = match.group(2)
|
|
|
|
start_matched = True
|
|
|
|
else:
|
|
|
|
match = re.search(r'[\s]*([\w]*),?(?:[\s]*//)?([^\n]*)?', line)
|
|
|
|
if match:
|
|
|
|
count += 1
|
2021-06-21 12:52:01 +02:00
|
|
|
if re.search(r"\[EXPORT][\s]*:[\s]*\[END]", match.group(2)):
|
2021-06-08 12:37:10 +02:00
|
|
|
last_entry_name = match.group(1)
|
|
|
|
end_matched = True
|
|
|
|
self.end_name_list.append([last_entry_name, None])
|
|
|
|
else:
|
|
|
|
short_name = match.group(2)
|
|
|
|
if short_name == "":
|
|
|
|
short_name = match.group(1)[0:3]
|
|
|
|
current_file_table.update({count: [match.group(1), short_name]})
|
|
|
|
if not start_matched:
|
|
|
|
print("No start match detected when parsing interface files..")
|
|
|
|
print(f"Current file: {file_name} | Make sure to include a start definition")
|
|
|
|
sys.exit(1)
|
|
|
|
if not end_matched:
|
2021-06-21 12:52:01 +02:00
|
|
|
print(
|
|
|
|
"No end match detected when parsing interface files. "
|
|
|
|
"Make sure to use [EXPORT] : [END]"
|
|
|
|
)
|
2021-06-08 12:37:10 +02:00
|
|
|
sys.exit(1)
|
|
|
|
self.start_name_list.append([start_name, target_end_name, None, count])
|
|
|
|
self.file_name_table.append(file_name)
|
|
|
|
self.file_table_list.append(current_file_table)
|
|
|
|
|
|
|
|
def _post_parsing_operation(self):
|
|
|
|
self.start_name_list, self.end_name_list = self.__assign_start_end_indexes(
|
|
|
|
self.start_name_list, self.end_name_list
|
|
|
|
)
|
|
|
|
for idx, file_table in enumerate(self.file_table_list):
|
|
|
|
self.__build_mod_interface_table(self.start_name_list[idx][2], file_table)
|
|
|
|
if self.print_table:
|
|
|
|
PrettyPrinter.pprint(self.mib_table)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def __assign_start_end_indexes(start_name_list_list, end_name_list_list) -> Tuple[List, List]:
|
|
|
|
start_list_list_completed = start_name_list_list
|
|
|
|
end_list_list_completed = end_name_list_list
|
|
|
|
all_indexes_filled = False
|
|
|
|
max_outer_iterations = 15
|
|
|
|
current_iteration = 0
|
|
|
|
while not all_indexes_filled:
|
|
|
|
for idx, start_name_list in enumerate(start_list_list_completed):
|
|
|
|
if start_name_list[1].isdigit():
|
|
|
|
start_list_list_completed[idx][2] = int(start_name_list[1])
|
|
|
|
end_list_list_completed[idx][1] = \
|
|
|
|
start_list_list_completed[idx][2] + start_list_list_completed[idx][3]
|
|
|
|
target_end_name = start_name_list[1]
|
|
|
|
for end_name_list in end_list_list_completed:
|
|
|
|
end_name = end_name_list[0]
|
|
|
|
end_value = end_name_list[1]
|
|
|
|
if end_name == target_end_name and end_value is not None:
|
|
|
|
start_list_list_completed[idx][2] = end_value
|
2021-06-21 12:54:03 +02:00
|
|
|
end_list_list_completed[idx][1] = \
|
|
|
|
end_value + start_list_list_completed[idx][3]
|
2021-06-08 12:37:10 +02:00
|
|
|
all_indexes_filled = True
|
|
|
|
for idx, start_name_list in enumerate(start_list_list_completed):
|
|
|
|
if start_name_list[2] is None or end_name_list_list[idx][1] is None:
|
|
|
|
all_indexes_filled = False
|
|
|
|
current_iteration += 1
|
|
|
|
if current_iteration >= max_outer_iterations:
|
2021-06-21 12:52:01 +02:00
|
|
|
print(
|
|
|
|
"Could not fill out start and end index list in "
|
|
|
|
"given number of maximum outer iterations!"
|
|
|
|
)
|
2021-06-08 12:37:10 +02:00
|
|
|
sys.exit(1)
|
|
|
|
return start_list_list_completed, end_list_list_completed
|
|
|
|
|
|
|
|
def __build_mod_interface_table(self, count_start: int, interface_dict: dict):
|
|
|
|
dict_to_build = dict()
|
|
|
|
for local_count, interface_name_and_shortname in interface_dict.items():
|
|
|
|
dict_to_build.update(
|
2021-06-21 12:52:01 +02:00
|
|
|
{interface_name_and_shortname[0]: [local_count + count_start,
|
|
|
|
interface_name_and_shortname[1]]}
|
2021-06-08 12:37:10 +02:00
|
|
|
)
|
|
|
|
self.mib_table.update(dict_to_build)
|
|
|
|
|
|
|
|
|
|
|
|
class ReturnValueParser(FileParser):
|
|
|
|
"""
|
|
|
|
Generic return value parser.
|
|
|
|
"""
|
|
|
|
def __init__(self, interfaces, file_list, print_tables):
|
|
|
|
super().__init__(file_list)
|
|
|
|
self.print_tables = print_tables
|
|
|
|
self.interfaces = interfaces
|
|
|
|
self.return_value_dict = dict()
|
|
|
|
self.count = 0
|
|
|
|
# Stores last three lines
|
|
|
|
self.last_lines = ["", "", ""]
|
|
|
|
self.current_interface_id_entries = {
|
|
|
|
"Name": "",
|
|
|
|
"ID": 0,
|
|
|
|
"FullName": ""
|
|
|
|
}
|
|
|
|
self.return_value_dict.update({0: ('OK', 'System-wide code for ok.', 'RETURN_OK',
|
|
|
|
'HasReturnvaluesIF.h', 'HasReturnvaluesIF')})
|
|
|
|
self.return_value_dict.update({1: ('Failed', 'Unspecified system-wide code for failed.',
|
|
|
|
'RETURN_FAILED', 'HasReturnvaluesIF.h',
|
|
|
|
'HasReturnvaluesIF')})
|
|
|
|
|
|
|
|
def _handle_file_parsing(self, file_name: str, *args, **kwargs):
|
2021-06-21 12:47:03 +02:00
|
|
|
"""Former way to parse returnvalues. Not recommended anymore.
|
2021-06-08 12:37:10 +02:00
|
|
|
:param file_name:
|
|
|
|
:param args:
|
|
|
|
:param kwargs:
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
if len(args) > 0:
|
|
|
|
print_truncated_entries = args[0]
|
|
|
|
else:
|
|
|
|
print_truncated_entries = False
|
|
|
|
all_lines = self._open_file(file_name=file_name)
|
|
|
|
for line in all_lines:
|
|
|
|
self.__handle_line_reading(line, file_name, print_truncated_entries)
|
|
|
|
|
2021-06-21 12:47:03 +02:00
|
|
|
def _handle_file_parsing_moving_window(
|
|
|
|
self, file_name: str, current_line: int, moving_window_size: int, moving_window: list,
|
|
|
|
*args, **kwargs
|
|
|
|
):
|
|
|
|
"""Parse for returnvalues using a moving window"""
|
2021-06-08 12:37:10 +02:00
|
|
|
interface_id_match = re.search(
|
2021-08-02 12:49:10 +02:00
|
|
|
rf'{CLASS_ID_NAMESPACE}::([a-zA-Z_0-9]*)', moving_window[self._moving_window_center_idx]
|
2021-06-08 12:37:10 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
if interface_id_match:
|
2021-06-21 12:47:03 +02:00
|
|
|
self.__handle_interfaceid_match(
|
|
|
|
interface_id_match=interface_id_match, file_name=file_name
|
|
|
|
)
|
2021-06-08 12:37:10 +02:00
|
|
|
returnvalue_match = re.search(
|
|
|
|
r"^[\s]*static const(?:expr)?[\s]*ReturnValue_t[\s]*([\w]*)[\s]*=[\s]*((?!;).*$)",
|
2021-06-21 12:47:03 +02:00
|
|
|
moving_window[self._moving_window_center_idx], re.DOTALL
|
2021-06-08 12:37:10 +02:00
|
|
|
)
|
|
|
|
full_returnvalue_string = ""
|
|
|
|
if returnvalue_match:
|
|
|
|
if ";" in returnvalue_match.group(0):
|
|
|
|
full_returnvalue_string = returnvalue_match.group(0)
|
|
|
|
else:
|
|
|
|
full_returnvalue_string = self.__build_multi_line_returnvalue_string(
|
2021-06-21 12:47:03 +02:00
|
|
|
moving_window=moving_window,
|
|
|
|
first_line=moving_window[self._moving_window_center_idx]
|
2021-06-08 12:37:10 +02:00
|
|
|
)
|
2021-08-02 12:49:10 +02:00
|
|
|
number_match = INVALID_IF_ID
|
|
|
|
# Try to match for a string using the new API first. Example:
|
|
|
|
# static const ReturnValue_t PACKET_TOO_LONG =
|
|
|
|
# HasReturnvaluesIF::makeReturnCode(CLASS_ID, 0);
|
2021-06-08 12:37:10 +02:00
|
|
|
returnvalue_match = re.search(
|
2021-08-02 12:49:10 +02:00
|
|
|
r"^[\s]*static const(?:expr)? ReturnValue_t[\s]*([\w]*)[\s]*"
|
2021-06-21 12:47:03 +02:00
|
|
|
r"=[\s]*.*::[\w]*\(([\w]*),[\s]*([\d]*)\)",
|
2021-06-08 12:37:10 +02:00
|
|
|
full_returnvalue_string
|
|
|
|
)
|
|
|
|
if not returnvalue_match:
|
2021-08-02 12:49:10 +02:00
|
|
|
# Try to match for old API using MAE_RETURN_CODE macro
|
2021-06-08 12:37:10 +02:00
|
|
|
returnvalue_match = re.search(
|
|
|
|
r'^[\s]*static const(?:expr)? ReturnValue_t[\s]*([a-zA-Z_0-9]*)[\s]*=[\s]*'
|
|
|
|
r'MAKE_RETURN_CODE[\s]*\([\s]*([\w]*)[\s]*\)',
|
|
|
|
full_returnvalue_string
|
|
|
|
)
|
2021-08-02 12:49:10 +02:00
|
|
|
if returnvalue_match:
|
|
|
|
number_match = returnvalue_match.group(2)
|
|
|
|
else:
|
|
|
|
number_match = returnvalue_match.group(3)
|
2021-06-08 12:37:10 +02:00
|
|
|
if returnvalue_match:
|
|
|
|
description = self.__search_for_descrip_string(moving_window=moving_window)
|
2021-08-02 12:49:10 +02:00
|
|
|
if number_match == INVALID_IF_ID:
|
|
|
|
LOGGER.warning(f'Invalid number match detected for file {file_name}')
|
|
|
|
LOGGER.warning(f'Match groups:')
|
|
|
|
for group in returnvalue_match.groups():
|
|
|
|
LOGGER.info(group)
|
2021-06-08 12:37:10 +02:00
|
|
|
self.__handle_returnvalue_match(
|
2021-06-21 12:47:03 +02:00
|
|
|
name_match=returnvalue_match.group(1), file_name=file_name,
|
2021-08-02 12:49:10 +02:00
|
|
|
number_match=number_match, description=description
|
2021-06-08 12:37:10 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
def __build_multi_line_returnvalue_string(
|
|
|
|
self, first_line: str, moving_window: List[str]
|
|
|
|
) -> str:
|
2021-06-21 12:47:03 +02:00
|
|
|
return self._build_multi_line_string_generic(
|
|
|
|
first_line=first_line, moving_window=moving_window
|
|
|
|
)
|
2021-06-08 12:37:10 +02:00
|
|
|
|
|
|
|
def __search_for_descrip_string(self, moving_window: List[str]) -> str:
|
2021-06-21 12:47:03 +02:00
|
|
|
return self._search_for_descrip_string_generic(
|
|
|
|
moving_window=moving_window, break_pattern=r"^[\s]*static const(?:expr)? ReturnValue_t"
|
2021-06-08 12:37:10 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
def __handle_line_reading(self, line, file_name, print_truncated_entries: bool):
|
|
|
|
newline = line
|
|
|
|
if self.last_lines[0] != '\n':
|
|
|
|
two_lines = self.last_lines[0] + ' ' + newline.strip()
|
|
|
|
else:
|
|
|
|
two_lines = ''
|
|
|
|
interface_id_match = re.search(r'INTERFACE_ID[\s]*=[\s]*CLASS_ID::([a-zA-Z_0-9]*)',
|
|
|
|
two_lines)
|
|
|
|
if interface_id_match:
|
|
|
|
self.__handle_interfaceid_match(interface_id_match, file_name=file_name)
|
|
|
|
|
|
|
|
returnvalue_match = re.search(
|
|
|
|
r'^[\s]*static const(?:expr)? ReturnValue_t[\s]*([a-zA-Z_0-9]*)[\s]*=[\s]*'
|
|
|
|
r'MAKE_RETURN_CODE[\s]*\([\s]*([x0-9a-fA-F]{1,4})[\s]*\);[\t ]*(//)?([^\n]*)',
|
|
|
|
two_lines
|
|
|
|
)
|
|
|
|
if returnvalue_match:
|
2021-06-21 12:52:01 +02:00
|
|
|
self.__handle_returnvalue_match(
|
|
|
|
name_match=returnvalue_match.group(1), file_name=file_name, description="",
|
|
|
|
number_match=returnvalue_match.group(2)
|
|
|
|
)
|
2021-06-08 12:37:10 +02:00
|
|
|
self.last_lines[1] = self.last_lines[0]
|
|
|
|
self.last_lines[0] = newline
|
|
|
|
|
2021-08-02 12:49:10 +02:00
|
|
|
def __handle_interfaceid_match(self, interface_id_match, file_name: str) -> bool:
|
|
|
|
"""Handle a match of an interface ID definition in the code.
|
|
|
|
Returns whether the interface ID was found successfully in the IF ID header files
|
|
|
|
"""
|
|
|
|
if self.get_verbosity() == VerbosityLevels.DEBUG:
|
|
|
|
LOGGER.info(f'Interface ID {interface_id_match.group(1)} found in {file_name}')
|
|
|
|
if_id_entry = self.interfaces.get(interface_id_match.group(1))
|
|
|
|
if if_id_entry is not None:
|
|
|
|
self.current_interface_id_entries["ID"] = if_id_entry[0]
|
|
|
|
else:
|
|
|
|
LOGGER.warning(
|
|
|
|
f'Interface ID {interface_id_match.group(1)} not found in IF ID dictionary'
|
|
|
|
)
|
|
|
|
return False
|
2021-06-08 12:37:10 +02:00
|
|
|
self.current_interface_id_entries["Name"] = \
|
|
|
|
self.interfaces[interface_id_match.group(1)][1]
|
|
|
|
self.current_interface_id_entries["FullName"] = interface_id_match.group(1)
|
2021-08-02 12:49:10 +02:00
|
|
|
if self.get_verbosity() == VerbosityLevels.DEBUG:
|
2021-06-08 12:37:10 +02:00
|
|
|
current_id = self.current_interface_id_entries["ID"]
|
2021-08-02 12:49:10 +02:00
|
|
|
LOGGER.info(f'Current ID: {current_id}')
|
|
|
|
return True
|
2021-06-08 12:37:10 +02:00
|
|
|
|
|
|
|
def __handle_returnvalue_match(
|
|
|
|
self, name_match: str, number_match: str, file_name: str, description: str
|
|
|
|
):
|
|
|
|
string_to_add = self.build_checked_string(
|
|
|
|
self.current_interface_id_entries["Name"], name_match, MAX_STRING_LEN,
|
|
|
|
PRINT_TRUNCATED_ENTRIES
|
|
|
|
)
|
2021-06-21 12:52:01 +02:00
|
|
|
full_id = (self.current_interface_id_entries["ID"] << 8) + \
|
|
|
|
return_number_from_string(number_match)
|
2021-06-08 12:37:10 +02:00
|
|
|
if full_id in self.return_value_dict:
|
|
|
|
# print('Duplicate returncode ' + hex(full_id) + ' from ' + file_name +
|
|
|
|
# ' was already in ' + self.return_value_dict[full_id][3])
|
|
|
|
pass
|
|
|
|
dict_tuple = (
|
2021-06-21 12:52:01 +02:00
|
|
|
string_to_add, description, number_match, file_name,
|
|
|
|
self.current_interface_id_entries["FullName"]
|
2021-06-08 12:37:10 +02:00
|
|
|
)
|
|
|
|
self.return_value_dict.update({
|
|
|
|
full_id: dict_tuple
|
|
|
|
})
|
|
|
|
self.count = self.count + 1
|
|
|
|
|
|
|
|
def _post_parsing_operation(self):
|
|
|
|
if self.print_tables:
|
|
|
|
PrettyPrinter.pprint(self.return_value_dict)
|
|
|
|
self.mib_table = self.return_value_dict
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def export_to_file(filename: str, list_of_entries: dict, file_separator: str):
|
|
|
|
file = open(filename, "w")
|
|
|
|
for entry in list_of_entries.items():
|
2021-06-21 12:47:03 +02:00
|
|
|
file.write(
|
|
|
|
hex(entry[0]) + file_separator + entry[1][0] + file_separator + entry[1][1] +
|
2021-06-21 12:54:03 +02:00
|
|
|
file_separator + entry[1][2] + file_separator + entry[1][3] + file_separator +
|
|
|
|
entry[1][4] + '\n'
|
2021-06-21 12:47:03 +02:00
|
|
|
)
|
2021-06-08 12:37:10 +02:00
|
|
|
file.close()
|
|
|
|
|
|
|
|
def build_checked_string(self, first_part, second_part, max_string_len: int,
|
|
|
|
print_truncated_entries: bool):
|
|
|
|
""" Build a checked string """
|
|
|
|
my_str = first_part + '_' + self.convert(second_part)
|
|
|
|
if len(my_str) > max_string_len:
|
|
|
|
if print_truncated_entries:
|
2021-08-02 12:49:10 +02:00
|
|
|
LOGGER.warning(f'Entry {my_str} too long. Will truncate.')
|
2021-06-08 12:37:10 +02:00
|
|
|
my_str = my_str[0:max_string_len]
|
|
|
|
else:
|
|
|
|
# print("Entry: " + myStr + " is all right.")
|
|
|
|
pass
|
|
|
|
return my_str
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def convert(name):
|
|
|
|
single_strings = name.split('_')
|
|
|
|
new_string = ''
|
|
|
|
for one_string in single_strings:
|
|
|
|
one_string = one_string.lower()
|
|
|
|
one_string = one_string.capitalize()
|
|
|
|
new_string = new_string + one_string
|
|
|
|
return new_string
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def clean_up_description(descr_string):
|
|
|
|
description = descr_string.lstrip('!<- ')
|
|
|
|
if description == '':
|
|
|
|
description = ' '
|
|
|
|
return description
|
|
|
|
|
|
|
|
|
|
|
|
def return_number_from_string(a_string):
|
|
|
|
if a_string.startswith('0x'):
|
|
|
|
return int(a_string, 16)
|
|
|
|
if a_string.isdigit():
|
|
|
|
return int(a_string)
|
2021-08-02 12:49:10 +02:00
|
|
|
LOGGER.warning(f'Illegal number representation: {a_string}')
|
2021-06-08 12:37:10 +02:00
|
|
|
return 0
|
|
|
|
|