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
| Tool | Typical use |
|---|---|
| Read-only walk | Inventory, call listing, metrics |
| Transform | Rename identifiers, strip constructs, transpile to a target language |
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"
}
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.
- Kotlin
- Java
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) }
}
package com.strumenta.example;
import com.strumenta.kolasu.commercial.LicenseManager;
import com.strumenta.kolasu.javalib.ASTTransformer;
import com.strumenta.kolasu.javalib.Traversing;
import com.strumenta.kolasu.model.Node;
import com.strumenta.kolasu.transformation.IdentityTransformationKt;
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;
import java.util.ArrayList;
import java.util.List;
import kotlin.jvm.JvmClassMappingKt;
public class Transform {
public static class CallTarget extends Node {
private final String name;
public CallTarget(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public static class CallSummary extends Node {
private final List<CallTarget> targets;
public CallSummary(List<CallTarget> targets) {
this.targets = targets;
}
public List<CallTarget> getTargets() {
return targets;
}
}
public static void collectProgramCalls(File rpgFile, File license) {
LicenseManager.INSTANCE.registerLicense(license);
var result = RPGKolasuParser.parserFromExtension(rpgFile).parse(rpgFile);
CompilationUnit root = result.getRoot();
if (root == null) {
return;
}
List<Issue> issues = new ArrayList<>(result.getIssues());
ASTTransformer transformer = new ASTTransformer(
issues,
true,
false,
true,
IdentityTransformationKt.getIDENTTITY_TRANSFORMATION()
);
transformer.registerNodeFactory(JvmClassMappingKt.getKotlinClass(CompilationUnit.class),
(cu, t) -> {
List<CallTarget> targets = new ArrayList<>();
Traversing.walkDescendantsBreadthFirst(cu, CallProgramStatement.class).forEach(call ->
targets.add((CallTarget) t.transform(call))
);
return new CallSummary(targets);
});
transformer.registerNodeFactory(JvmClassMappingKt.getKotlinClass(CallProgramStatement.class),
(call, t) -> new CallTarget(call.getProgramName().toString()));
CallSummary summary = (CallSummary) transformer.transform(root);
summary.getTargets().forEach(target -> System.out.println(target.getName()));
}
}
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.