Skip to main content
Version: 2.2.0

Transform the AST

Parsers produce ASTs so you can analyze and rewrite code. Starlasu (Kolasu) supports transforming trees: visit each node, replace it or transform it, and create a new root.

Use transforms for refactoring helpers, normalization passes, and the first stage of a transpiler. Transformers are the foundational feature we at Strumenta use to build our transpilers. The RPG analyzer also uses ASTTransformer internally when building PlantUML sequence diagrams.

When to use transforms​

ToolTypical use
Read-only walkInventory, call listing, metrics
TransformRename identifiers, strip constructs, transpile to a target language

Add Dependencies​

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

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

Collect external program calls​

This example builds a small summary tree of every CallProgramStatement in a compilation unit. ASTTransformer.transform gives you complete control on every transformation.

The consequence of this approach is that you need to define a transformation not just for every node you need to transform, but for every node that could contain that node — otherwise the transformer applies the default transformation you selected when creating the ASTTransformer.

import com.strumenta.kolasu.commercial.LicenseManager
import com.strumenta.kolasu.model.Node
import com.strumenta.kolasu.transformation.ASTTransformer
import com.strumenta.kolasu.transformation.IDENTTITY_TRANSFORMATION
import com.strumenta.kolasu.traversing.walkDescendants
import com.strumenta.kolasu.validation.Issue
import com.strumenta.rpgparser.RPGKolasuParser
import com.strumenta.rpgparser.model.CallProgramStatement
import com.strumenta.rpgparser.model.CompilationUnit
import java.io.File

data class CallTarget(val name: String) : Node()

data class CallSummary(val targets: List<CallTarget>) : Node()

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

val issues: MutableList<Issue> = result.issues.toMutableList()
val transformer = ASTTransformer(
issues = issues,
allowGenericNode = true,
defaultTransformation = IDENTTITY_TRANSFORMATION
)

transformer.registerNodeFactory(CompilationUnit::class) { cu: CompilationUnit, t ->
val calls = cu.walkDescendants(CallProgramStatement::class)
.map { t.transform(it) as CallTarget }
.toList()
CallSummary(calls)
}

transformer.registerNodeFactory(CallProgramStatement::class) { call: CallProgramStatement, _ ->
CallTarget(call.programName.toString())
}

val summary = transformer.transform(root) as CallSummary
summary.targets.forEach { println(it.name) }
}

Typically you define a transformation for every node, unless you have a well-defined and specific need — for example collapsing all CALL sites into a call-graph summary, or mapping RPG statements onto another language's AST.