-
Notifications
You must be signed in to change notification settings - Fork 0
Authoring Generators
This page describes the process required to create a custom generator. The required steps can be summarized to:
- Decide on plugin programming language (e.g. Kotlin or Java)
- Decide on transport technology (e.g. official SAMT HTTP or a custom transport configuration)
- Create SAMT plugin with a new generator
- Release SAMT plugin
All you need to get started with writing your own SAMT generator is a working Java or Kotlin development environment. SAMT is built and tested against Java 17, we recommend targeting a modern JVM version. This guide will use Kotlin for the example code, but the same principles apply to Java or any other JVM language as well. An example of a generator written in Java can be found at samtkit/example-java-plugin.
You will need to add the following dependencies to your project:
Maven
<dependencies>
<dependency>
<groupId>tools.samt</groupId>
<artifactId>public-api</artifactId>
<version>${samt.version}</version>
</dependency>
<dependency>
<groupId>tools.samt</groupId>
<artifactId>common</artifactId>
<version>${samt.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>tools.samt</groupId>
<artifactId>compiler</artifactId>
<version>${samt.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>tools.samt</groupId>
<artifactId>codegen</artifactId>
<version>${samt.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>tools.samt</groupId>
<artifactId>samt-config</artifactId>
<version>${samt.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Gradle (Kotlin)
dependencies {
implementation("tools.samt:public-api:$samtVersion")
testImplementation("tools.samt:common:$samtVersion")
testImplementation("tools.samt:compiler:$samtVersion")
testImplementation("tools.samt:codegen:$samtVersion")
testImplementation("tools.samt:samt-config:$samtVersion")
}
Gradle (Groovy)
dependencies {
implementation 'tools.samt:public-api:$samtVersion'
testImplementation 'tools.samt:common:$samtVersion'
testImplementation 'tools.samt:compiler:$samtVersion'
testImplementation 'tools.samt:codegen:$samtVersion'
testImplementation 'tools.samt:samt-config:$samtVersion'
}
New generators can be created by implementing the Generator interface and overriding the name
field and generate
method.
For the purposes of this example, we will create a generator that creates a markdown file containing a list of all the declared enums in the SAMT project.
This can be achieved by iterating over all the provided SAMT packages and accessing the list of declared enums.
Once the output has been generated, it must be returned as a list of CodegenFile objects.
object EnumMarkdownGenerator : Generator {
override val name: String = "enum-markdown-generator"
override fun generate(params: GeneratorParams): List<CodegenFile> {
// extract all declared enums
val enums = params.packages.flatMap { it.enums }
// create the markdown file
val markdown = buildString {
appendLine("# Enums")
appendLine()
// We simply generate a flat list of enums, completely ignoring their SAMT package
// Alternatively, qualifiedName would include the package, but we ignore it here for simplicity
enums.forEach { enum ->
appendLine("## ${enum.name}")
appendLine()
enum.values.forEach { value ->
appendLine("- ${value.name}")
}
appendLine()
}
}
// Construct generated file, this will be placed in the configured "out" directory
val output = CodegenFile(
name = "enums.md",
content = markdown
)
return listOf(output)
}
}
When developing a custom generator, you'll want to test it locally. This can be done by adding the required
You can however create a new project, include SAMT as a dependency and manually execute the generator that way. This approach, even though it works, is rather cumbersome and will be improved in the future.
fun main() {
// Parse example samt.yaml configuration
val (configuration ,_) = CliConfigParser.readConfig(Path("path/to/samt.yaml"), controller) ?: return
// Read *.samt files from disk
val sourceFiles = collectSamtFiles(configuration.source.toUri()).readSamtSource(controller)
// Parse *.samt files
val fileNodes = sourceFiles.map { source ->
val context = controller.getOrCreateContext(source)
val tokenStream = Lexer.scan(source.content.reader(), context)
Parser.parse(source, tokenStream, context)
}
// Build semantic model
val model = SemanticModel.build(fileNodes, controller)
if (controller.hasErrors()) {
// Nicely printing all errors isn't easy here, it's best to use a debugger to analyze the controller
println("Has errors!")
return
}
// Register your generators / transport parsers here
Codegen.registerGenerator(MyGenerator)
Codegen.registerTransportParser(MyTransportParser)
for (generatorConfig in configuration.generators) {
for (generatedFile in Codegen.generate(model, generatorConfig, controller)) {
println(generatedFile.filepath)
println("-".repeat(generatedFile.filepath.length))
println(generatedFile.source)
}
}
}
DISCLAIMER: As currently implemented, there is no way to load your own generator into the SAMT CLI. This feature hasn't been implemented yet, but will possible in the future.