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.

Add Dependencies

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

dependencies {
implementation(files("deps/sas-parser-with-dependencies-1.6.5-all.jar"))
}

</TabItem>
<TabItem value="java" label="Java">

<CodeBlock language="gradle">
{`repositories {
mavenLocal()
mavenCentral()
flatDir {
dirs("deps")
}
}

dependencies {
implementation(files("deps/sas-parser-with-dependencies-${LATEST_VERSION}-all.jar"))
implementation "com.strumenta.kolasu:kolasu-javalib:1.5.96"
}`}
</CodeBlock>

</TabItem>
</Tabs>

## 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.

<Tabs>
<TabItem value="kotlin" label="Kotlin">

```kotlin
import com.strumenta.kolasu.traversing.walk
import com.strumenta.sas.ast.SourceFile
import com.strumenta.kolasu.commercial.LicenseManager
import com.strumenta.sas.parser.SASLanguage
import java.io.File

private const val INDENT = " "

fun walkAst(sasFile: File, license: File) {
LicenseManager.registerLicense(license)
val sas = SASLanguage()
sas.parseNativeSQL = true
val result = sas.parse(sasFile)
val root: SourceFile = 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))
val simpleName = node.simpleNodeType
print(simpleName)
node.position?.let {
print(node.position)
}
println()
}
}

Filter nodes by type

When you need a simple and controllable way to inspect the AST and work with it, you can use any of the walk methods. These methods also provides quick ways to filter and pick the type of walk. You can pick the type of walk between depth-first (the default), breadth first or even from leaves to root. You can filter the type of nodes you work with either using standard Kotlin or Java methods to filter a stream, or using specific overloading of the walk methods that provides a class filter. For instance, walkDescendants. In this sample code we filter for DataStep — using walkDescendantsBreadthFirst or walkDescendants with that node class.

import com.strumenta.kolasu.traversing.walk
import com.strumenta.kolasu.traversing.walkDescendants
import com.strumenta.sas.ast.Identifier
import com.strumenta.sas.ast.SourceFile
import com.strumenta.sas.ast.VariableExpression
import com.strumenta.sas.ast.datastep.DataStep
import com.strumenta.sas.ast.sql.SqlProcedure

fun listTopLevelConstructs(root: SourceFile) {
println("DATA steps:")
root.walkDescendants(DataStep::class).forEach { step ->
val outputs = step.datasets.map { spec ->
val name = spec.name
val textName = when(name) {
is VariableExpression -> name.variable
is Identifier -> name.name
else -> name.toString()
}
if(spec.library != null)
"${spec.library}.${textName}"
else
textName
}
println(" outputs: ${outputs.joinToString()}")
}

println("PROC SQL blocks:")
root.walk().filterIsInstance<SqlProcedure>().forEach { proc ->
println(" ${proc.sqlStatements.size} SQL statement(s)")
}
}

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 ->
// scalar properties (strings, numbers, booleans, …)
if (!property.providesNodes) {
println(" ${property.name} = ${property.value}")
}
// properties whose value is another AST node or a list of nodes
else {
println(" ${property.name} = <Nodes>")
}
}
}

When to stop walking

A simple Traversing callback is enough for one-off filters and exploration. When logic spans many node types, accumulates state, or needs a complex control or the order of the visit, consider a typed visitor instead.