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. The CL parser does not ship a dedicated visitor hierarchy — use these traversal helpers instead.
Add Dependencies
- Kotlin
- Java
repositories {
mavenLocal()
mavenCentral()
flatDir {
dirs("deps")
}
}
dependencies {
implementation(files("deps/clparser-with-dependencies-1.0.1-all.jar"))
}
repositories {
mavenLocal()
mavenCentral()
flatDir {
dirs("deps")
}
}
dependencies {
implementation(files("deps/clparser-with-dependencies-1.0.1-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.cl.model.CLCompilationUnit
import com.strumenta.cl.parser.CLKolasuParser
import com.strumenta.kolasu.commercial.LicenseManager
import com.strumenta.kolasu.traversing.walk
import java.io.File
private const val INDENT = " "
fun walkAst(clFile: File, license: File) {
LicenseManager.registerLicense(license)
val result = CLKolasuParser().parse(clFile)
val root: CLCompilationUnit = 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.cl.model.CLCompilationUnit;
import com.strumenta.cl.parser.CLKolasuParser;
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 java.io.File;
public class WalkAst {
private static final String INDENTATION = " ";
public static void walkAst(File clFile, File license) {
LicenseManager.INSTANCE.registerLicense(license);
ParsingResult<CLCompilationUnit> result = new CLKolasuParser().parse(clFile);
CLCompilationUnit 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 with standard stream APIs, or use overloads such as walkDescendants that take a node class.
The example below lists procedures, DCL variables, and external program calls (CallProgramCommand):
- Kotlin
- Java
import com.strumenta.cl.model.CLCompilationUnit
import com.strumenta.cl.model.CLLiteral
import com.strumenta.cl.model.CLReferenceExpr
import com.strumenta.cl.model.CallProgramCommand
import com.strumenta.cl.model.DeclareVariableCommand
import com.strumenta.cl.model.Procedure
import com.strumenta.cl.model.QualifiedObjectName
import com.strumenta.kolasu.traversing.walkDescendants
fun formatExpr(expr: com.strumenta.cl.model.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 listTopLevelConstructs(root: CLCompilationUnit) {
println("Procedures:")
root.procedures.forEach { proc: Procedure ->
println(" ${proc.parameters.size} parameter(s), ${proc.commands.size} command(s)")
}
println("DCL variables:")
root.walkDescendants(DeclareVariableCommand::class).forEach { dcl ->
println(" ${dcl.name}: ${dcl.type}")
}
println("CALL targets:")
root.walkDescendants(CallProgramCommand::class).forEach { call ->
println(" ${formatExpr(call.pgm)}")
}
}
import com.strumenta.cl.model.CLCompilationUnit;
import com.strumenta.cl.model.CLExpression;
import com.strumenta.cl.model.CLLiteral;
import com.strumenta.cl.model.CLReferenceExpr;
import com.strumenta.cl.model.CallProgramCommand;
import com.strumenta.cl.model.DeclareVariableCommand;
import com.strumenta.cl.model.Procedure;
import com.strumenta.cl.model.QualifiedObjectName;
import com.strumenta.kolasu.javalib.Traversing;
public class FilterAst {
public static String formatExpr(CLExpression expr) {
if (expr instanceof CLReferenceExpr ref) {
return ref.getName().getName();
}
if (expr instanceof CLLiteral lit) {
return lit.getText();
}
if (expr instanceof QualifiedObjectName q) {
return q.getContainer() != null ? q.getContainer() + "/" + q.getName() : q.getName();
}
return expr == null ? "?" : expr.toString();
}
public static void listTopLevelConstructs(CLCompilationUnit root) {
System.out.println("Procedures:");
for (Procedure proc : root.getProcedures()) {
System.out.println(
" " + proc.getParameters().size() + " parameter(s), "
+ proc.getCommands().size() + " command(s)");
}
System.out.println("DCL variables:");
Traversing.walkDescendantsBreadthFirst(root, DeclareVariableCommand.class, dcl ->
System.out.println(" " + dcl.getName() + ": " + dcl.getType()));
System.out.println("CALL targets:");
Traversing.walkDescendantsBreadthFirst(root, CallProgramCommand.class, call ->
System.out.println(" " + formatExpr(call.getPgm())));
}
}
Inspect node properties
Kolasu discovers AST properties automatically. In Kotlin you can iterate node.properties directly; in Java use node.getProperties(). Both list scalar fields and node-reference properties — useful when exploring an unfamiliar command type among the large generated catalog.
- 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.PropertyDescription;
public class DumpProperties {
public static void dumpProperties(Node node) {
for (PropertyDescription property : node.getProperties()) {
if (!property.getProvideNodes()) {
System.out.println(" " + property.getName() + " = " + property.getValue());
} else {
System.out.println(" " + property.getName() + " = <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 binding &VAR references, subroutine names, and GOTO labels, use symbol resolution.