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
- 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"
}
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.
- Kotlin
- Java
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()
}
}
import com.strumenta.kolasu.commercial.LicenseManager;
import com.strumenta.kolasu.javalib.Traversing;
import com.strumenta.kolasu.model.Node;
import com.strumenta.kolasu.parsing.ParsingResult;
import com.strumenta.rpgparser.RPGKolasuParser;
import com.strumenta.rpgparser.model.CompilationUnit;
import java.io.File;
public class WalkAst {
private static final String INDENTATION = " ";
public static void walkAst(File rpgFile, File license) {
LicenseManager.INSTANCE.registerLicense(license);
ParsingResult<CompilationUnit> result =
RPGKolasuParser.parserFromExtension(rpgFile).parse(rpgFile);
CompilationUnit root = result.getRoot();
if (root == null) {
return;
}
Traversing.walk(root).forEach(node -> {
for (Node parent = node.getParent(); parent != null; parent = parent.getParent()) {
System.out.print(INDENTATION);
}
System.out.print(node.getSimpleNodeType());
if (node.getPosition() != null) {
System.out.print(node.getPosition());
}
System.out.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):
- Kotlin
- Java
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}")
}
}
import com.strumenta.kolasu.javalib.Traversing;
import com.strumenta.rpgparser.model.CallProgramStatement;
import com.strumenta.rpgparser.model.CompilationUnit;
import com.strumenta.rpgparser.model.Procedure;
import com.strumenta.rpgparser.model.Subroutine;
public class FilterAst {
public static void listTopLevelConstructs(CompilationUnit root) {
System.out.println("Procedures:");
for (Procedure proc : root.getProcedures()) {
System.out.println(" " + proc.getName()
+ " (" + proc.statements().size() + " statement(s))");
}
System.out.println("Subroutines (including main):");
for (Subroutine sr : root.getSubroutinesIncludingMain()) {
System.out.println(" " + sr.getName() + " [" + sr.getCategory() + "]");
}
System.out.println("CALL (program) statements:");
Traversing.walkDescendantsBreadthFirst(root, CallProgramStatement.class).forEach(call ->
System.out.println(" " + call.getProgramName() + " @ " + call.getPosition())
);
}
}
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.
- Kotlin
- Java
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>")
}
}
}
import com.strumenta.kolasu.model.Node;
import com.strumenta.kolasu.model.Processing;
public class DumpProperties {
public static void dumpProperties(Node node) {
Processing.processProperties(node, property -> {
if (!property.getProvideNodes()) {
System.out.println(" " + property.getName() + " = " + property.getValue());
} else {
System.out.println(" " + property.getName() + " = <Nodes>");
}
return null;
});
}
}
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.