@@ -209,6 +209,23 @@ def from_declaration(
209209 filename = filename ,
210210 ** Summary .get_meta_kwargs (item .meta ),
211211 )
212+ elif isinstance (item , tf .ExternalVariableDeclaration ):
213+ location = str (getattr (item .spec , "location" , None ))
214+ init = str (getattr (item .spec , "init" , None ))
215+ type_ = getattr (init , "full_type_name" , str (item .spec ))
216+ base_type = getattr (init , "base_type_name" , str (item .spec ))
217+ result [item .name ] = DeclarationSummary (
218+ name = str (item .name ),
219+ item = item ,
220+ location = location ,
221+ block = block_header ,
222+ type = type_ ,
223+ base_type = base_type ,
224+ value = "None" , # TODO
225+ parent = parent .name if parent is not None else "" ,
226+ filename = filename ,
227+ ** Summary .get_meta_kwargs (item .meta ),
228+ )
212229 else :
213230 raise NotImplementedError (f"TODO: { type (item )} " )
214231
@@ -479,7 +496,9 @@ class FunctionBlockSummary(Summary):
479496 methods : List [MethodSummary ] = field (default_factory = list )
480497 properties : List [PropertySummary ] = field (default_factory = list )
481498
482- def __getitem__ (self , key : str ) -> DeclarationSummary :
499+ def __getitem__ (
500+ self , key : str
501+ ) -> Union [DeclarationSummary , MethodSummary , PropertySummary , ActionSummary ]:
483502 if key in self .declarations :
484503 return self .declarations [key ]
485504 for item in self .actions + self .methods + self .properties :
@@ -528,7 +547,7 @@ def squash_base_extends(
528547 if self .extends is None :
529548 return self
530549
531- extends_from = function_blocks .get (self .extends , None )
550+ extends_from = function_blocks .get (str ( self .extends ) , None )
532551 if extends_from is None :
533552 return self
534553
@@ -539,6 +558,7 @@ def squash_base_extends(
539558 declarations .update (self .declarations )
540559 actions = list (extends_from .actions ) + self .actions
541560 methods = list (extends_from .methods ) + self .methods
561+ properties = list (extends_from .properties ) + self .properties
542562 return FunctionBlockSummary (
543563 name = self .name ,
544564 comments = extends_from .comments + self .comments ,
@@ -551,6 +571,101 @@ def squash_base_extends(
551571 declarations = declarations ,
552572 actions = actions ,
553573 methods = methods ,
574+ properties = properties ,
575+ squashed = True ,
576+ )
577+
578+
579+ @dataclass
580+ class InterfaceSummary (Summary ):
581+ """Summary representation of an Interfae."""
582+
583+ name : str
584+ source_code : str
585+ item : tf .Interface
586+ extends : Optional [str ]
587+ squashed : bool
588+ declarations : Dict [str , DeclarationSummary ] = field (default_factory = dict )
589+ methods : List [MethodSummary ] = field (default_factory = list )
590+ properties : List [PropertySummary ] = field (default_factory = list )
591+ # TwinCAT IDE doesn't allow for actions to be added to interfaces, it
592+ # seems. Overlap with methods?
593+ # actions: List[ActionSummary] = field(default_factory=list)
594+
595+ def __getitem__ (
596+ self , key : str
597+ ) -> Union [DeclarationSummary , MethodSummary , PropertySummary ]:
598+ if key in self .declarations :
599+ return self .declarations [key ]
600+ for item in self .methods + self .properties :
601+ if item .name == key :
602+ return item
603+ raise KeyError (key )
604+
605+ @property
606+ def declarations_by_block (self ) -> Dict [str , Dict [str , DeclarationSummary ]]:
607+ result = {}
608+ for decl in self .declarations .values ():
609+ result .setdefault (decl .block , {})[decl .name ] = decl
610+ return result
611+
612+ @classmethod
613+ def from_interface (
614+ cls ,
615+ itf : tf .Interface ,
616+ source_code : Optional [str ] = None ,
617+ filename : Optional [pathlib .Path ] = None ,
618+ ) -> InterfaceSummary :
619+ if source_code is None :
620+ source_code = str (itf )
621+
622+ summary = InterfaceSummary (
623+ name = itf .name ,
624+ item = itf ,
625+ source_code = source_code ,
626+ filename = filename ,
627+ extends = itf .extends .name if itf .extends else None ,
628+ squashed = False ,
629+ ** Summary .get_meta_kwargs (itf .meta ),
630+ )
631+
632+ for decl in itf .declarations :
633+ summary .declarations .update (
634+ DeclarationSummary .from_block (decl , parent = itf , filename = filename )
635+ )
636+
637+ return summary
638+
639+ def squash_base_extends (
640+ self , interfaces : Dict [str , InterfaceSummary ]
641+ ) -> InterfaceSummary :
642+ """Squash the "EXTENDS" INTERFACE into this one."""
643+ if self .extends is None :
644+ return self
645+
646+ extends_from = interfaces .get (str (self .extends ), None )
647+ if extends_from is None :
648+ return self
649+
650+ if extends_from .extends :
651+ extends_from = extends_from .squash_base_extends (interfaces )
652+
653+ declarations = dict (extends_from .declarations )
654+ declarations .update (self .declarations )
655+ methods = list (extends_from .methods ) + self .methods
656+ properties = list (extends_from .properties ) + self .properties
657+ return InterfaceSummary (
658+ name = self .name ,
659+ comments = extends_from .comments + self .comments ,
660+ pragmas = extends_from .pragmas + self .pragmas ,
661+ meta = self .meta ,
662+ filename = self .filename ,
663+ source_code = "\n \n " .join ((extends_from .source_code , self .source_code )),
664+ item = self .item ,
665+ extends = self .extends ,
666+ declarations = declarations ,
667+ properties = properties ,
668+ methods = methods ,
554669 squashed = True ,
555670 )
556671
@@ -622,7 +737,7 @@ def squash_base_extends(
622737 if self .extends is None :
623738 return self
624739
625- extends_from = data_types .get (self .extends , None )
740+ extends_from = data_types .get (str ( self .extends ) , None )
626741 if extends_from is None :
627742 return self
628743
@@ -631,7 +746,6 @@ def squash_base_extends(
631746
632747 declarations = dict (extends_from .declarations )
633748 declarations .update (self .declarations )
634- raise
635749 return DataTypeSummary (
636750 name = self .name ,
637751 type = self .type ,
@@ -767,15 +881,16 @@ class CodeSummary:
767881 data_types : Dict [str , DataTypeSummary ] = field (default_factory = dict )
768882 programs : Dict [str , ProgramSummary ] = field (default_factory = dict )
769883 globals : Dict [str , GlobalVariableSummary ] = field (default_factory = dict )
770- # interfaces: Dict[str, ... ] = field(default_factory=dict) # TODO
884+ interfaces : Dict [str , InterfaceSummary ] = field (default_factory = dict )
771885
772886 def __str__ (self ):
773887 attr_to_header = {
888+ "data_types" : "Data Types" ,
889+ "globals" : "Global Variable Declarations" ,
890+ "interfaces" : "Interface Declarations" ,
774891 "functions" : "Functions" ,
775892 "function_blocks" : "Function Blocks" ,
776- "data_types" : "Data Types" ,
777893 "programs" : "Programs" ,
778- "globals" : "Global Variable Declarations" ,
779894 }
780895 summary_text = []
781896 for attr , header in attr_to_header .items ():
@@ -892,6 +1007,7 @@ def append(self, other: CodeSummary, namespace: Optional[str] = None):
8921007 self .data_types .update (other .data_types )
8931008 self .globals .update (other .globals )
8941009 self .programs .update (other .programs )
1010+ self .interfaces .update (other .interfaces )
8951011
8961012 if namespace :
8971013 # LCLS_General.GVL_Logger and GVL_Logger are equally valid
@@ -906,8 +1022,13 @@ def append(self, other: CodeSummary, namespace: Optional[str] = None):
9061022 # for name, item in other.programs.items():
9071023 # self.programs[f"{namespace}.{name}"] = item
9081024
1025+ self .squash ()
1026+
9091027 @staticmethod
910- def from_parse_results (all_parsed_items : list [ParseResult ]) -> CodeSummary :
1028+ def from_parse_results (
1029+ all_parsed_items : list [ParseResult ],
1030+ squash : bool = True ,
1031+ ) -> CodeSummary :
9111032 result = CodeSummary ()
9121033
9131034 def get_code_by_meta (parsed : ParseResult , meta : Optional [tf .Meta ]) -> str :
@@ -958,16 +1079,19 @@ def new_context(summary: Summary):
9581079 def push_context (summary : Summary ):
9591080 context .append (summary )
9601081
961- def get_pou_context () -> Union [ProgramSummary , FunctionBlockSummary ]:
1082+ def get_pou_context () -> Union [
1083+ ProgramSummary , FunctionBlockSummary , InterfaceSummary
1084+ ]:
9621085 for item in reversed (context ):
963- if isinstance (item , (ProgramSummary , FunctionBlockSummary )):
1086+ if isinstance (
1087+ item , (ProgramSummary , FunctionBlockSummary , InterfaceSummary )
1088+ ):
9641089 return item
9651090
9661091 raise ValueError (
9671092 "Expected to parse a POU prior to this but none were in the context "
9681093 "list. Code summaries of PROPERTY objects, for example, require "
969- "that a FUNCTION_BLOCK (or other PROPERTY-containing POU) be "
970- "parsed previously."
1094+ "that a FUNCTION_BLOCK, INTERFACE, or PROGRAM be parsed previously."
9711095 )
9721096
9731097 for parsed in all_parsed_items :
@@ -1047,6 +1171,14 @@ def get_pou_context() -> Union[ProgramSummary, FunctionBlockSummary]:
10471171 )
10481172 result .programs [item .name ] = summary
10491173 new_context (summary )
1174+ elif isinstance (item , tf .Interface ):
1175+ summary = InterfaceSummary .from_interface (
1176+ item ,
1177+ source_code = get_code_by_meta (parsed , item .meta ),
1178+ filename = parsed .filename ,
1179+ )
1180+ result .interfaces [item .name ] = summary
1181+ new_context (summary )
10501182 elif isinstance (item , tf .StatementList ):
10511183 if parsed .item .type != SourceType .action :
10521184 add_implementation (parsed , item )
@@ -1064,21 +1196,33 @@ def get_pou_context() -> Union[ProgramSummary, FunctionBlockSummary]:
10641196 )
10651197 parent .actions .append (action )
10661198 else :
1199+ raise ValueError (type (item ))
10671200 logger .warning ("Unhandled: %s" , type (item ))
10681201
1069- for name , item in list (result .function_blocks .items ()):
1202+ if squash :
1203+ result .squash ()
1204+
1205+ return result
1206+
1207+ def squash (self ) -> None :
1208+ """Squash derived interfaces/etc to include base summaries."""
1209+ for name , item in list (self .function_blocks .items ()):
10701210 if item .extends and not item .squashed :
1071- result .function_blocks [name ] = item .squash_base_extends (
1072- result .function_blocks
1211+ self .function_blocks [name ] = item .squash_base_extends (
1212+ self .function_blocks
10731213 )
10741214
1075- for name , item in list (result .data_types .items ()):
1215+ for name , item in list (self .data_types .items ()):
10761216 if item .extends and not item .squashed :
1077- result .data_types [name ] = item .squash_base_extends (
1078- result .data_types
1217+ self .data_types [name ] = item .squash_base_extends (
1218+ self .data_types
10791219 )
10801220
1081- return result
1221+ for name , item in list (self .interfaces .items ()):
1222+ if item .extends and not item .squashed :
1223+ self .interfaces [name ] = item .squash_base_extends (
1224+ self .interfaces
1225+ )
10821226
10831227
10841228@dataclass
0 commit comments