Skip to content

Commit

Permalink
Merge pull request #13 from samtkit/codegen
Browse files Browse the repository at this point in the history
Codegen
  • Loading branch information
PascalHonegger authored May 30, 2023
2 parents c3010a0 + c35ead5 commit c1373e5
Show file tree
Hide file tree
Showing 72 changed files with 4,040 additions and 169 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,10 @@ build
ehthumbs.db
Thumbs.db

# SAMT wrapper generated files #
.samt
samtw
samtw.bat

# Random files used for debugging
specification/examples/debug.samt
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies {
kover(project(":cli"))
kover(project(":language-server"))
kover(project(":samt-config"))
kover(project(":codegen"))
}

koverReport {
Expand Down
3 changes: 3 additions & 0 deletions cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ dependencies {
implementation(project(":lexer"))
implementation(project(":parser"))
implementation(project(":semantic"))
implementation(project(":samt-config"))
implementation(project(":codegen"))
implementation(project(":public-api"))
}

application {
Expand Down
8 changes: 4 additions & 4 deletions cli/src/main/kotlin/tools/samt/cli/CliArgs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ class CliArgs {

@Parameters(commandDescription = "Compile SAMT files")
class CompileCommand {
@Parameter(description = "Files to compile, defaults to all .samt files in the current directory")
var files: List<String> = mutableListOf()
@Parameter(description = "SAMT project to compile, defaults to the 'samt.yaml' file in the current directory")
var file: String = "./samt.yaml"
}

@Parameters(commandDescription = "Dump SAMT files in various formats for debugging purposes")
Expand All @@ -25,8 +25,8 @@ class DumpCommand {
@Parameter(names = ["--types"], description = "Dump a visual representation of the resolved types")
var dumpTypes: Boolean = false

@Parameter(description = "Files to dump, defaults to all .samt files in the current directory")
var files: List<String> = mutableListOf()
@Parameter(description = "SAMT project to dump, defaults to the 'samt.yaml' file in the current directory")
var file: String = "./samt.yaml"
}

@Parameters(commandDescription = "Initialize or update the SAMT wrapper")
Expand Down
36 changes: 33 additions & 3 deletions cli/src/main/kotlin/tools/samt/cli/CliCompiler.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
package tools.samt.cli

import tools.samt.codegen.Codegen
import tools.samt.common.DiagnosticController
import tools.samt.common.DiagnosticException
import tools.samt.common.collectSamtFiles
import tools.samt.common.readSamtSource
import tools.samt.lexer.Lexer
import tools.samt.parser.Parser
import tools.samt.semantic.SemanticModel
import java.io.IOException
import kotlin.io.path.isDirectory
import kotlin.io.path.notExists

internal fun compile(command: CompileCommand, controller: DiagnosticController) {
val sourceFiles = command.files.readSamtSourceFiles(controller)
val (configuration ,_) = CliConfigParser.readConfig(command.file, controller) ?: return

if (configuration.source.notExists() || !configuration.source.isDirectory()) {
controller.reportGlobalError("Source path '${configuration.source.toUri()}' does not point to valid directory")
return
}

val sourceFiles = collectSamtFiles(configuration.source.toUri()).readSamtSource(controller)

if (controller.hasErrors()) {
return
Expand Down Expand Up @@ -40,7 +53,24 @@ internal fun compile(command: CompileCommand, controller: DiagnosticController)
}

// build up the semantic model from the AST
SemanticModel.build(fileNodes, controller)
val model = SemanticModel.build(fileNodes, controller)

// Code Generators will be called here
// if the semantic model failed to build, exit
if (controller.hasErrors()) {
return
}

if (configuration.generators.isEmpty()) {
controller.reportGlobalInfo("No generators configured, did you forget to add a 'generators' section to the 'samt.yaml' configuration?")
return
}

for (generator in configuration.generators) {
val files = Codegen.generate(model, generator, controller)
try {
OutputWriter.write(generator.output, files)
} catch (e: IOException) {
controller.reportGlobalError("Failed to write output for generator '${generator.name}': ${e.message}")
}
}
}
41 changes: 41 additions & 0 deletions cli/src/main/kotlin/tools/samt/cli/CliConfigParser.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package tools.samt.cli

import tools.samt.common.DiagnosticController
import tools.samt.common.SamtConfiguration
import tools.samt.common.SamtLinterConfiguration
import tools.samt.config.SamtConfigurationParser
import java.nio.file.InvalidPathException
import kotlin.io.path.Path
import kotlin.io.path.notExists

internal object CliConfigParser {
fun readConfig(file: String, controller: DiagnosticController): Pair<SamtConfiguration, SamtLinterConfiguration>? {
val configFile = try {
Path(file)
} catch (e: InvalidPathException) {
controller.reportGlobalError("Invalid path '${file}': ${e.message}")
return null
}
if (configFile.notExists()) {
controller.reportGlobalInfo("Configuration file '${configFile.toUri()}' does not exist, using default configuration")
}
val configuration = try {
SamtConfigurationParser.parseConfiguration(configFile)
} catch (e: Exception) {
controller.reportGlobalError("Failed to parse configuration file '${configFile.toUri()}': ${e.message}")
return null
}
val samtLintConfigFile = configFile.resolveSibling(".samtrc.yaml")
if (samtLintConfigFile.notExists()) {
controller.reportGlobalInfo("Lint configuration file '${samtLintConfigFile.toUri()}' does not exist, using default lint configuration")
}
val linterConfiguration = try {
SamtConfigurationParser.parseLinterConfiguration(samtLintConfigFile)
} catch (e: Exception) {
controller.reportGlobalError("Failed to parse lint configuration file '${samtLintConfigFile.toUri()}': ${e.message}")
return null
}

return Pair(configuration, linterConfiguration)
}
}
17 changes: 16 additions & 1 deletion cli/src/main/kotlin/tools/samt/cli/CliDumper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,27 @@ package tools.samt.cli
import com.github.ajalt.mordant.terminal.Terminal
import tools.samt.common.DiagnosticController
import tools.samt.common.DiagnosticException
import tools.samt.common.collectSamtFiles
import tools.samt.common.readSamtSource
import tools.samt.lexer.Lexer
import tools.samt.parser.Parser
import tools.samt.semantic.SemanticModel
import kotlin.io.path.isDirectory
import kotlin.io.path.notExists

internal fun dump(command: DumpCommand, terminal: Terminal, controller: DiagnosticController) {
val sourceFiles = command.files.readSamtSourceFiles(controller)
val (configuration ,_) = CliConfigParser.readConfig(command.file, controller) ?: return

if (configuration.source.notExists() || !configuration.source.isDirectory()) {
controller.reportGlobalError("Source path '${configuration.source.toUri()}' does not point to valid directory")
return
}

val sourceFiles = collectSamtFiles(configuration.source.toUri()).readSamtSource(controller)

if (controller.hasErrors()) {
return
}

if (controller.hasErrors()) {
return
Expand Down
12 changes: 0 additions & 12 deletions cli/src/main/kotlin/tools/samt/cli/CliFileResolution.kt

This file was deleted.

3 changes: 0 additions & 3 deletions cli/src/main/kotlin/tools/samt/cli/DiagnosticFormatter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ internal class DiagnosticFormatter(
companion object {
private const val CONTEXT_ROW_COUNT = 3

// FIXME: this is a bit of a hack to get the terminal width
// it also means we're assuming this output will only ever be printed in a terminal
// i don't actually know what happens if it doesn't run in a tty setting
fun format(controller: DiagnosticController, startTimestamp: Long, currentTimestamp: Long, terminalWidth: Int = Terminal().info.width): String {
val formatter = DiagnosticFormatter(controller, startTimestamp, currentTimestamp, terminalWidth)
return formatter.format()
Expand Down
39 changes: 39 additions & 0 deletions cli/src/main/kotlin/tools/samt/cli/OutputWriter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package tools.samt.cli

import tools.samt.api.plugin.CodegenFile
import java.io.IOException
import java.nio.file.InvalidPathException
import java.nio.file.Path
import kotlin.io.path.*

internal object OutputWriter {
@Throws(IOException::class)
fun write(outputDirectory: Path, files: List<CodegenFile>) {
if (!outputDirectory.exists()) {
try {
outputDirectory.createDirectories()
} catch (e: IOException) {
throw IOException("Failed to create output directory '${outputDirectory}'", e)
}
}
if (!outputDirectory.isDirectory()) {
throw IOException("Path '${outputDirectory}' does not point to a directory")
}
for (file in files) {
val outputFile = try {
outputDirectory.resolve(file.filepath)
} catch (e: InvalidPathException) {
throw IOException("Invalid path '${file.filepath}'", e)
}
try {
outputFile.parent.createDirectories()
if (outputFile.notExists()) {
outputFile.createFile()
}
outputFile.writeText(file.source)
} catch (e: IOException) {
throw IOException("Failed to write file '${outputFile.toUri()}'", e)
}
}
}
}
7 changes: 6 additions & 1 deletion cli/src/main/kotlin/tools/samt/cli/TypePrinter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import tools.samt.semantic.Package

internal object TypePrinter {
fun dump(samtPackage: Package): String = buildString {
appendLine(blue(samtPackage.name.ifEmpty { "<root>" }))
if (samtPackage.isRootPackage) {
appendLine(red("<root>"))
} else {
appendLine(blue(samtPackage.name))
}

for (enum in samtPackage.enums) {
appendLine(" ${bold("enum")} ${yellow(enum.name)}")
}
Expand Down
13 changes: 13 additions & 0 deletions codegen/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
plugins {
id("samt-core.kotlin-conventions")
alias(libs.plugins.kover)
}

dependencies {
implementation(project(":common"))
implementation(project(":parser"))
implementation(project(":semantic"))
implementation(project(":public-api"))
testImplementation(project(":lexer"))
testImplementation(project(":samt-config"))
}
61 changes: 61 additions & 0 deletions codegen/src/main/kotlin/tools/samt/codegen/Codegen.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package tools.samt.codegen

import tools.samt.api.plugin.CodegenFile
import tools.samt.api.plugin.Generator
import tools.samt.api.plugin.GeneratorParams
import tools.samt.api.plugin.TransportConfigurationParser
import tools.samt.api.types.SamtPackage
import tools.samt.codegen.http.HttpTransportConfigurationParser
import tools.samt.codegen.kotlin.KotlinTypesGenerator
import tools.samt.codegen.kotlin.ktor.KotlinKtorConsumerGenerator
import tools.samt.codegen.kotlin.ktor.KotlinKtorProviderGenerator
import tools.samt.common.DiagnosticController
import tools.samt.common.SamtGeneratorConfiguration
import tools.samt.semantic.SemanticModel

object Codegen {
private val generators: List<Generator> = listOf(
KotlinTypesGenerator,
KotlinKtorProviderGenerator,
KotlinKtorConsumerGenerator,
)

private val transports: List<TransportConfigurationParser> = listOf(
HttpTransportConfigurationParser,
)

internal class SamtGeneratorParams(
semanticModel: SemanticModel,
private val controller: DiagnosticController,
override val options: Map<String, String>,
) : GeneratorParams {
private val apiMapper = PublicApiMapper(transports, controller)
override val packages: List<SamtPackage> = semanticModel.global.allSubPackages.map { apiMapper.toPublicApi(it) }

override fun reportError(message: String) {
controller.reportGlobalError(message)
}

override fun reportWarning(message: String) {
controller.reportGlobalWarning(message)
}

override fun reportInfo(message: String) {
controller.reportGlobalInfo(message)
}
}

fun generate(
semanticModel: SemanticModel,
configuration: SamtGeneratorConfiguration,
controller: DiagnosticController,
): List<CodegenFile> {
val matchingGenerators = generators.filter { it.name == configuration.name }
when (matchingGenerators.size) {
0 -> controller.reportGlobalError("No matching generator found for '${configuration.name}'")
1 -> return matchingGenerators.single().generate(SamtGeneratorParams(semanticModel, controller, configuration.options))
else -> controller.reportGlobalError("Multiple matching generators found for '${configuration.name}'")
}
return emptyList()
}
}
Loading

0 comments on commit c1373e5

Please sign in to comment.