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.
When to use transforms
| Tool | Typical use |
|---|---|
| Read-only walk | Inventory, call graphs, metrics |
| Transform | Rename identifiers, strip constructs, transpile to a target model |
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"
}
Collect CALL sites into a summary tree
This example builds a small summary tree of every CallProgramCommand in a compilation unit. ASTTransformer.transform gives you complete control on every transformation.
When you need a complete rewritten tree, register a transformer for every containing node type (or rely on the default identity transformation). For a call-site summary, collecting with walkDescendants and wrapping the result is often enough.
- Kotlin
- Java
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.QualifiedObjectName
import com.strumenta.cl.parser.CLKolasuParser
import com.strumenta.kolasu.commercial.LicenseManager
import com.strumenta.kolasu.model.Node
import com.strumenta.kolasu.transformation.ASTTransformer
import com.strumenta.kolasu.traversing.walkDescendants
import java.io.File
fun formatExpr(expr: 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()
}
data class CallTarget(val name: String) : Node()
data class CallSummary(val calls: List<CallTarget>) : Node()
fun collectProgramCalls(clFile: File, license: File) {
LicenseManager.registerLicense(license)
val result = CLKolasuParser().parse(clFile)
val root = result.root ?: return
val transformer = ASTTransformer(allowGenericNode = false)
transformer.registerNodeFactory(CLCompilationUnit::class) { cu ->
val calls =
cu.walkDescendants(CallProgramCommand::class)
.map { CallTarget(formatExpr(it.pgm)) }
.toList()
CallSummary(calls)
}
val summary = transformer.transform(root) as CallSummary
summary.calls.forEach { println("CALL ${it.name}") }
}
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.QualifiedObjectName;
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;
import java.util.ArrayList;
import java.util.List;
public class CollectCalls {
public static class CallTarget extends Node {
public final String name;
public CallTarget(String name) { this.name = name; }
}
public static class CallSummary extends Node {
public final List<CallTarget> calls;
public CallSummary(List<CallTarget> calls) { this.calls = calls; }
}
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 collectProgramCalls(File clFile, File license) {
LicenseManager.INSTANCE.registerLicense(license);
ParsingResult<CLCompilationUnit> result = new CLKolasuParser().parse(clFile);
CLCompilationUnit root = result.getRoot();
if (root == null) return;
List<CallTarget> calls = new ArrayList<>();
Traversing.walkDescendantsBreadthFirst(root, CallProgramCommand.class, call ->
calls.add(new CallTarget(formatExpr(call.getPgm()))));
CallSummary summary = new CallSummary(calls);
summary.calls.forEach(c -> System.out.println("CALL " + c.name));
}
}
Typically you define a transformation for every node when building a complete transpiler. For well-scoped extractions — collapsing all CALL sites into a call-graph summary, or mapping CL commands onto another language's AST — a combination of walkDescendants and a small target model is often enough to start.