Skip to main content
Version: Next

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​

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

dependencies {
implementation(files("deps/clparser-with-dependencies-1.0.1-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.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()
}
}

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):

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)}")
}
}

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.

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 binding &VAR references, subroutine names, and GOTO labels, use symbol resolution.