Skip to main content

Walk and Filter the AST

After parsing, the AST is a tree of Kolasu (Starlasu) nodes. The first thing most tools do is walk that tree: visit every node, filter by type, and read properties or source positions.

This recipe shows the two main approaches bundled with the library: a full depth-first walk, and a targeted search for nodes of a given type.

Add Dependencies

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

dependencies {
implementation(files("deps/rpgparser-2.2.0-all.jar"))
}

Walk every node

Use walk() (Kotlin) or Traversing.walk (Java) to visit the entire tree in depth-first order. Each node exposes its runtime class, its position in the source, and its parent link.

import com.strumenta.kolasu.commercial.LicenseManager
import com.strumenta.kolasu.traversing.walk
import com.strumenta.rpgparser.RPGKolasuParser
import com.strumenta.rpgparser.model.CompilationUnit
import java.io.File

private const val INDENT = " "

fun walkAst(rpgFile: File, license: File) {
LicenseManager.registerLicense(license)
val result = RPGKolasuParser.parserFromExtension(rpgFile).parse(rpgFile)
val root: CompilationUnit = result.root ?: return

root.walk().forEach { node ->
var depth = 0
var parent = node.parent
while (parent != null) {
depth++
parent = parent.parent
}
print(INDENT.repeat(depth))
print(node.simpleNodeType)
node.position?.let { print(it) }
println()
}
}

Filter nodes by type

When you need a controllable way to inspect the AST, use any of the walk methods. You can choose depth-first (the default), breadth-first, or leaves-to-root order. Filter by type with standard stream APIs, or with overloads such as walkDescendants.

The example below lists procedures, subroutines, and external program calls (CallProgramStatement):

import com.strumenta.kolasu.traversing.walkDescendants
import com.strumenta.rpgparser.model.CallProgramStatement
import com.strumenta.rpgparser.model.CompilationUnit
import com.strumenta.rpgparser.model.Procedure
import com.strumenta.rpgparser.model.Subroutine

fun listTopLevelConstructs(root: CompilationUnit) {
println("Procedures:")
root.procedures.forEach { proc: Procedure ->
println(" ${proc.name} (${proc.statements().size} statement(s))")
}

println("Subroutines (including main):")
root.subroutinesIncludingMain.forEach { sr: Subroutine ->
println(" ${sr.name} [${sr.category}]")
}

println("CALL (program) statements:")
root.walkDescendants(CallProgramStatement::class).forEach { call ->
println(" ${call.programName} @ ${call.position}")
}
}

Inspect node properties

Kolasu discovers AST properties automatically. In Kotlin you can iterate node.properties directly; in Java use Processing.processProperties. Both list scalar fields and node-reference properties — useful when exploring an unfamiliar node type or building debug output.

fun dumpProperties(node: com.strumenta.kolasu.model.Node) {
node.properties.forEach { property ->
if (!property.providesNodes) {
println(" ${property.name} = ${property.value}")
} else {
println(" ${property.name} = <Nodes>")
}
}
}

When to stop walking

A simple walk is enough for one-off filters and exploration. When you need to rewrite nodes into another model, use an AST transform. For codebase-wide call and copybook graphs, prefer the analyzer module after symbol resolution.