Skip to main content
Version: 1.0.1

Call Graph

CL programs orchestrate IBM i work through CALL, CALLPRC, and CALLSUBR. Walking those command nodes produces a call surface you can use for impact analysis, migration scoping, or a simple dependency graph across a directory of members.

Program and bound-procedure targets are expressions (names, qualified objects, or variables). They are not resolved by the semantics module — treat them as string-like edges. Subroutine calls can be linked to definitions after symbol resolution.

Add Dependencies​

repositories {
mavenLocal()
mavenCentral()
flatDir {
dirs("deps")
}
}

dependencies {
implementation(files("deps/clparser-with-dependencies-1.0.1-all.jar"))
}

Extract CALL / CALLPRC / CALLSUBR from one file​

import com.strumenta.cl.model.CLExpression
import com.strumenta.cl.model.CLLiteral
import com.strumenta.cl.model.CLReferenceExpr
import com.strumenta.cl.model.CallBoundProcedureCommand
import com.strumenta.cl.model.CallProgramCommand
import com.strumenta.cl.model.CallSubroutineCommand
import com.strumenta.cl.model.QualifiedObjectName
import com.strumenta.cl.parser.CLKolasuParser
import com.strumenta.kolasu.commercial.LicenseManager
import com.strumenta.kolasu.traversing.walkDescendants
import java.io.File

fun formatExpr(expr: CLExpression?): String =
when (expr) {
is CLReferenceExpr -> expr.name.name
is CLLiteral -> expr.text
is QualifiedObjectName ->
if (expr.container != null) "${expr.container}/${expr.name}" else expr.name
null -> "?"
else -> expr.toString()
}

fun printCallSurface(clFile: File, license: File) {
LicenseManager.registerLicense(license)
val root = CLKolasuParser().parse(clFile).root ?: return

println("CALL (programs):")
root.walkDescendants(CallProgramCommand::class).forEach { call ->
println(" ${formatExpr(call.pgm)} (${call.parameters.size} parm expr(s))")
}

println("CALLPRC (bound procedures):")
root.walkDescendants(CallBoundProcedureCommand::class).forEach { call ->
println(" ${formatExpr(call.prc)}")
}

println("CALLSUBR (subroutines):")
root.walkDescendants(CallSubroutineCommand::class).forEach { call ->
println(" ${formatExpr(call.subr)}")
}
}

Build edges across a directory​

Walk every .cl / .clp / .clle file and emit caller → callee edges. Member names are a useful caller id when the source does not encode a program name in the AST.

fun buildCallEdges(projectDir: File, license: File): List<Pair<String, String>> {
LicenseManager.registerLicense(license)
val parser = CLKolasuParser()
val edges = mutableListOf<Pair<String, String>>()

projectDir.walkTopDown()
.filter { it.isFile && it.extension.lowercase() in setOf("cl", "clp", "clle") }
.forEach { file ->
val root = parser.parse(file).root ?: return@forEach
val caller = file.nameWithoutExtension
root.walkDescendants(CallProgramCommand::class).forEach { call ->
edges += caller to formatExpr(call.pgm)
}
}
return edges
}

Node types: CallProgramCommand (pgm), CallBoundProcedureCommand (prc), CallSubroutineCommand (subr).