Program Inventory
Before refactoring or migrating RPG, teams need a high-level map of each member: procedures and subroutines, data definitions, file specifications, and DDS record formats. The parser turns source files into a structured inventory you can print, export to CSV, or feed into a dashboard.
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"
}
Collect structural entries
CompilationUnit exposes the main structural collections directly: procedures, subroutines / subroutinesIncludingMain, dataDefinitions, fileDescriptions, and dataDescriptions (DDS).
- Kotlin
- Java
import com.strumenta.kolasu.commercial.LicenseManager
import com.strumenta.rpgparser.RPGKolasuParser
import com.strumenta.rpgparser.model.CompilationUnit
import com.strumenta.rpgparser.model.Constant
import com.strumenta.rpgparser.model.Prototype
import com.strumenta.rpgparser.model.RecordFormat
import com.strumenta.rpgparser.model.StandaloneField
import com.strumenta.rpgparser.model.StandardDataStructure
import java.io.File
data class InventoryEntry(val kind: String, val detail: String, val line: Int)
fun inventory(root: CompilationUnit): List<InventoryEntry> {
val entries = mutableListOf<InventoryEntry>()
root.procedures.forEach { proc ->
entries += InventoryEntry("PROCEDURE", proc.name, proc.position?.start?.line ?: 0)
}
root.subroutines.forEach { sr ->
entries += InventoryEntry("SUBROUTINE", sr.name, sr.position?.start?.line ?: 0)
}
root.fileDescriptions.forEach { file ->
entries += InventoryEntry("FILE", file.name, file.position?.start?.line ?: 0)
}
root.dataDefinitions.forEach { dd ->
val kind = when (dd) {
is StandaloneField -> "D-FIELD"
is StandardDataStructure -> "D-DS"
is Prototype -> "D-PR"
is Constant -> "D-CONST"
else -> dd.simpleNodeType
}
entries += InventoryEntry(kind, dd.name ?: "?", dd.position?.start?.line ?: 0)
}
root.dataDescriptions.filterIsInstance<RecordFormat>().forEach { rf ->
entries += InventoryEntry("DDS-FORMAT", rf.name ?: "?", rf.position?.start?.line ?: 0)
}
return entries
}
fun printInventory(rpgFile: File, license: File) {
LicenseManager.registerLicense(license)
val root = RPGKolasuParser.parserFromExtension(rpgFile).parse(rpgFile).root ?: return
inventory(root).forEach { entry ->
println("${entry.kind.padEnd(12)} ${entry.detail.padEnd(24)} line ${entry.line}")
}
}
import com.strumenta.kolasu.commercial.LicenseManager;
import com.strumenta.rpgparser.RPGKolasuParser;
import com.strumenta.rpgparser.model.CompilationUnit;
import com.strumenta.rpgparser.model.Constant;
import com.strumenta.rpgparser.model.DataDefinition;
import com.strumenta.rpgparser.model.DataDescriptionSpecification;
import com.strumenta.rpgparser.model.FileSpecification;
import com.strumenta.rpgparser.model.Procedure;
import com.strumenta.rpgparser.model.Prototype;
import com.strumenta.rpgparser.model.RecordFormat;
import com.strumenta.rpgparser.model.StandaloneField;
import com.strumenta.rpgparser.model.StandardDataStructure;
import com.strumenta.rpgparser.model.Subroutine;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class ProgramInventory {
public record InventoryEntry(String kind, String detail, int line) {}
public static List<InventoryEntry> inventory(CompilationUnit root) {
List<InventoryEntry> entries = new ArrayList<>();
for (Procedure proc : root.getProcedures()) {
entries.add(new InventoryEntry("PROCEDURE", proc.getName(), lineOf(proc)));
}
for (Subroutine sr : root.getSubroutines()) {
entries.add(new InventoryEntry("SUBROUTINE", sr.getName(), lineOf(sr)));
}
for (FileSpecification file : root.getFileDescriptions()) {
entries.add(new InventoryEntry("FILE", file.getName(), lineOf(file)));
}
for (DataDefinition dd : root.getDataDefinitions()) {
String kind;
if (dd instanceof StandaloneField) kind = "D-FIELD";
else if (dd instanceof StandardDataStructure) kind = "D-DS";
else if (dd instanceof Prototype) kind = "D-PR";
else if (dd instanceof Constant) kind = "D-CONST";
else kind = dd.getSimpleNodeType();
String name = dd.getName() != null ? dd.getName() : "?";
entries.add(new InventoryEntry(kind, name, lineOf(dd)));
}
for (DataDescriptionSpecification desc : root.getDataDescriptions()) {
if (desc instanceof RecordFormat rf) {
String name = rf.getName() != null ? rf.getName() : "?";
entries.add(new InventoryEntry("DDS-FORMAT", name, lineOf(rf)));
}
}
return entries;
}
private static int lineOf(com.strumenta.kolasu.model.Node node) {
return node.getPosition() != null ? node.getPosition().getStart().getLine() : 0;
}
public static void printInventory(File rpgFile, File license) {
LicenseManager.INSTANCE.registerLicense(license);
CompilationUnit root =
RPGKolasuParser.parserFromExtension(rpgFile).parse(rpgFile).getRoot();
if (root == null) {
return;
}
for (InventoryEntry entry : inventory(root)) {
System.out.printf("%-12s %-24s line %d%n",
entry.kind(), entry.detail(), entry.line());
}
}
}
Summarize a member at a glance
For dashboards, aggregate counts rather than listing every entry:
fun summarize(root: CompilationUnit): Map<String, Int> = mapOf(
"procedures" to root.procedures.size,
"subroutines" to root.subroutines.size,
"mainStatements" to root.mainStatements.size,
"dataDefinitions" to root.dataDefinitions.size,
"fileDescriptions" to root.fileDescriptions.size,
"ddsRecordFormats" to root.dataDescriptions.filterIsInstance<RecordFormat>().size,
)
For file I/O operations (READ, WRITE, CHAIN, EXFMT), see File I/O and display operations. For cross-member CALL / COPY graphs, see symbol resolution and the analyzer.