Copybook and Cross-File Dependencies
Production RPG lives in libraries of programs linked by /COPY and /INCLUDE, external CALL targets, and shared DDS. For a best-effort sketch of copybook edges you can walk CopyStatement and IncludeStatement on each AST. For resolved, codebase-wide graphs, use symbol resolution and the analyzer.
When to use this pattern
| Approach | Good for |
|---|---|
| Walk COPY / INCLUDE directives | Quick inventory of include sites from a single file |
rpgCodebase + resolveSymbolsForRPG | Loading copybooks and DDS into a shared symbol table |
RPGAnalyzer.dependencyGraph() | File- and behavior-level edges (CopyInclude, CallProgram, InvokeSubroutine, …) |
Add Dependencies
- Kotlin
- Java
repositories {
mavenLocal()
mavenCentral()
flatDir {
dirs("deps")
}
}
dependencies {
implementation(files("deps/rpgparser-2.2.0-all.jar"))
}
repositories {
mavenLocal()
mavenCentral()
flatDir {
dirs("deps")
}
}
dependencies {
implementation(files("deps/rpgparser-2.2.0-all.jar"))
implementation "com.strumenta.kolasu:kolasu-javalib:1.5.107"
}
Extract COPY / INCLUDE targets from one file
Each copy/include statement wraps a directive with memberName, optional fileName, and optional libraryName:
- Kotlin
- Java
import com.strumenta.kolasu.commercial.LicenseManager
import com.strumenta.kolasu.traversing.walkDescendants
import com.strumenta.rpgparser.RPGKolasuParser
import com.strumenta.rpgparser.model.CopyStatement
import com.strumenta.rpgparser.model.IncludeStatement
import java.io.File
data class IncludeEdge(val kind: String, val member: String, val file: String?, val library: String?)
fun copyIncludeEdges(rpgFile: File, license: File): List<IncludeEdge> {
LicenseManager.registerLicense(license)
val root = RPGKolasuParser.parserFromExtension(rpgFile).parse(rpgFile).root ?: return emptyList()
val edges = mutableListOf<IncludeEdge>()
root.walkDescendants(CopyStatement::class).forEach { copy ->
val d = copy.copyDirective
edges += IncludeEdge("COPY", d.memberName, d.fileName, d.libraryName)
}
root.walkDescendants(IncludeStatement::class).forEach { include ->
val d = include.includeDirective
edges += IncludeEdge("INCLUDE", d.memberName, d.fileName, d.libraryName)
}
return edges
}
import com.strumenta.kolasu.commercial.LicenseManager;
import com.strumenta.kolasu.javalib.Traversing;
import com.strumenta.rpgparser.RPGKolasuParser;
import com.strumenta.rpgparser.model.CompilationUnit;
import com.strumenta.rpgparser.model.CopyDirective;
import com.strumenta.rpgparser.model.CopyStatement;
import com.strumenta.rpgparser.model.IncludeDirective;
import com.strumenta.rpgparser.model.IncludeStatement;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class CopyInclude {
public record IncludeEdge(String kind, String member, String file, String library) {}
public static List<IncludeEdge> copyIncludeEdges(File rpgFile, File license) {
LicenseManager.INSTANCE.registerLicense(license);
CompilationUnit root =
RPGKolasuParser.parserFromExtension(rpgFile).parse(rpgFile).getRoot();
if (root == null) {
return List.of();
}
List<IncludeEdge> edges = new ArrayList<>();
Traversing.walkDescendantsBreadthFirst(root, CopyStatement.class).forEach(copy -> {
CopyDirective d = copy.getCopyDirective();
edges.add(new IncludeEdge("COPY", d.getMemberName(), d.getFileName(), d.getLibraryName()));
});
Traversing.walkDescendantsBreadthFirst(root, IncludeStatement.class).forEach(include -> {
IncludeDirective d = include.getIncludeDirective();
edges.add(new IncludeEdge(
"INCLUDE", d.getMemberName(), d.getFileName(), d.getLibraryName()));
});
return edges;
}
}
Next steps
- Resolve names against a project tree with
rpgCodebase+resolveSymbolsForRPG. - Build a full dependency graph (copy edges plus CALL / EXSR / prototype links) with the analyzer.