import re
import networkx as nx
import matplotlib.pyplot as plt
class ABAPDependencyAnalyzer:
def __init__(self, sources: str):
"""
:param sources: single string containing all ABAP source code for
all objects
"""
# Split the sources into objects using a simple heuristic
self.sources = self._split_sources(sources)
self.objects = set(self.sources.keys())
self.graph = nx.DiGraph()
def _split_sources(self, sources: str):
"""
Splits the input string into a dict mapping object name to a tuple
of
(source code string, starting line number in the original text).
Assumes objects start with lines like 'REPORT <name>',
'FUNCTION <name>',
'TABLE <name>', or 'CLASS <name> DEFINITION'.
"""
pattern = re.compile(
r'^(REPORT|FUNCTION|TABLE|CLASS)\s+([A-Z][A-Z0-9_]+)',
re.MULTILINE | re.IGNORECASE
)
matches = list(pattern.finditer(sources))
result = {}
for i, match in enumerate(matches):
obj_name = match.group(2).upper()
start_idx = match.start()
end_idx = matches[i+1].start() if i+1 < len(matches) else
len(sources)
code_block = sources[start_idx:end_idx]
# Calculate starting line number
start_line = sources[:start_idx].count('\n') + 1
result[obj_name] = (code_block.strip(), start_line)
return result
def build_graph(self):
"""
Constructs a directed graph where each node is an ABAP object
and
edges represent "object A references object B".
"""
token_pattern = re.compile(r'\b([A-Z][A-Z0-9_]+)\b')
self.graph.clear()
self.graph.add_nodes_from(self.objects)
for obj, (code, _) in self.sources.items():
tokens = set(token_pattern.findall(code.upper()))
refs = tokens.intersection(self.objects) - {obj}
for ref in refs:
self.graph.add_edge(obj, ref)
return self.graph
def plot_graph(self, title="ABAP Dependency Graph"):
"""
Visualizes the dependency graph using NetworkX and Matplotlib.
"""
plt.figure(figsize=(8, 6))
pos = nx.spring_layout(self.graph)
nx.draw(self.graph, pos, with_labels=True, arrows=True,
node_size=600, font_size=9)
plt.title(title)
plt.tight_layout()
plt.show()
def get_impacted_objects(self, snippet: str):
"""
Parses a raw snippet of ABAP code to identify all directly
referenced
objects, then returns that set plus all their transitive
dependencies.
"""
token_pattern = re.compile(r'\b([A-Z][A-Z0-9_]+)\b')
tokens = set(token_pattern.findall(snippet.upper()))
initial = tokens.intersection(self.objects)
impacted = set(initial)
for obj in initial:
impacted.update(nx.descendants(self.graph, obj))
return list(impacted)
def get_impacted_snippets(self, snippet: str):
"""
Returns a list of dicts for each impacted object with keys:
- 'dependent_obj': the object name
- 'snippet_code': the full code block for that object
- 'start': starting line number in the original source text
- 'end': ending line number in the original source text
"""
impacted = self.get_impacted_objects(snippet)
results = []
for obj in impacted:
code_block, start_line = self.sources[obj]
# Split and strip each line to remove leading/trailing
whitespace
lines = [line.rstrip() for line in code_block.splitlines()]
snippet_code = "\n".join(lines)
end_line = start_line + len(lines) - 1
results.append({
"dependent_obj": obj,
"snippet_code": snippet_code,
"start": start_line,
"end": end_line
})
return results
# Example usage
if __name__ == "__main__":
full_sources = """
REPORT Z_PROG1.
CALL FUNCTION Z_FUNC1.
SELECT * FROM Z_TABLE1.
FUNCTION Z_FUNC1.
WRITE: / Z_TABLE1.
ENDFUNCTION.
TABLE Z_TABLE1.
FIELDS: FIELD1.
CLASS Z_LIB1 DEFINITION.
METHODS: DO_SOMETHING.
ENDCLASS.
CLASS Z_LIB1 IMPLEMENTATION.
METHOD DO_SOMETHING.
READ TABLE Z_TABLE1.
ENDMETHOD.
ENDCLASS.
"""
snippet = "CALL FUNCTION Z_FUNC1."
analyzer = ABAPDependencyAnalyzer(full_sources)
analyzer.build_graph()
analyzer.plot_graph()
impacted_snippets = analyzer.get_impacted_snippets(snippet)
print(impacted_snippets)