diff --git a/ComplexParser.py b/ComplexParser.py index ed035e27b..41cb9c9b3 100644 --- a/ComplexParser.py +++ b/ComplexParser.py @@ -136,8 +136,9 @@ def __init__(self): self.structs = [] self.programs = [] self.csv_vars = [] - self.array_dependant = [] - self.array_dependant_names = [] + self.simple_types = [] + self.simple_types_names = [] + self.complex_types = [] self.complex_structs = [] self.function_blocks = [] self.__loader = FileSystemLoader( @@ -153,8 +154,9 @@ def __clear(self): self.structs = [] self.programs = [] self.csv_vars = [] - self.array_dependant = [] - self.array_dependant_names = [] + self.simple_types = [] + self.simple_types_names = [] + self.complex_types = [] self.complex_structs = [] self.function_blocks = [] @@ -220,7 +222,7 @@ def __getBlockLines(self, block, ignoreComplexStructs=True): if ( ignoreComplexStructs and isinstance(block, _StructInstance) - and block.name not in self.array_dependant_names + and block.name in self.complex_types ): return [] for line in block.lines: @@ -231,45 +233,49 @@ def __getBlockLines(self, block, ignoreComplexStructs=True): return lines - def __isBlockAnArrayType(self, block): - for a in self.arrays: - if block.name == a.data_type: - return True - return False - def __analyseTypes(self, block): """ - Separate structs that have to be manually parsed + Separate custom defined types into simple and complex types. """ + changes = [b for b in block.inner_blocks if b.simple] + self.simple_types.extend(changes) - changes = [b for b in block.inner_blocks if self.__isBlockAnArrayType(b)] - - for c in changes: - self.__checkSubtypes(c, block.inner_blocks) - - self.array_dependant_names = list(set(self.array_dependant_names)) + self.simple_types_names = [s.name for s in self.simple_types] - complex_blocks = [ - b for b in block.inner_blocks if b.name not in self.array_dependant_names - ] - self.complex_structs = [ - b for b in complex_blocks if isinstance(b, _StructInstance) - ] + complex_blocks = [b for b in block.inner_blocks if not b.simple] - def __checkSubtypes(self, subtype, blocks): - if isinstance(subtype, _StructInstance): - self.array_dependant.append(subtype) - self.array_dependant_names.append(subtype.name) - for inner_block in subtype.inner_blocks: - block = self.__findBlock(inner_block, blocks) - if block: - self.__checkSubtypes(block, blocks) - - def __findBlock(self, block, block_list): - for b in block_list: - if b.name == block.data_type: - return b - return None + while changes: + changes = False + complex_blocks_copy = complex_blocks.copy() + complex_blocks = [] + for complex_block in complex_blocks_copy: + if isinstance(complex_block, _VariableInstance): + complex_block.simple = ( + complex_block.data_type in self.simple_types_names + ) + if complex_block.simple: + changes = True + self.simple_types.append(complex_block) + self.simple_types_names.append(complex_block.name) + else: + complex_blocks.append(complex_block) + elif isinstance(complex_block, _StructInstance): + for inner_block in [ + i for i in complex_block.inner_blocks if not i.simple + ]: + if inner_block.data_type not in self.simple_types_names: + complex_blocks.append(complex_block) + break + else: + complex_block.simple = True + self.simple_types.append(complex_block) + self.simple_types_names.append(complex_block.name) + changes = True + + self.complex_types.extend([b.name for b in complex_blocks]) + self.complex_structs.extend( + [b for b in complex_blocks if isinstance(b, _StructInstance)] + ) def __separateOuterBlocks(self): """ @@ -314,10 +320,23 @@ def __getSTLines(self): for inner_block in block.inner_blocks: if ( not isinstance(inner_block, _StructInstance) - or inner_block.name in self.array_dependant_names + or inner_block.name in self.simple_types_names ): break else: + if ( + len( + list( + filter( + lambda x: not isinstance(x, _InsertLine) + and not EMPTY_LINE.match(x), + block.lines, + ) + ) + ) + > 2 + ): + lines.extend(self.__getBlockLines(block)) lines.append(self.__rewriteStructsAsFunctionBlocks()) continue lines.extend(self.__getBlockLines(block)) @@ -351,7 +370,7 @@ def __getCustomType(self, type_name): """ Get the complex type by its name. """ - for custom_type in self.array_dependant: + for custom_type in self.simple_types: if custom_type.name == type_name: return custom_type return None @@ -393,7 +412,7 @@ def __spreadDeclarations( prefix = f"{prefix}.{block.name.upper()}" if block.data_type in BASE_TYPES and write_base_types: self.csv_vars.append({"name": prefix, "type": block.data_type}) - elif block.data_type in self.array_dependant_names: + elif block.data_type in self.simple_types_names: type = self.__getCustomType(block.data_type) if type: self.__spreadDeclarations( diff --git a/GlueGenerator.py b/GlueGenerator.py index 2d90aa704..fedcf6f9e 100644 --- a/GlueGenerator.py +++ b/GlueGenerator.py @@ -8,16 +8,19 @@ # __LOCATED_VAR(INT,__QW0,Q,W,0) # __LOCATED_VAR(BOOL,__QX0_1,Q,X,0,1) + class GlueGenerator: def __init__(self): - self.__loader = FileSystemLoader(os.path.join(paths.AbsDir(__file__), "templates")) + self.__loader = FileSystemLoader( + os.path.join(paths.AbsDir(__file__), "templates") + ) def __glue_logic(self, varName): """ Generate glue logic based on variable type. """ - + # Extract indices print(f"Linking variable {varName}") try: @@ -28,38 +31,38 @@ def __glue_logic(self, varName): raise Exception(f"Error parsing variable name '{varName}': {e}") kind = varName[2] # I, Q, M - sub = varName[3] # X, B, W, D, L + sub = varName[3] # X, B, W, D, L - if kind == 'I': - if sub == 'X': + if kind == "I": + if sub == "X": return f"bool_input_ptr[{pos1}][{pos2}] = (IEC_BOOL *){varName};" - elif sub == 'B': + elif sub == "B": return f"byte_input_ptr[{pos1}] = (IEC_BYTE *){varName};" - elif sub == 'W': + elif sub == "W": return f"int_input_ptr[{pos1}] = (IEC_UINT *){varName};" - elif sub == 'D': + elif sub == "D": return f"dint_input_ptr[{pos1}] = (IEC_UDINT *){varName};" - elif sub == 'L': + elif sub == "L": return f"lint_input_ptr[{pos1}] = (IEC_ULINT *){varName};" - elif kind == 'Q': - if sub == 'X': + elif kind == "Q": + if sub == "X": return f"bool_output_ptr[{pos1}][{pos2}] = (IEC_BOOL *){varName};" - elif sub == 'B': + elif sub == "B": return f"byte_output_ptr[{pos1}] = (IEC_BYTE *){varName};" - elif sub == 'W': + elif sub == "W": return f"int_output_ptr[{pos1}] = (IEC_UINT *){varName};" - elif sub == 'D': + elif sub == "D": return f"dint_output_ptr[{pos1}] = (IEC_UDINT *){varName};" - elif sub == 'L': + elif sub == "L": return f"lint_output_ptr[{pos1}] = (IEC_ULINT *){varName};" - elif kind == 'M': - if sub == 'W': + elif kind == "M": + if sub == "W": return f"int_memory_ptr[{pos1}] = (IEC_UINT *){varName};" - elif sub == 'D': + elif sub == "D": return f"dint_memory_ptr[{pos1}] = (IEC_UDINT *){varName};" - elif sub == 'L': + elif sub == "L": return f"lint_memory_ptr[{pos1}] = (IEC_ULINT *){varName};" raise Exception(f"Unhandled variable type: {varName}") @@ -74,7 +77,11 @@ def __parse_line(self, line): print(f"Warning: Line '{line.strip()}' does not match expected format.") return None varType, varName = m.group(1), m.group(2) - return {"type": varType, "name": varName, "glue_code": self.__glue_logic(varName)} + return { + "type": varType, + "name": varName, + "glue_code": self.__glue_logic(varName), + } def generate_glue_variables(self, located_vars_lines): """ diff --git a/STParser.py b/STParser.py index 70fc79a5f..3caef9887 100644 --- a/STParser.py +++ b/STParser.py @@ -20,7 +20,7 @@ "BYTE", "WORD", "DWORD", - "LWORD" + "LWORD", ] @@ -137,8 +137,27 @@ def GetInfo(self, line): return None +class _Function(_NamedBlock): + def __init__(self, name="function"): + super().__init__(name) + self.start = re.compile( + rf"^{self.name}\s+(?P[A-Za-z_][A-Za-z0-9_]*)\s*:\s*(?P[A-Za-z_][A-Za-z0-9_]*)\s*$" + ) + + def GetInfo(self, line): + match = self.start.match(line) + if match: + return { + "name": match.group("name"), + "type": self.name, + "return_type": match.group("return_type"), + } + return None + + TYPE = _Block("type") FUNCTION_BLOCK = _NamedBlock("function_block") +FUNCTION = _Function() PROGRAM = _NamedBlock("program") CONFIGURATION = _NamedBlock("configuration") RESOURCE = _NamedBlock("resource") @@ -159,6 +178,7 @@ def GetInfo(self, line): STRUCT, ARRAY, VARIABLE, + FUNCTION, ] CLOSABLE_BLOCKS = [ @@ -168,4 +188,5 @@ def GetInfo(self, line): CONFIGURATION, RESOURCE, STRUCT, + FUNCTION, ] diff --git a/xml2st.py b/xml2st.py index 81660b0de..811fb1b5f 100755 --- a/xml2st.py +++ b/xml2st.py @@ -81,14 +81,17 @@ def append_debugger_to_st(st_file, debug_text): f.write("\n") f.write(c_debug) + def generate_gluevars(located_vars_file): - if not os.path.isfile(located_vars_file) or not located_vars_file.lower().endswith(".h"): + if not os.path.isfile(located_vars_file) or not located_vars_file.lower().endswith( + ".h" + ): print( f"Error: Invalid file '{located_vars_file}'. A path to a LOCATED_VARIABLES.h file is expected.", file=sys.stderr, ) return None - + # Read the LOCATED_VARIABLES.h file with open(located_vars_file, "r") as f: located_vars = f.readlines() @@ -109,15 +112,13 @@ def generate_gluevars(located_vars_file): # Print success message print(f"Glue variables saved to {glue_vars_file}") + def main(): parser = argparse.ArgumentParser( description="Process a PLCopen XML file and transpiles it into a Structured Text (ST) program." ) parser.add_argument( - "--generate-st", - metavar=("XML_FILE"), - type=str, - help="The path to the XML file" + "--generate-st", metavar=("XML_FILE"), type=str, help="The path to the XML file" ) parser.add_argument( "--generate-debug", @@ -127,10 +128,10 @@ def main(): help="Paths to the ST file and the variables CSV file", ) parser.add_argument( - "--generate-gluevars", - metavar=("LOCATED_VARS_FILE"), - type=str, - help="The path to the LOCATED_VARIABLES.h file" + "--generate-gluevars", + metavar=("LOCATED_VARS_FILE"), + type=str, + help="The path to the LOCATED_VARIABLES.h file", ) args = parser.parse_args()