From 9fb75caca0a7abc0650e8c61395b787030362755 Mon Sep 17 00:00:00 2001 From: Daniel Coutinho Date: Mon, 22 Sep 2025 20:59:18 +0000 Subject: [PATCH 1/4] parsing FUNCTION block --- ComplexParser.py | 13 +++++++++++++ GlueGenerator.py | 47 +++++++++++++++++++++++++++-------------------- STParser.py | 23 ++++++++++++++++++++++- xml2st.py | 21 +++++++++++---------- 4 files changed, 73 insertions(+), 31 deletions(-) diff --git a/ComplexParser.py b/ComplexParser.py index ed035e27b..143df9219 100644 --- a/ComplexParser.py +++ b/ComplexParser.py @@ -318,6 +318,19 @@ def __getSTLines(self): ): break else: + if ( + len( + list( + filter( + lambda x: not EMPTY_LINE.match(x) + and not isinstance(x, _InsertLine), + block.lines, + ) + ) + ) + > 2 + ): + lines.extend(self.__getBlockLines(block)) lines.append(self.__rewriteStructsAsFunctionBlocks()) continue lines.extend(self.__getBlockLines(block)) 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() From 8ef90e82e6bb9b27c2d9f8202b1186a928aeb41a Mon Sep 17 00:00:00 2001 From: Daniel Coutinho Date: Tue, 23 Sep 2025 16:58:47 +0000 Subject: [PATCH 2/4] hotfix --- ComplexParser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ComplexParser.py b/ComplexParser.py index 143df9219..062c4fc06 100644 --- a/ComplexParser.py +++ b/ComplexParser.py @@ -322,8 +322,8 @@ def __getSTLines(self): len( list( filter( - lambda x: not EMPTY_LINE.match(x) - and not isinstance(x, _InsertLine), + lambda x: not isinstance(x, _InsertLine) + and not EMPTY_LINE.match(x), block.lines, ) ) From a24c2227190a742e21987f53c7b765b43b79cdcf Mon Sep 17 00:00:00 2001 From: Daniel Coutinho Date: Wed, 24 Sep 2025 14:46:23 +0000 Subject: [PATCH 3/4] fixed struct transformation only for complex types --- ComplexParser.py | 94 +++++++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/ComplexParser.py b/ComplexParser.py index 062c4fc06..7502fd28d 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,47 @@ 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 - """ - - 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)) - - 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) - ] - - 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 + 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) + + simple_types_names = [s.name for s in self.simple_types] + + complex_blocks = [b for b in block.inner_blocks if not b.simple] + + 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 simple_types_names + if complex_block.simple: + changes = True + self.simple_types.append(complex_block) + 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 simple_types_names: + complex_blocks.append(complex_block) + break + else: + complex_block.simple = True + self.simple_types.append(complex_block) + changes = True + + self.simple_types_names = [s.name for s in self.simple_types] + 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,7 +318,7 @@ 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: @@ -364,7 +368,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 @@ -406,7 +410,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( From 1d8b56c538ed76774be08f3d3b5611495e2f8bf7 Mon Sep 17 00:00:00 2001 From: Daniel Coutinho Date: Wed, 24 Sep 2025 19:57:29 +0000 Subject: [PATCH 4/4] code rabbit suggestions --- ComplexParser.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ComplexParser.py b/ComplexParser.py index 7502fd28d..41cb9c9b3 100644 --- a/ComplexParser.py +++ b/ComplexParser.py @@ -240,7 +240,7 @@ def __analyseTypes(self, block): changes = [b for b in block.inner_blocks if b.simple] self.simple_types.extend(changes) - simple_types_names = [s.name for s in self.simple_types] + self.simple_types_names = [s.name for s in self.simple_types] complex_blocks = [b for b in block.inner_blocks if not b.simple] @@ -250,26 +250,28 @@ def __analyseTypes(self, block): complex_blocks = [] for complex_block in complex_blocks_copy: if isinstance(complex_block, _VariableInstance): - complex_block.simple = complex_block.data_type in simple_types_names + complex_block.simple = ( + complex_block.data_type in self.simple_types_names + ) if complex_block.simple: changes = True self.simple_types.append(complex_block) - simple_types_names.append(complex_block.name) + 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 simple_types_names: + 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.simple_types_names = [s.name for s in self.simple_types] 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)]