|
| 1 | +package com.compiler.server.compiler.components |
| 2 | + |
| 3 | +import com.compiler.server.compiler.components.IndexationProvider.Companion.UNRESOLVED_REFERENCE_PREFIX |
| 4 | +import com.compiler.server.model.CompilerDiagnostics |
| 5 | +import com.compiler.server.model.ErrorDescriptor |
| 6 | +import com.compiler.server.model.ProjectSeveriry |
| 7 | +import com.compiler.server.model.TextInterval |
| 8 | +import org.jetbrains.kotlin.cli.common.CLICompiler |
| 9 | +import org.jetbrains.kotlin.cli.common.CLITool |
| 10 | +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity |
| 11 | +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.* |
| 12 | +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation |
| 13 | +import org.jetbrains.kotlin.cli.common.messages.MessageRenderer |
| 14 | +import org.jetbrains.kotlin.psi.KtFile |
| 15 | +import java.nio.file.Path |
| 16 | +import java.nio.file.Paths |
| 17 | +import java.util.* |
| 18 | +import kotlin.io.path.* |
| 19 | + |
| 20 | +private fun minusOne(value: Int) = if (value > 0) value - 1 else value |
| 21 | + |
| 22 | +sealed class CompilationResult<out T> { |
| 23 | + abstract val compilerDiagnostics: CompilerDiagnostics |
| 24 | + |
| 25 | + fun <R> flatMap(action: (T) -> CompilationResult<R>): CompilationResult<R> = when (this) { |
| 26 | + is Compiled -> { |
| 27 | + val innerResult = action(result) |
| 28 | + val newDiagnostics = (compilerDiagnostics.map.keys + innerResult.compilerDiagnostics.map.keys).associateWith { |
| 29 | + val l1 = compilerDiagnostics.map[it] |
| 30 | + val l2 = innerResult.compilerDiagnostics.map[it] |
| 31 | + if (l1 != null && l2 != null) l1 + l2 else (l1 ?: l2)!! |
| 32 | + }.let(::CompilerDiagnostics) |
| 33 | + when (innerResult) { |
| 34 | + is Compiled -> innerResult.copy(compilerDiagnostics = newDiagnostics) |
| 35 | + is NotCompiled -> innerResult.copy(compilerDiagnostics = newDiagnostics) |
| 36 | + } |
| 37 | + } |
| 38 | + is NotCompiled -> this |
| 39 | + } |
| 40 | + fun <R> map(action: (T) -> R): CompilationResult<R> = when (this) { |
| 41 | + is Compiled -> Compiled(compilerDiagnostics, action(result)) |
| 42 | + is NotCompiled -> this |
| 43 | + } |
| 44 | +} |
| 45 | + |
| 46 | +data class Compiled<T>(override val compilerDiagnostics: CompilerDiagnostics, val result: T) : CompilationResult<T>() |
| 47 | + |
| 48 | +data class NotCompiled(override val compilerDiagnostics: CompilerDiagnostics) : CompilationResult<Nothing>() |
| 49 | + |
| 50 | +fun CLICompiler<*>.tryCompilation(inputDirectory: Path, inputFiles: List<Path>, arguments: List<String>): CompilationResult<Unit> = tryCompilation(inputDirectory, inputFiles, arguments) {} |
| 51 | + |
| 52 | +fun <T> CLICompiler<*>.tryCompilation(inputDirectory: Path, inputFiles: List<Path>, arguments: List<String>, onSuccess: () -> T): CompilationResult<T> { |
| 53 | + fun Path.outputFilePathString() = inputDirectory.relativize(this).pathString |
| 54 | + |
| 55 | + val diagnosticsMap = mutableMapOf<String, MutableList<ErrorDescriptor>>().apply { |
| 56 | + inputFiles.forEach { put(it.outputFilePathString(), mutableListOf()) } |
| 57 | + } |
| 58 | + val defaultFileName = inputFiles.singleOrNull()?.outputFilePathString() ?: "" |
| 59 | + val exitCode = CLITool.doMainNoExit(this, arguments.toTypedArray(), object : MessageRenderer { |
| 60 | + override fun renderPreamble(): String = "" |
| 61 | + |
| 62 | + override fun render( |
| 63 | + severity: CompilerMessageSeverity, |
| 64 | + message: String, |
| 65 | + location: CompilerMessageSourceLocation? |
| 66 | + ): String { |
| 67 | + val textInterval = location?.let { |
| 68 | + TextInterval( |
| 69 | + start = TextInterval.TextPosition(minusOne(location.line), minusOne(location.column)), |
| 70 | + end = TextInterval.TextPosition(minusOne(location.lineEnd), minusOne(location.columnEnd)) |
| 71 | + ) |
| 72 | + } |
| 73 | + val messageSeverity: ProjectSeveriry = when (severity) { |
| 74 | + EXCEPTION, ERROR -> ProjectSeveriry.ERROR |
| 75 | + STRONG_WARNING, WARNING -> ProjectSeveriry.WARNING |
| 76 | + INFO, LOGGING, OUTPUT -> return "" |
| 77 | + } |
| 78 | + val errorFilePath = location?.path?.let(::Path)?.outputFilePathString() ?: defaultFileName |
| 79 | + |
| 80 | + val className = if (!message.startsWith(UNRESOLVED_REFERENCE_PREFIX) && severity == ERROR) "red_wavy_line" else messageSeverity.name |
| 81 | + val errorDescriptor = ErrorDescriptor(textInterval, message, messageSeverity, className) |
| 82 | + |
| 83 | + diagnosticsMap.getOrPut(errorFilePath) { mutableListOf() }.add(errorDescriptor) |
| 84 | + return "" |
| 85 | + } |
| 86 | + |
| 87 | + override fun renderUsage(usage: String): String = |
| 88 | + render(STRONG_WARNING, usage, null) |
| 89 | + |
| 90 | + override fun renderConclusion(): String = "" |
| 91 | + |
| 92 | + override fun getName(): String = "Redirector" |
| 93 | + }) |
| 94 | + val diagnostics = CompilerDiagnostics(diagnosticsMap) |
| 95 | + return when { |
| 96 | + diagnostics.any { it.severity == ProjectSeveriry.ERROR } -> NotCompiled(diagnostics) |
| 97 | + exitCode.code != 0 -> ErrorDescriptor( |
| 98 | + severity = ProjectSeveriry.ERROR, |
| 99 | + message = "Compiler finished with non-null exit code ${exitCode.code}: ${exitCode.name}", |
| 100 | + interval = null |
| 101 | + ).let { NotCompiled(CompilerDiagnostics(mapOf(defaultFileName to listOf(it)))) } |
| 102 | + |
| 103 | + else -> Compiled(result = onSuccess(), compilerDiagnostics = diagnostics) |
| 104 | + } |
| 105 | +} |
| 106 | + |
| 107 | +@OptIn(ExperimentalPathApi::class) |
| 108 | +fun <T> usingTempDirectory(action: (path: Path) -> T): T { |
| 109 | + val path = getTempDirectory() |
| 110 | + path.createDirectories() |
| 111 | + return try { |
| 112 | + action(path) |
| 113 | + } finally { |
| 114 | + path.deleteRecursively() |
| 115 | + } |
| 116 | +} |
| 117 | + |
| 118 | +private fun getTempDirectory(): Path { |
| 119 | + val dir = System.getProperty("java.io.tmpdir") |
| 120 | + val sessionId = UUID.randomUUID().toString().replace("-", "") |
| 121 | + return Paths.get(dir, sessionId) |
| 122 | +} |
| 123 | + |
| 124 | +fun List<KtFile>.writeToIoFiles(inputDir: Path): List<Path> { |
| 125 | + val ioFiles = map { inputDir / it.name } |
| 126 | + for ((ioFile, ktFile) in ioFiles zip this) { |
| 127 | + ioFile.writeText(ktFile.text) |
| 128 | + } |
| 129 | + return ioFiles |
| 130 | +} |
| 131 | + |
| 132 | +val PATH_SEPARATOR: String = java.io.File.pathSeparator |
0 commit comments