Adding a transformation¶
Finally, we will define a transformation for our language and add a task, command-tasks, command, and menu item for it.
Open the main Stratego file helloworld/src/main.str
.
Stratego. is a meta-language for defining term (AST) transformations through rewrite rules.
We will add a silly transformation that replaces all instances of World()
with Hello()
.
Add the following code to the end of the Stratego file:
rules
replace-world: Hello() -> Hello()
replace-world: World() -> Hello()
replace-worlds = topdown(try(replace-world))
The replace-world
rule passes Hello()
terms but rewrites World()
terms to Hello()
.
The replace-worlds
strategy tries to apply replace-world
in a top-down manner over the entire AST.
src/main.str
full contents
module main
imports
statixruntime
statix/api
injections/-
signatures/-
rules // Analysis
pre-analyze = explicate-injections-helloworld-Start
post-analyze = implicate-injections-helloworld-Start
editor-analyze = stx-editor-analyze(pre-analyze, post-analyze|"main", "programOk")
rules
replace-world: Hello() -> Hello()
replace-world: World() -> Hello()
replace-worlds = topdown(try(replace-world))
```
Now we add a task and command-task for this transformation.
We define two separate tasks to keep separate
1. the act of transforming the program, and
2. feeding back the result of that transformation to the user that executes a command.
This practice later allows us to reuse the first task in a different task if we need to.
Right-click the `mb.helloworld.task` package and create the `HelloWorldReplaceWorlds` class and replace the entire Java file with:
```{ .java .annotate linenums="1" }
package mb.helloworld.task;
import mb.helloworld.HelloWorldClassLoaderResources;
import mb.helloworld.HelloWorldScope;
import mb.pie.api.ExecContext;
import mb.pie.api.stamp.resource.ResourceStampers;
import mb.stratego.pie.AstStrategoTransformTaskDef;
import javax.inject.Inject;
import java.io.IOException;
@HelloWorldScope
public class HelloWorldReplaceWorlds extends AstStrategoTransformTaskDef {
private final HelloWorldClassLoaderResources classloaderResources;
@Inject
public HelloWorldReplaceWorlds( // 1
HelloWorldClassLoaderResources classloaderResources,
HelloWorldGetStrategoRuntimeProvider getStrategoRuntimeProvider
) {
super(getStrategoRuntimeProvider, "replace-worlds"); // 2
this.classloaderResources = classloaderResources;
}
@Override public String getId() { // 3
return getClass().getName();
}
@Override protected void createDependencies(ExecContext context) throws IOException { // 4
context.require(classloaderResources.tryGetAsLocalResource(getClass()), ResourceStampers.hashFile());
}
}
This task extends AstStrategoTransformTaskDef
which is a convenient abstract class for creating tasks that run Stratego transformations by implementing a constructor and a couple of methods:
- The constructor should inject
HelloWorldClassLoaderResources
which we again will use to create a self-dependency, andHelloWorldGetStrategoRuntimeProvider
which is a task that Spoofax generates for your language, which provides a Stratego runtime to execute strategies with. - The
HelloWorldGetStrategoRuntimeProvider
instance is provided to the superclass constructor, along with the strategy that we want this task to execute, which is"replace-worlds"
. - We override
getId
ofTaskDef
again to give this task a unique identifier. - We override
createDependencies
ofAstStrategoTransformTaskDef
to create a self-dependency.
Then create the HelloWorldShowReplaceWorlds
class and replace the entire Java file with:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
|
This class very similar to HelloWorldShowParsedAst
, but runs the HelloWorldReplaceWorlds
task on the parsed AST, transforming the AST.
Now open helloworld/spoofax.cfg
again and register the tasks by adding:
task-def mb.helloworld.task.HelloWorldReplaceWorlds
let showReplaceWorlds = task-def mb.helloworld.task.HelloWorldShowReplaceWorlds
Then add a command for it by adding:
let showReplaceWorldsCommand = command-def {
task-def = showReplaceWorlds
display-name = "Replace world with hello"
parameters = [
file = parameter {
type = java mb.resource.ResourceKey
argument-providers = [Context(ReadableResource)]
}
]
}
Finally, add menu items for the command by adding:
editor-context-menu [
menu "Transform" [
command-action {
command-def = showReplaceWorldsCommand
execution-type = Once
}
command-action {
command-def = showReplaceWorldsCommand
execution-type = Continuous
}
]
]
resource-context-menu [
menu "Transform" [
command-action {
command-def = showReplaceWorldsCommand
execution-type = Once
required-resource-types = [File]
}
]
]
Build the project so that we can test our changes. Test the command similarly to testing the "Show parsed AST" command.