Skip to main content
Version: Next

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​

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

Add Dependencies​

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

dependencies {
implementation(files("deps/clparser-with-dependencies-1.0.1-all.jar"))
}

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.

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

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.