Fix all small and medium-sized issues after review
This commit is contained in:
@@ -152,10 +152,7 @@ public class ScriptCodegen extends MemberCodegen<KtScript> {
|
|||||||
iv.invokespecial("java/lang/Object", "<init>", "()V", false);
|
iv.invokespecial("java/lang/Object", "<init>", "()V", false);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// TODO: check if it is correct way to find a primary constructor
|
ConstructorDescriptor ctorDesc = superclass.getUnsubstitutedPrimaryConstructor();
|
||||||
Collection<ClassConstructorDescriptor> ctorDescriptors = superclass.getConstructors();
|
|
||||||
assert ctorDescriptors.size() == 1;
|
|
||||||
ConstructorDescriptor ctorDesc = ctorDescriptors.iterator().next();
|
|
||||||
assert ctorDesc != null;
|
assert ctorDesc != null;
|
||||||
|
|
||||||
iv.load(0, classType);
|
iv.load(0, classType);
|
||||||
|
|||||||
+1
-1
@@ -38,6 +38,6 @@ data class CompilerMessageLocation private constructor(
|
|||||||
): CompilerMessageLocation =
|
): CompilerMessageLocation =
|
||||||
if (path == null) NO_LOCATION else CompilerMessageLocation(path, line, column, lineContent)
|
if (path == null) NO_LOCATION else CompilerMessageLocation(path, line, column, lineContent)
|
||||||
|
|
||||||
private val serialVersionUID: Long = 8228357578L // just a random number, should be changed when serialized data is changed
|
private val serialVersionUID: Long = 8228357578L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,13 +73,13 @@ fun tryConstructScriptClass(scriptClass: Class<*>, scriptArgs: List<String>): An
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return scriptClass.getConstructor(Array<String>::class.java).newInstance(*arrayOf<Any>(scriptArgs.toTypedArray()))
|
return scriptClass.getConstructor(Array<String>::class.java).newInstance(scriptArgs.toTypedArray())
|
||||||
}
|
}
|
||||||
catch (e: java.lang.NoSuchMethodException) {
|
catch (e: NoSuchMethodException) {
|
||||||
for (ctor in scriptClass.kotlin.constructors) {
|
for (ctor in scriptClass.kotlin.constructors) {
|
||||||
val (ctorArgs, scriptArgsLeft) = ctor.parameters.fold(Pair(emptyList<Any>(), scriptArgs), ::foldingFunc)
|
val (ctorArgs, scriptArgsLeft) = ctor.parameters.fold(Pair(emptyList<Any>(), scriptArgs), ::foldingFunc)
|
||||||
if (ctorArgs.size <= ctor.parameters.size && (scriptArgsLeft == null || scriptArgsLeft.isEmpty())) {
|
if (ctorArgs.size <= ctor.parameters.size && (scriptArgsLeft == null || scriptArgsLeft.isEmpty())) {
|
||||||
val argsMap = ctorArgs.zip(ctor.parameters) { a, p -> Pair(p, a) }.toMap()
|
val argsMap = ctor.parameters.zip(ctorArgs).toMap()
|
||||||
try {
|
try {
|
||||||
return ctor.callBy(argsMap)
|
return ctor.callBy(argsMap)
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-11
@@ -25,8 +25,7 @@ import kotlin.concurrent.write
|
|||||||
|
|
||||||
open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>, baseClassloader: ClassLoader?, val scriptArgs: Array<Any?>? = null, val scriptArgsTypes: Array<Class<*>>? = null) : ReplCompiledEvaluator {
|
open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>, baseClassloader: ClassLoader?, val scriptArgs: Array<Any?>? = null, val scriptArgsTypes: Array<Class<*>>? = null) : ReplCompiledEvaluator {
|
||||||
|
|
||||||
private var classLoader: org.jetbrains.kotlin.cli.common.repl.ReplClassLoader =
|
private var classLoader: org.jetbrains.kotlin.cli.common.repl.ReplClassLoader = makeReplClassLoader(baseClassloader, baseClasspath)
|
||||||
org.jetbrains.kotlin.cli.common.repl.ReplClassLoader(URLClassLoader(baseClasspath.map { it.toURI().toURL() }.toTypedArray(), baseClassloader))
|
|
||||||
private val classLoaderLock = ReentrantReadWriteLock()
|
private val classLoaderLock = ReentrantReadWriteLock()
|
||||||
|
|
||||||
private class ClassWithInstance(val klass: Class<*>, val instance: Any)
|
private class ClassWithInstance(val klass: Class<*>, val instance: Any)
|
||||||
@@ -42,7 +41,7 @@ open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>, baseClass
|
|||||||
classLoaderLock.read {
|
classLoaderLock.read {
|
||||||
if (newClasspath.isNotEmpty()) {
|
if (newClasspath.isNotEmpty()) {
|
||||||
classLoaderLock.write {
|
classLoaderLock.write {
|
||||||
classLoader = org.jetbrains.kotlin.cli.common.repl.ReplClassLoader(URLClassLoader(newClasspath.map { it.toURI().toURL() }.toTypedArray(), classLoader))
|
classLoader = makeReplClassLoader(classLoader, newClasspath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compiledClasses.filter { it.path.endsWith(".class") }
|
compiledClasses.filter { it.path.endsWith(".class") }
|
||||||
@@ -55,14 +54,9 @@ open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>, baseClass
|
|||||||
|
|
||||||
val constructorParams: Array<Class<*>> =
|
val constructorParams: Array<Class<*>> =
|
||||||
(compiledLoadedClassesHistory.map { it.second.klass } +
|
(compiledLoadedClassesHistory.map { it.second.klass } +
|
||||||
(scriptArgs?.asIterable()
|
(scriptArgs?.mapIndexed { i, it -> scriptArgsTypes?.getOrNull(i) ?: it?.javaClass ?: Any::class.java } ?: emptyList())
|
||||||
?.mapIndexed { i, it ->
|
|
||||||
if (i < (scriptArgsTypes?.size ?: 0)) scriptArgsTypes!![i] else it?.javaClass ?: Any::class.java
|
|
||||||
}
|
|
||||||
?: emptyList()
|
|
||||||
)
|
|
||||||
).toTypedArray()
|
).toTypedArray()
|
||||||
val constructorArgs: Array<Any?> = (compiledLoadedClassesHistory.map { it.second.instance } + (scriptArgs?.asIterable() ?: emptyList())).toTypedArray()
|
val constructorArgs: Array<Any?> = (compiledLoadedClassesHistory.map { it.second.instance } + scriptArgs.orEmpty()).toTypedArray()
|
||||||
|
|
||||||
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
|
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
|
||||||
val scriptInstance =
|
val scriptInstance =
|
||||||
@@ -85,4 +79,7 @@ open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>, baseClass
|
|||||||
companion object {
|
companion object {
|
||||||
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
|
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun makeReplClassLoader(baseClassloader: ClassLoader?, baseClasspath: Iterable<File>) =
|
||||||
|
ReplClassLoader(URLClassLoader(baseClasspath.map { it.toURI().toURL() }.toTypedArray(), baseClassloader))
|
||||||
@@ -23,7 +23,7 @@ import java.util.*
|
|||||||
|
|
||||||
data class ReplCodeLine(val no: Int, val code: String) : Serializable {
|
data class ReplCodeLine(val no: Int, val code: String) : Serializable {
|
||||||
companion object {
|
companion object {
|
||||||
private val serialVersionUID: Long = 8228357578L // just a random number, should be changed when serialized data is changed
|
private val serialVersionUID: Long = 8228357578L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ data class CompiledClassData(val path: String, val bytes: ByteArray) : Serializa
|
|||||||
override fun equals(other: Any?): Boolean = (other as? CompiledClassData)?.let { path == it.path && Arrays.equals(bytes, it.bytes) } ?: false
|
override fun equals(other: Any?): Boolean = (other as? CompiledClassData)?.let { path == it.path && Arrays.equals(bytes, it.bytes) } ?: false
|
||||||
override fun hashCode(): Int = path.hashCode() + Arrays.hashCode(bytes)
|
override fun hashCode(): Int = path.hashCode() + Arrays.hashCode(bytes)
|
||||||
companion object {
|
companion object {
|
||||||
private val serialVersionUID: Long = 8228357578L // just a random number, should be changed when serialized data is changed
|
private val serialVersionUID: Long = 8228357578L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ sealed class ReplCheckResult : Serializable {
|
|||||||
override fun toString(): String = "Error(message = \"$message\""
|
override fun toString(): String = "Error(message = \"$message\""
|
||||||
}
|
}
|
||||||
companion object {
|
companion object {
|
||||||
private val serialVersionUID: Long = 8228357578L // just a random number, should be changed when serialized data is changed
|
private val serialVersionUID: Long = 8228357578L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ sealed class ReplCompileResult : Serializable {
|
|||||||
override fun toString(): String = "Error(message = \"$message\""
|
override fun toString(): String = "Error(message = \"$message\""
|
||||||
}
|
}
|
||||||
companion object {
|
companion object {
|
||||||
private val serialVersionUID: Long = 8228357578L // just a random number, should be changed when serialized data is changed
|
private val serialVersionUID: Long = 8228357578L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,10 +68,10 @@ sealed class ReplEvalResult : Serializable {
|
|||||||
sealed class Error(val message: String) : ReplEvalResult() {
|
sealed class Error(val message: String) : ReplEvalResult() {
|
||||||
class Runtime(message: String) : Error(message)
|
class Runtime(message: String) : Error(message)
|
||||||
class CompileTime(message: String, val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : Error(message)
|
class CompileTime(message: String, val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : Error(message)
|
||||||
override fun toString(): String = "${this.javaClass.kotlin.simpleName}Error(message = \"$message\""
|
override fun toString(): String = "${this::class.simpleName}Error(message = \"$message\""
|
||||||
}
|
}
|
||||||
companion object {
|
companion object {
|
||||||
private val serialVersionUID: Long = 8228357578L // just a random number, should be changed when serialized data is changed
|
private val serialVersionUID: Long = 8228357578L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import org.jetbrains.kotlin.config.CompilerConfiguration
|
|||||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||||
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
import org.jetbrains.kotlin.name.Name
|
|
||||||
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
|
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||||
@@ -74,28 +73,27 @@ open class GenericReplChecker(
|
|||||||
|
|
||||||
fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder()
|
fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder()
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
override fun check(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCheckResult {
|
override fun check(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCheckResult {
|
||||||
synchronized(this) {
|
val virtualFile =
|
||||||
val virtualFile =
|
LightVirtualFile("line${codeLine.no}${KotlinParserDefinition.STD_SCRIPT_EXT}", KotlinLanguage.INSTANCE, codeLine.code).apply {
|
||||||
LightVirtualFile("line${codeLine.no}${KotlinParserDefinition.STD_SCRIPT_EXT}", KotlinLanguage.INSTANCE, codeLine.code).apply {
|
charset = CharsetToolkit.UTF8_CHARSET
|
||||||
charset = CharsetToolkit.UTF8_CHARSET
|
}
|
||||||
}
|
val psiFile: KtFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
|
||||||
val psiFile: KtFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
|
?: error("Script file not analyzed at line ${codeLine.no}: ${codeLine.code}")
|
||||||
?: error("Script file not analyzed at line ${codeLine.no}: ${codeLine.code}")
|
|
||||||
|
|
||||||
val errorHolder = createDiagnosticHolder()
|
val errorHolder = createDiagnosticHolder()
|
||||||
|
|
||||||
val syntaxErrorReport = AnalyzerWithCompilerReport.Companion.reportSyntaxErrors(psiFile, errorHolder)
|
val syntaxErrorReport = AnalyzerWithCompilerReport.Companion.reportSyntaxErrors(psiFile, errorHolder)
|
||||||
|
|
||||||
if (!syntaxErrorReport.isHasErrors) {
|
if (!syntaxErrorReport.isHasErrors) {
|
||||||
lineState = LineState(codeLine, psiFile, errorHolder)
|
lineState = LineState(codeLine, psiFile, errorHolder)
|
||||||
}
|
}
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof -> ReplCheckResult.Incomplete
|
syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof -> ReplCheckResult.Incomplete
|
||||||
syntaxErrorReport.isHasErrors -> ReplCheckResult.Error(errorHolder.renderedDiagnostics)
|
syntaxErrorReport.isHasErrors -> ReplCheckResult.Error(errorHolder.renderedDiagnostics)
|
||||||
else -> ReplCheckResult.Ok
|
else -> ReplCheckResult.Ok
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,61 +111,59 @@ open class GenericReplCompiler(
|
|||||||
|
|
||||||
private val descriptorsHistory = arrayListOf<Pair<ReplCodeLine, ScriptDescriptor>>()
|
private val descriptorsHistory = arrayListOf<Pair<ReplCodeLine, ScriptDescriptor>>()
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
override fun compile(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCompileResult {
|
override fun compile(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCompileResult {
|
||||||
synchronized(this) {
|
checkAndUpdateReplHistoryCollection(descriptorsHistory, history)?.let {
|
||||||
|
return@compile ReplCompileResult.HistoryMismatch(it)
|
||||||
checkAndUpdateReplHistoryCollection(descriptorsHistory, history)?.let {
|
|
||||||
return@compile ReplCompileResult.HistoryMismatch(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
val (psiFile, errorHolder) = run {
|
|
||||||
if (lineState == null || lineState!!.codeLine != codeLine) {
|
|
||||||
val res = check(codeLine, history)
|
|
||||||
when (res) {
|
|
||||||
ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete
|
|
||||||
is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(res.message, res.location)
|
|
||||||
ReplCheckResult.Ok -> {} // continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Pair(lineState!!.psiFile, lineState!!.errorHolder)
|
|
||||||
}
|
|
||||||
|
|
||||||
val newDependencies = scriptDefinition.getDependenciesFor(psiFile, environment.project, lastDependencies)
|
|
||||||
if (lastDependencies != newDependencies) {
|
|
||||||
lastDependencies = newDependencies
|
|
||||||
}
|
|
||||||
|
|
||||||
val analysisResult = analyzerEngine.analyzeReplLine(psiFile, codeLine.no)
|
|
||||||
AnalyzerWithCompilerReport.Companion.reportDiagnostics(analysisResult.diagnostics, errorHolder, false)
|
|
||||||
val scriptDescriptor = when (analysisResult) {
|
|
||||||
is CliReplAnalyzerEngine.ReplLineAnalysisResult.WithErrors -> return ReplCompileResult.Error(errorHolder.renderedDiagnostics)
|
|
||||||
is CliReplAnalyzerEngine.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
|
|
||||||
else -> error("Unexpected result ${analysisResult.javaClass}")
|
|
||||||
}
|
|
||||||
|
|
||||||
val state = GenerationState(
|
|
||||||
psiFile.project,
|
|
||||||
ClassBuilderFactories.binaries(false),
|
|
||||||
analyzerEngine.module,
|
|
||||||
analyzerEngine.trace.bindingContext,
|
|
||||||
listOf(psiFile),
|
|
||||||
compilerConfiguration
|
|
||||||
)
|
|
||||||
state.replSpecific.scriptResultFieldName = SCRIPT_RESULT_FIELD_NAME
|
|
||||||
state.replSpecific.earlierScriptsForReplInterpreter = descriptorsHistory.map { it.second }
|
|
||||||
state.beforeCompile()
|
|
||||||
KotlinCodegenFacade.generatePackage(
|
|
||||||
state,
|
|
||||||
psiFile.script!!.getContainingKtFile().packageFqName,
|
|
||||||
setOf(psiFile.script!!.getContainingKtFile()),
|
|
||||||
org.jetbrains.kotlin.codegen.CompilationErrorHandler.THROW_EXCEPTION)
|
|
||||||
|
|
||||||
descriptorsHistory.add(codeLine to scriptDescriptor)
|
|
||||||
|
|
||||||
return ReplCompileResult.CompiledClasses(state.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) },
|
|
||||||
state.replSpecific.hasResult,
|
|
||||||
newDependencies?.let { environment.updateClasspath(it.classpath.map(::JvmClasspathRoot)) } ?: emptyList())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val (psiFile, errorHolder) = run {
|
||||||
|
if (lineState == null || lineState!!.codeLine != codeLine) {
|
||||||
|
val res = check(codeLine, history)
|
||||||
|
when (res) {
|
||||||
|
ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete
|
||||||
|
is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(res.message, res.location)
|
||||||
|
ReplCheckResult.Ok -> {} // continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Pair(lineState!!.psiFile, lineState!!.errorHolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
val newDependencies = scriptDefinition.getDependenciesFor(psiFile, environment.project, lastDependencies)
|
||||||
|
if (lastDependencies != newDependencies) {
|
||||||
|
lastDependencies = newDependencies
|
||||||
|
}
|
||||||
|
|
||||||
|
val analysisResult = analyzerEngine.analyzeReplLine(psiFile, codeLine.no)
|
||||||
|
AnalyzerWithCompilerReport.Companion.reportDiagnostics(analysisResult.diagnostics, errorHolder, false)
|
||||||
|
val scriptDescriptor = when (analysisResult) {
|
||||||
|
is CliReplAnalyzerEngine.ReplLineAnalysisResult.WithErrors -> return ReplCompileResult.Error(errorHolder.renderedDiagnostics)
|
||||||
|
is CliReplAnalyzerEngine.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
|
||||||
|
else -> error("Unexpected result ${analysisResult.javaClass}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val state = GenerationState(
|
||||||
|
psiFile.project,
|
||||||
|
ClassBuilderFactories.binaries(false),
|
||||||
|
analyzerEngine.module,
|
||||||
|
analyzerEngine.trace.bindingContext,
|
||||||
|
listOf(psiFile),
|
||||||
|
compilerConfiguration
|
||||||
|
)
|
||||||
|
state.replSpecific.scriptResultFieldName = SCRIPT_RESULT_FIELD_NAME
|
||||||
|
state.replSpecific.earlierScriptsForReplInterpreter = descriptorsHistory.map { it.second }
|
||||||
|
state.beforeCompile()
|
||||||
|
KotlinCodegenFacade.generatePackage(
|
||||||
|
state,
|
||||||
|
psiFile.script!!.getContainingKtFile().packageFqName,
|
||||||
|
setOf(psiFile.script!!.getContainingKtFile()),
|
||||||
|
org.jetbrains.kotlin.codegen.CompilationErrorHandler.THROW_EXCEPTION)
|
||||||
|
|
||||||
|
descriptorsHistory.add(codeLine to scriptDescriptor)
|
||||||
|
|
||||||
|
return ReplCompileResult.CompiledClasses(state.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) },
|
||||||
|
state.replSpecific.hasResult,
|
||||||
|
newDependencies?.let { environment.updateClasspath(it.classpath.map(::JvmClasspathRoot)) } ?: emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -188,9 +184,9 @@ open class GenericRepl(
|
|||||||
|
|
||||||
private val compiledEvaluator = GenericReplCompiledEvaluator(compilerConfiguration.jvmClasspathRoots, baseClassloader, scriptArgs, scriptArgsTypes)
|
private val compiledEvaluator = GenericReplCompiledEvaluator(compilerConfiguration.jvmClasspathRoots, baseClassloader, scriptArgs, scriptArgsTypes)
|
||||||
|
|
||||||
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult = synchronized(this) {
|
@Synchronized
|
||||||
return compileAndEval(this, compiledEvaluator, codeLine, history)
|
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult =
|
||||||
}
|
compileAndEval(this, compiledEvaluator, codeLine, history)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -390,9 +390,10 @@ class CompileServiceImpl(
|
|||||||
|
|
||||||
synchronized(state.sessions) {
|
synchronized(state.sessions) {
|
||||||
// 2. check if any session hanged - clean
|
// 2. check if any session hanged - clean
|
||||||
state.sessions.filterValues { !it.isAlive }.apply { anyDead = true }.forEach {
|
state.sessions.filterValues { !it.isAlive }.forEach {
|
||||||
it.value.dispose()
|
it.value.dispose()
|
||||||
state.sessions.remove(it.key)
|
state.sessions.remove(it.key)
|
||||||
|
anyDead = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,13 +404,14 @@ class CompileServiceImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4. clean dead clients, then check if any left - conditional shutdown (with small delay)
|
// 4. clean dead clients, then check if any left - conditional shutdown (with small delay)
|
||||||
synchronized(state.clientProxies) {
|
synchronized(state.clientProxies) {
|
||||||
state.clientProxies.removeAll(
|
state.clientProxies.removeAll(
|
||||||
state.clientProxies.filter { !it.isAlive }.apply { anyDead = true }.map {
|
state.clientProxies.filter { !it.isAlive }.map {
|
||||||
it.dispose()
|
it.dispose()
|
||||||
it
|
anyDead = true
|
||||||
})
|
it
|
||||||
}
|
})
|
||||||
|
}
|
||||||
if (state.clientProxies.isEmpty() && compilationsCounter.get() > 0 && !state.delayedShutdownQueued.get()) {
|
if (state.clientProxies.isEmpty() && compilationsCounter.get() > 0 && !state.delayedShutdownQueued.get()) {
|
||||||
log.info("No more clients left, delayed shutdown in ${daemonOptions.shutdownDelayMilliseconds}ms")
|
log.info("No more clients left, delayed shutdown in ${daemonOptions.shutdownDelayMilliseconds}ms")
|
||||||
shutdownWithDelay()
|
shutdownWithDelay()
|
||||||
|
|||||||
+2
-2
@@ -52,11 +52,11 @@ open class KotlinScriptDefinitionFromAnnotatedTemplate(
|
|||||||
defAnn != null ->
|
defAnn != null ->
|
||||||
try {
|
try {
|
||||||
defAnn.resolver.primaryConstructor?.call() ?: null.apply {
|
defAnn.resolver.primaryConstructor?.call() ?: null.apply {
|
||||||
log.error("[kts] No default constructor found for ${defAnn.resolver.qualifiedName}")
|
log.warn("[kts] No default constructor found for ${defAnn.resolver.qualifiedName}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ex: ClassCastException) {
|
catch (ex: ClassCastException) {
|
||||||
log.error("[kts] Script def error ${ex.message}")
|
log.warn("[kts] Script def error ${ex.message}")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
else -> BasicScriptDependenciesResolver()
|
else -> BasicScriptDependenciesResolver()
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ fun KotlinScriptDefinition.getScriptParameters(scriptDescriptor: ScriptDescripto
|
|||||||
|
|
||||||
fun getKotlinType(scriptDescriptor: ScriptDescriptor, kClass: KClass<out Any>): KotlinType =
|
fun getKotlinType(scriptDescriptor: ScriptDescriptor, kClass: KClass<out Any>): KotlinType =
|
||||||
getKotlinTypeByFqName(scriptDescriptor,
|
getKotlinTypeByFqName(scriptDescriptor,
|
||||||
kClass.qualifiedName ?: throw java.lang.RuntimeException("Cannot get FQN from $kClass"))
|
kClass.qualifiedName ?: throw RuntimeException("Cannot get FQN from $kClass"))
|
||||||
|
|
||||||
fun getKotlinTypeByFqName(scriptDescriptor: ScriptDescriptor, fqName: String): KotlinType =
|
fun getKotlinTypeByFqName(scriptDescriptor: ScriptDescriptor, fqName: String): KotlinType =
|
||||||
scriptDescriptor.module.findNonGenericClassAcrossDependencies(
|
scriptDescriptor.module.findNonGenericClassAcrossDependencies(
|
||||||
|
|||||||
@@ -22,14 +22,14 @@ import junit.framework.TestCase
|
|||||||
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
|
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
|
||||||
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
|
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
|
||||||
import org.jetbrains.kotlin.cli.common.repl.*
|
import org.jetbrains.kotlin.cli.common.repl.*
|
||||||
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
|
|
||||||
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
|
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
|
||||||
import org.jetbrains.kotlin.config.CommonConfigurationKeys
|
import org.jetbrains.kotlin.config.CommonConfigurationKeys
|
||||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
|
||||||
import org.jetbrains.kotlin.integration.KotlinIntegrationTestBase
|
import org.jetbrains.kotlin.integration.KotlinIntegrationTestBase
|
||||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||||
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate
|
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate
|
||||||
import org.jetbrains.kotlin.utils.PathUtil
|
import org.jetbrains.kotlin.test.ConfigurationKind
|
||||||
|
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||||
|
import org.jetbrains.kotlin.test.TestJdkKind
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
@@ -48,7 +48,7 @@ class GenericReplTest : TestCase() {
|
|||||||
val res1 = repl.replCompiler?.check(ReplCodeLine(0, "val x ="), emptyList())
|
val res1 = repl.replCompiler?.check(ReplCodeLine(0, "val x ="), emptyList())
|
||||||
TestCase.assertTrue("Unexpected check results: $res1", res1 is ReplCheckResult.Incomplete)
|
TestCase.assertTrue("Unexpected check results: $res1", res1 is ReplCheckResult.Incomplete)
|
||||||
|
|
||||||
val codeLine0 = ReplCodeLine(0, "1 + 2")
|
val codeLine0 = ReplCodeLine(0, "val l1 = listOf(1 + 2)\nl1.first()")
|
||||||
val res2 = repl.replCompiler?.compile(codeLine0, emptyList())
|
val res2 = repl.replCompiler?.compile(codeLine0, emptyList())
|
||||||
val res2c = res2 as? ReplCompileResult.CompiledClasses
|
val res2c = res2 as? ReplCompileResult.CompiledClasses
|
||||||
TestCase.assertNotNull("Unexpected compile result: $res2", res2c)
|
TestCase.assertNotNull("Unexpected compile result: $res2", res2c)
|
||||||
@@ -89,12 +89,10 @@ internal class TestRepl(
|
|||||||
templateClasspath: List<File>,
|
templateClasspath: List<File>,
|
||||||
templateClassName: String
|
templateClassName: String
|
||||||
) {
|
) {
|
||||||
private val configuration = CompilerConfiguration().apply {
|
private val configuration = KotlinTestUtils.newConfiguration(ConfigurationKind.ALL, TestJdkKind.MOCK_JDK, *templateClasspath.toTypedArray()).apply {
|
||||||
addJvmClasspathRoots(PathUtil.getJdkClassesRoots())
|
|
||||||
addJvmClasspathRoots(PathUtil.getKotlinPathsForCompiler().let { listOf(it.runtimePath, it.reflectPath) })
|
|
||||||
addJvmClasspathRoots(templateClasspath)
|
|
||||||
put(CommonConfigurationKeys.MODULE_NAME, "kotlin-script")
|
put(CommonConfigurationKeys.MODULE_NAME, "kotlin-script")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeScriptDefinition(templateClasspath: List<File>, templateClassName: String): KotlinScriptDefinition {
|
private fun makeScriptDefinition(templateClasspath: List<File>, templateClassName: String): KotlinScriptDefinition {
|
||||||
val classloader = URLClassLoader(templateClasspath.map { it.toURI().toURL() }.toTypedArray(), this.javaClass.classLoader)
|
val classloader = URLClassLoader(templateClasspath.map { it.toURI().toURL() }.toTypedArray(), this.javaClass.classLoader)
|
||||||
val cls = classloader.loadClass(templateClassName)
|
val cls = classloader.loadClass(templateClassName)
|
||||||
|
|||||||
@@ -510,7 +510,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
|
|||||||
val res0 = repl.check(ReplCodeLine(0, "val x ="), emptyList())
|
val res0 = repl.check(ReplCodeLine(0, "val x ="), emptyList())
|
||||||
TestCase.assertTrue("Unexpected check results: $res0", res0 is ReplCheckResult.Incomplete)
|
TestCase.assertTrue("Unexpected check results: $res0", res0 is ReplCheckResult.Incomplete)
|
||||||
|
|
||||||
val codeLine1 = ReplCodeLine(1, "val x = 5")
|
val codeLine1 = ReplCodeLine(1, "val lst = listOf(1)\nval x = 5")
|
||||||
val res1 = repl.compile(codeLine1, emptyList())
|
val res1 = repl.compile(codeLine1, emptyList())
|
||||||
val res1c = res1 as? ReplCompileResult.CompiledClasses
|
val res1c = res1 as? ReplCompileResult.CompiledClasses
|
||||||
TestCase.assertNotNull("Unexpected compile result: $res1", res1c)
|
TestCase.assertNotNull("Unexpected compile result: $res1", res1c)
|
||||||
|
|||||||
@@ -181,29 +181,45 @@ class ScriptTemplateTest {
|
|||||||
fun testScriptWithStandardTemplate() {
|
fun testScriptWithStandardTemplate() {
|
||||||
val aClass = compileScript("fib_std.kts", StandardScriptTemplate::class, runIsolated = false)
|
val aClass = compileScript("fib_std.kts", StandardScriptTemplate::class, runIsolated = false)
|
||||||
Assert.assertNotNull(aClass)
|
Assert.assertNotNull(aClass)
|
||||||
aClass!!.getConstructor(Array<String>::class.java).newInstance(arrayOf("4", "other"))
|
captureOut {
|
||||||
|
aClass!!.getConstructor(Array<String>::class.java).newInstance(arrayOf("4", "other"))
|
||||||
|
}.let {
|
||||||
|
assertEqualsTrimmed(NUM_4_LINE + " (other)" + FIB_SCRIPT_OUTPUT_TAIL, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testScriptWithPackage() {
|
fun testScriptWithPackage() {
|
||||||
val aClass = compileScript("fib.pkg.kts", ScriptWithIntParam::class)
|
val aClass = compileScript("fib.pkg.kts", ScriptWithIntParam::class)
|
||||||
Assert.assertNotNull(aClass)
|
Assert.assertNotNull(aClass)
|
||||||
aClass!!.getConstructor(Integer.TYPE).newInstance(4)
|
captureOut {
|
||||||
|
aClass!!.getConstructor(Integer.TYPE).newInstance(4)
|
||||||
|
}.let {
|
||||||
|
assertEqualsTrimmed(NUM_4_LINE + FIB_SCRIPT_OUTPUT_TAIL, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testScriptWithScriptDefinition() {
|
fun testScriptWithScriptDefinition() {
|
||||||
val aClass = compileScript("fib.kts", ScriptWithIntParam::class)
|
val aClass = compileScript("fib.kts", ScriptWithIntParam::class)
|
||||||
Assert.assertNotNull(aClass)
|
Assert.assertNotNull(aClass)
|
||||||
aClass!!.getConstructor(Integer.TYPE).newInstance(4)
|
captureOut {
|
||||||
|
aClass!!.getConstructor(Integer.TYPE).newInstance(4)
|
||||||
|
}.let {
|
||||||
|
assertEqualsTrimmed(NUM_4_LINE + FIB_SCRIPT_OUTPUT_TAIL, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testScriptWithParamConversion() {
|
fun testScriptWithParamConversion() {
|
||||||
val aClass = compileScript("fib.kts", ScriptWithIntParam::class)
|
val aClass = compileScript("fib.kts", ScriptWithIntParam::class)
|
||||||
Assert.assertNotNull(aClass)
|
Assert.assertNotNull(aClass)
|
||||||
val anObj = tryConstructScriptClass(aClass!!, listOf("4"))
|
captureOut {
|
||||||
Assert.assertNotNull(anObj)
|
val anObj = tryConstructScriptClass(aClass!!, listOf("4"))
|
||||||
|
Assert.assertNotNull(anObj)
|
||||||
|
}.let {
|
||||||
|
assertEqualsTrimmed(NUM_4_LINE + FIB_SCRIPT_OUTPUT_TAIL, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -275,8 +291,6 @@ class ScriptTemplateTest {
|
|||||||
|
|
||||||
open class TestKotlinScriptDummyDependenciesResolver : ScriptDependenciesResolver {
|
open class TestKotlinScriptDummyDependenciesResolver : ScriptDependenciesResolver {
|
||||||
|
|
||||||
private val kotlinPaths by lazy { PathUtil.getKotlinPathsForCompiler() }
|
|
||||||
|
|
||||||
@AcceptedAnnotations(DependsOn::class, DependsOnTwo::class)
|
@AcceptedAnnotations(DependsOn::class, DependsOnTwo::class)
|
||||||
override fun resolve(script: ScriptContents,
|
override fun resolve(script: ScriptContents,
|
||||||
environment: Map<String, Any?>?,
|
environment: Map<String, Any?>?,
|
||||||
|
|||||||
@@ -39,9 +39,13 @@ internal fun captureOut(body: () -> Unit): String {
|
|||||||
val outStream = ByteArrayOutputStream()
|
val outStream = ByteArrayOutputStream()
|
||||||
val prevOut = System.out
|
val prevOut = System.out
|
||||||
System.setOut(PrintStream(outStream))
|
System.setOut(PrintStream(outStream))
|
||||||
body()
|
try {
|
||||||
System.out.flush()
|
body()
|
||||||
System.setOut(prevOut)
|
}
|
||||||
|
finally {
|
||||||
|
System.out.flush()
|
||||||
|
System.setOut(prevOut)
|
||||||
|
}
|
||||||
return outStream.toString()
|
return outStream.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,8 +69,9 @@ class KotlinJsr223JvmScriptEngine4Idea(
|
|||||||
|
|
||||||
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult {
|
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult {
|
||||||
|
|
||||||
fun ReplCompileResult.Error.locationString() = if (location == CompilerMessageLocation.NO_LOCATION) ""
|
fun ReplCompileResult.Error.locationString() =
|
||||||
else " at ${location.line}:${location.column}:"
|
if (location == CompilerMessageLocation.NO_LOCATION) ""
|
||||||
|
else " at ${location.line}:${location.column}"
|
||||||
|
|
||||||
val compileResult = replCompiler.compile(codeLine, history)
|
val compileResult = replCompiler.compile(codeLine, history)
|
||||||
val compiled = when (compileResult) {
|
val compiled = when (compileResult) {
|
||||||
|
|||||||
+1
-1
@@ -36,6 +36,6 @@ class KotlinJsr223StandardScriptEngineFactory4Idea : KotlinJsr223JvmScriptEngine
|
|||||||
arrayOf(
|
arrayOf(
|
||||||
(bindings[ScriptEngine.ARGV] as? Array<*>) ?: emptyArray<String>(),
|
(bindings[ScriptEngine.ARGV] as? Array<*>) ?: emptyArray<String>(),
|
||||||
bindings) },
|
bindings) },
|
||||||
arrayOf(Array<String>::class.java, java.util.Map::class.java)
|
arrayOf(Array<String>::class.java, Map::class.java)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2016 JetBrains s.r.o.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.jsr223
|
|
||||||
|
|
||||||
import com.intellij.openapi.Disposable
|
|
||||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
|
|
||||||
import org.jetbrains.kotlin.cli.common.repl.GenericReplCompiledEvaluator
|
|
||||||
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
|
|
||||||
import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult
|
|
||||||
import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult
|
|
||||||
import org.jetbrains.kotlin.daemon.client.DaemonReportMessage
|
|
||||||
import org.jetbrains.kotlin.daemon.client.DaemonReportingTargets
|
|
||||||
import org.jetbrains.kotlin.daemon.client.KotlinCompilerClient
|
|
||||||
import org.jetbrains.kotlin.daemon.client.KotlinRemoteReplCompiler
|
|
||||||
import org.jetbrains.kotlin.daemon.common.*
|
|
||||||
import org.jetbrains.kotlin.utils.PathUtil
|
|
||||||
import java.io.File
|
|
||||||
import java.io.Reader
|
|
||||||
import javax.script.*
|
|
||||||
|
|
||||||
class KotlinJvmJsr223ScriptEngine4Idea(
|
|
||||||
disposable: Disposable,
|
|
||||||
private val factory: ScriptEngineFactory,
|
|
||||||
templateClasspath: List<File>,
|
|
||||||
templateClassName: String,
|
|
||||||
getScriptArgs: (ScriptContext) -> Array<Any?>?,
|
|
||||||
scriptArgsTypes: Array<Class<*>>?
|
|
||||||
) : AbstractScriptEngine(), ScriptEngine {
|
|
||||||
|
|
||||||
private val daemon by lazy {
|
|
||||||
val path = PathUtil.getKotlinPathsForIdeaPlugin().compilerPath
|
|
||||||
assert(path.exists())
|
|
||||||
val compilerId = CompilerId.makeCompilerId(path)
|
|
||||||
val daemonOptions = configureDaemonOptions()
|
|
||||||
val daemonJVMOptions = DaemonJVMOptions()
|
|
||||||
|
|
||||||
val daemonReportMessages = arrayListOf<DaemonReportMessage>()
|
|
||||||
|
|
||||||
KotlinCompilerClient.connectToCompileService(compilerId, daemonJVMOptions, daemonOptions, DaemonReportingTargets(null, daemonReportMessages), true, true)
|
|
||||||
?: throw ScriptException("Unable to connect to repl server:" + daemonReportMessages.joinToString("\n ", prefix = "\n ") { "${it.category.name} ${it.message}" })
|
|
||||||
}
|
|
||||||
|
|
||||||
private val replCompiler by lazy {
|
|
||||||
daemon.let {
|
|
||||||
KotlinRemoteReplCompiler(disposable,
|
|
||||||
it,
|
|
||||||
makeAutodeletingFlagFile("idea-jsr223-repl-session"),
|
|
||||||
CompileService.TargetPlatform.JVM,
|
|
||||||
templateClasspath,
|
|
||||||
templateClassName,
|
|
||||||
System.out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: bindings passing works only once on the first eval, subsequent setContext/setBindings call have no effect. Consider making it dynamic, but take history into account
|
|
||||||
val localEvaluator by lazy { GenericReplCompiledEvaluator(templateClasspath, Thread.currentThread().contextClassLoader, getScriptArgs(getContext()), scriptArgsTypes) }
|
|
||||||
|
|
||||||
private var lineCount = 0
|
|
||||||
|
|
||||||
private val history = arrayListOf<ReplCodeLine>()
|
|
||||||
|
|
||||||
override fun eval(script: String, context: ScriptContext?): Any? {
|
|
||||||
|
|
||||||
fun ReplCompileResult.Error.locationString() = if (location == CompilerMessageLocation.NO_LOCATION) ""
|
|
||||||
else " at ${location.line}:${location.column}:"
|
|
||||||
|
|
||||||
lineCount += 1
|
|
||||||
// TODO bind to context
|
|
||||||
val codeLine = ReplCodeLine(lineCount, script)
|
|
||||||
val compileResult = replCompiler?.compile(codeLine, history)
|
|
||||||
val compiled = when (compileResult) {
|
|
||||||
is ReplCompileResult.Error -> throw ScriptException("Error${compileResult.locationString()}: ${compileResult.message}")
|
|
||||||
is ReplCompileResult.Incomplete -> throw ScriptException("error: incomplete code")
|
|
||||||
is ReplCompileResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${compileResult.lineNo}")
|
|
||||||
is ReplCompileResult.CompiledClasses -> compileResult
|
|
||||||
}
|
|
||||||
|
|
||||||
val evalResult = localEvaluator.eval(codeLine, history, compiled.classes, compiled.hasResult, compiled.newClasspath)
|
|
||||||
val ret = when (evalResult) {
|
|
||||||
is ReplEvalResult.ValueResult -> evalResult.value
|
|
||||||
is ReplEvalResult.UnitResult -> null
|
|
||||||
is ReplEvalResult.Error -> throw ScriptException(evalResult.message)
|
|
||||||
is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code")
|
|
||||||
is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${evalResult.lineNo}")
|
|
||||||
}
|
|
||||||
history.add(codeLine)
|
|
||||||
// TODO update context
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun eval(script: Reader, context: ScriptContext?): Any? = eval(script.readText(), context)
|
|
||||||
|
|
||||||
override fun createBindings(): Bindings = SimpleBindings()
|
|
||||||
|
|
||||||
override fun getFactory(): ScriptEngineFactory = factory
|
|
||||||
}
|
|
||||||
@@ -37,10 +37,10 @@ class IdeaJsr223Test : PlatformTestCase() {
|
|||||||
val res0 = assertFails { engine.eval("val x =") }
|
val res0 = assertFails { engine.eval("val x =") }
|
||||||
assertTrue("Unexpected check results: $res0", (res0 as? ScriptException)?.message?.contains("incomplete code") ?: false)
|
assertTrue("Unexpected check results: $res0", (res0 as? ScriptException)?.message?.contains("incomplete code") ?: false)
|
||||||
|
|
||||||
val res1 = engine.eval("val x = 5")
|
val res1 = engine.eval("val x = 5\nval y = listOf(x)")
|
||||||
assertNull("Unexpected eval result: $res1", res1)
|
assertNull("Unexpected eval result: $res1", res1)
|
||||||
|
|
||||||
val res2 = engine.eval("x + 2")
|
val res2 = engine.eval("y.first() + 2")
|
||||||
assertEquals(7, res2)
|
assertEquals(7, res2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,8 +86,8 @@
|
|||||||
<version>2.6</version>
|
<version>2.6</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<systemPropertyVariables>
|
<systemPropertyVariables>
|
||||||
<KOTLIN_COMPILER_JAR>${org.jetbrains.kotlin:kotlin-compiler:jar}</KOTLIN_COMPILER_JAR>
|
<kotlin.compiler.jar>${org.jetbrains.kotlin:kotlin-compiler:jar}</kotlin.compiler.jar>
|
||||||
<KOTLIN_JAVA_RUNTIME_JAR>${org.jetbrains.kotlin:kotlin-runtime:jar}</KOTLIN_JAVA_RUNTIME_JAR>
|
<kotlin.java.runtime.jar>${org.jetbrains.kotlin:kotlin-runtime:jar}</kotlin.java.runtime.jar>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
|
|||||||
@@ -86,8 +86,8 @@
|
|||||||
<version>2.6</version>
|
<version>2.6</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<systemPropertyVariables>
|
<systemPropertyVariables>
|
||||||
<KOTLIN_COMPILER_JAR>${org.jetbrains.kotlin:kotlin-compiler:jar}</KOTLIN_COMPILER_JAR>
|
<kotlin.compiler.jar>${org.jetbrains.kotlin:kotlin-compiler:jar}</kotlin.compiler.jar>
|
||||||
<KOTLIN_JAVA_RUNTIME_JAR>${org.jetbrains.kotlin:kotlin-runtime:jar}</KOTLIN_JAVA_RUNTIME_JAR>
|
<kotlin.java.runtime.jar>${org.jetbrains.kotlin:kotlin-runtime:jar}</kotlin.java.runtime.jar>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
|
|||||||
@@ -86,8 +86,8 @@
|
|||||||
<version>2.6</version>
|
<version>2.6</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<systemPropertyVariables>
|
<systemPropertyVariables>
|
||||||
<KOTLIN_COMPILER_JAR>${org.jetbrains.kotlin:kotlin-compiler:jar}</KOTLIN_COMPILER_JAR>
|
<kotlin.compiler.jar>${org.jetbrains.kotlin:kotlin-compiler:jar}</kotlin.compiler.jar>
|
||||||
<KOTLIN_JAVA_RUNTIME_JAR>${org.jetbrains.kotlin:kotlin-runtime:jar}</KOTLIN_JAVA_RUNTIME_JAR>
|
<kotlin.java.runtime.jar>${org.jetbrains.kotlin:kotlin-runtime:jar}</kotlin.java.runtime.jar>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
|
|||||||
+2
-1
@@ -1 +1,2 @@
|
|||||||
org.jetbrains.kotlin.script.jsr223.KotlinJsr232JvmLocalScriptEngineFactory
|
org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -51,7 +51,7 @@ class KotlinJsr223ScriptEngineIT {
|
|||||||
val factory = ScriptEngineManager().getEngineByExtension("kts").factory
|
val factory = ScriptEngineManager().getEngineByExtension("kts").factory
|
||||||
Assert.assertNotNull(factory)
|
Assert.assertNotNull(factory)
|
||||||
val engine = factory!!.scriptEngine
|
val engine = factory!!.scriptEngine
|
||||||
Assert.assertNotNull(engine as? KotlinJsr232JvmLocalScriptEngine)
|
Assert.assertNotNull(engine as? KotlinJsr223JvmLocalScriptEngine)
|
||||||
Assert.assertSame(factory, engine!!.factory)
|
Assert.assertSame(factory, engine!!.factory)
|
||||||
val bindings = engine.createBindings()
|
val bindings = engine.createBindings()
|
||||||
Assert.assertTrue(bindings is SimpleBindings)
|
Assert.assertTrue(bindings is SimpleBindings)
|
||||||
|
|||||||
+4
@@ -1,5 +1,9 @@
|
|||||||
|
import java.lang.Exception
|
||||||
|
|
||||||
if (!args.isEmpty())
|
if (!args.isEmpty())
|
||||||
println("some args passed")
|
println("some args passed")
|
||||||
|
|
||||||
|
if (this !is org.jetbrains.kotlin.script.util.StandardScript)
|
||||||
|
throw Exception("Unexpected script base class")
|
||||||
|
|
||||||
println("Hello from Kotlin script file!")
|
println("Hello from Kotlin script file!")
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<systemPropertyVariables>
|
<systemPropertyVariables>
|
||||||
<jvm>${env.JDK_17}/bin/java</jvm>
|
<jvm>${env.JDK_17}/bin/java</jvm>
|
||||||
<KOTLIN_JAVA_RUNTIME_JAR>${org.jetbrains.kotlin:kotlin-stdlib:jar}</KOTLIN_JAVA_RUNTIME_JAR>
|
<kotlin.java.runtime.jar>${org.jetbrains.kotlin:kotlin-stdlib:jar}</kotlin.java.runtime.jar>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
|
|||||||
+3
-4
@@ -37,7 +37,7 @@ import javax.script.ScriptContext
|
|||||||
import javax.script.ScriptEngineFactory
|
import javax.script.ScriptEngineFactory
|
||||||
import javax.script.ScriptException
|
import javax.script.ScriptException
|
||||||
|
|
||||||
class KotlinJsr232JvmLocalScriptEngine(
|
class KotlinJsr223JvmLocalScriptEngine(
|
||||||
disposable: Disposable,
|
disposable: Disposable,
|
||||||
factory: ScriptEngineFactory,
|
factory: ScriptEngineFactory,
|
||||||
val templateClasspath: List<File>,
|
val templateClasspath: List<File>,
|
||||||
@@ -68,12 +68,11 @@ class KotlinJsr232JvmLocalScriptEngine(
|
|||||||
fun resetAndThrowOnErrors() {
|
fun resetAndThrowOnErrors() {
|
||||||
try {
|
try {
|
||||||
if (hasErrors) {
|
if (hasErrors) {
|
||||||
val msg = reports.joinToString("\n") { messageRenderer.render(it.severity, it.message, it.location) }
|
|
||||||
val firstErr = reports.firstOrNull { it.severity.isError }
|
val firstErr = reports.firstOrNull { it.severity.isError }
|
||||||
if (firstErr != null)
|
if (firstErr != null)
|
||||||
throw ScriptException(msg, firstErr.location.path, firstErr.location.line, firstErr.location.column)
|
throw ScriptException(messageRenderer.render(firstErr.severity, firstErr.message, firstErr.location), firstErr.location.path, firstErr.location.line, firstErr.location.column)
|
||||||
else
|
else
|
||||||
throw ScriptException(msg)
|
throw ScriptException(reports.joinToString("\n") { messageRenderer.render(it.severity, it.message, it.location) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
+6
-6
@@ -27,10 +27,10 @@ import javax.script.ScriptContext
|
|||||||
import javax.script.ScriptEngine
|
import javax.script.ScriptEngine
|
||||||
import kotlin.script.StandardScriptTemplate
|
import kotlin.script.StandardScriptTemplate
|
||||||
|
|
||||||
class KotlinJsr232JvmLocalScriptEngineFactory : KotlinJsr223JvmScriptEngineFactoryBase() {
|
class KotlinJsr223JvmLocalScriptEngineFactory : KotlinJsr223JvmScriptEngineFactoryBase() {
|
||||||
|
|
||||||
override fun getScriptEngine(): ScriptEngine =
|
override fun getScriptEngine(): ScriptEngine =
|
||||||
KotlinJsr232JvmLocalScriptEngine(
|
KotlinJsr223JvmLocalScriptEngine(
|
||||||
Disposer.newDisposable(),
|
Disposer.newDisposable(),
|
||||||
this,
|
this,
|
||||||
listOf(kotlinRuntimeJar),
|
listOf(kotlinRuntimeJar),
|
||||||
@@ -88,11 +88,11 @@ private fun makeSerializableArgumentsForTemplateWithArgsAndBindings(ctx: ScriptC
|
|||||||
private fun File.existsOrNull(): File? = existsAndCheckOrNull { true }
|
private fun File.existsOrNull(): File? = existsAndCheckOrNull { true }
|
||||||
private inline fun File.existsAndCheckOrNull(check: (File.() -> Boolean)): File? = if (exists() && check()) this else null
|
private inline fun File.existsAndCheckOrNull(check: (File.() -> Boolean)): File? = if (exists() && check()) this else null
|
||||||
|
|
||||||
private val kotlinCompilerJar = System.getProperty("KOTLIN_COMPILER_JAR")?.let(::File)?.existsOrNull()
|
private val kotlinCompilerJar = System.getProperty("kotlin.compiler.jar")?.let(::File)?.existsOrNull()
|
||||||
?: getPathUtilJar().existsAndCheckOrNull { name == KOTLIN_COMPILER_JAR }
|
?: getPathUtilJar().existsAndCheckOrNull { name == KOTLIN_COMPILER_JAR }
|
||||||
?: throw FileNotFoundException("Cannot find kotlin compiler jar, set KOTLIN_COMPILER_JAR property to proper location")
|
?: throw FileNotFoundException("Cannot find kotlin compiler jar, set kotlin.compiler.jar property to proper location")
|
||||||
|
|
||||||
private val kotlinRuntimeJar = System.getProperty("KOTLIN_JAVA_RUNTIME_JAR")?.let(::File)?.existsOrNull()
|
private val kotlinRuntimeJar = System.getProperty("kotlin.java.runtime.jar")?.let(::File)?.existsOrNull()
|
||||||
?: kotlinCompilerJar.let { File(it.parentFile, KOTLIN_JAVA_RUNTIME_JAR) }.existsOrNull()
|
?: kotlinCompilerJar.let { File(it.parentFile, KOTLIN_JAVA_RUNTIME_JAR) }.existsOrNull()
|
||||||
?: getResourcePathForClass(StandardScriptTemplate::class.java).existsOrNull()
|
?: getResourcePathForClass(StandardScriptTemplate::class.java).existsOrNull()
|
||||||
?: throw FileNotFoundException("Cannot find kotlin runtime jar, set KOTLIN_JAVA_RUNTIME_JAR property to proper location")
|
?: throw FileNotFoundException("Cannot find kotlin runtime jar, set kotlin.java.runtime.jar property to proper location")
|
||||||
+4
@@ -16,10 +16,14 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.script.util
|
package org.jetbrains.kotlin.script.util
|
||||||
|
|
||||||
|
// in case of flat or direct resolvers the value should be a direct path or file name of a jar respectively
|
||||||
|
// in case of maven resolver the maven coordinates string is accepted (resolved with com.jcabi.aether library)
|
||||||
@Target(AnnotationTarget.FILE, AnnotationTarget.EXPRESSION, AnnotationTarget.LOCAL_VARIABLE, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
|
@Target(AnnotationTarget.FILE, AnnotationTarget.EXPRESSION, AnnotationTarget.LOCAL_VARIABLE, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class DependsOn(val value: String)
|
annotation class DependsOn(val value: String)
|
||||||
|
|
||||||
|
// only flat directory repositories are supported now, so value should be a path to a directory with jars
|
||||||
|
// TODO: support other types of repos
|
||||||
@Target(AnnotationTarget.FILE, AnnotationTarget.EXPRESSION, AnnotationTarget.LOCAL_VARIABLE, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
|
@Target(AnnotationTarget.FILE, AnnotationTarget.EXPRESSION, AnnotationTarget.LOCAL_VARIABLE, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class Repository(val value: String)
|
annotation class Repository(val value: String)
|
||||||
|
|||||||
+15
-15
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.script.util.resolvers.DirectResolver
|
|||||||
import org.jetbrains.kotlin.script.util.resolvers.FlatLibDirectoryResolver
|
import org.jetbrains.kotlin.script.util.resolvers.FlatLibDirectoryResolver
|
||||||
import org.jetbrains.kotlin.script.util.resolvers.MavenResolver
|
import org.jetbrains.kotlin.script.util.resolvers.MavenResolver
|
||||||
import org.jetbrains.kotlin.script.util.resolvers.Resolver
|
import org.jetbrains.kotlin.script.util.resolvers.Resolver
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.check
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.Exception
|
import java.lang.Exception
|
||||||
import java.lang.IllegalArgumentException
|
import java.lang.IllegalArgumentException
|
||||||
@@ -34,29 +35,28 @@ open class KotlinAnnotatedScriptDependenciesResolver(val baseClassPath: List<Fil
|
|||||||
{
|
{
|
||||||
private val resolvers: MutableList<Resolver> = resolvers.toMutableList()
|
private val resolvers: MutableList<Resolver> = resolvers.toMutableList()
|
||||||
|
|
||||||
|
inner class ResolvedDependencies(previousDependencies: KotlinScriptExternalDependencies?, depsFromAnnotations: List<File> ) : KotlinScriptExternalDependencies {
|
||||||
|
override val classpath = if (resolvers.isEmpty()) baseClassPath else baseClassPath + depsFromAnnotations
|
||||||
|
override val imports = if (previousDependencies != null) emptyList() else listOf(DependsOn::class.java.`package`.name + ".*")
|
||||||
|
}
|
||||||
|
|
||||||
@AcceptedAnnotations(DependsOn::class, Repository::class)
|
@AcceptedAnnotations(DependsOn::class, Repository::class)
|
||||||
override fun resolve(script: ScriptContents,
|
override fun resolve(script: ScriptContents,
|
||||||
environment: Map<String, Any?>?,
|
environment: Map<String, Any?>?,
|
||||||
report: (ScriptDependenciesResolver.ReportSeverity, String, ScriptContents.Position?) -> Unit,
|
report: (ScriptDependenciesResolver.ReportSeverity, String, ScriptContents.Position?) -> Unit,
|
||||||
previousDependencies: KotlinScriptExternalDependencies?
|
previousDependencies: KotlinScriptExternalDependencies?
|
||||||
): Future<KotlinScriptExternalDependencies?>
|
): Future<KotlinScriptExternalDependencies?> {
|
||||||
= (if (previousDependencies != null && resolveFromAnnotations(script).isEmpty()) previousDependencies
|
val depsFromAnnotations: List<File> = resolveFromAnnotations(script)
|
||||||
else
|
return (if (previousDependencies != null && depsFromAnnotations.isEmpty()) previousDependencies
|
||||||
object : KotlinScriptExternalDependencies {
|
else ResolvedDependencies(previousDependencies, depsFromAnnotations)
|
||||||
override val classpath: Iterable<File> = if (resolvers.isEmpty()) baseClassPath else baseClassPath + resolveFromAnnotations(script)
|
).asFuture()
|
||||||
override val imports: Iterable<String> =
|
}
|
||||||
previousDependencies?.let { emptyList<String>() } ?: listOf(DependsOn::class.java.`package`.name + ".*")
|
|
||||||
}
|
|
||||||
).asFuture()
|
|
||||||
|
|
||||||
private fun resolveFromAnnotations(script: ScriptContents): List<File> {
|
private fun resolveFromAnnotations(script: ScriptContents): List<File> {
|
||||||
script.annotations.forEach {
|
script.annotations.forEach {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Repository ->
|
is Repository -> File(it.value).check { it.exists() && it.isDirectory }?.let { resolvers.add(FlatLibDirectoryResolver(it)) }
|
||||||
when {
|
?: throw IllegalArgumentException("Illegal argument for Repository annotation: ${it.value}")
|
||||||
File(it.value).run { exists() && isDirectory } -> resolvers.add(FlatLibDirectoryResolver(File(it.value)))
|
|
||||||
else -> throw IllegalArgumentException("Illegal argument for Repository annotation: ${it.value}")
|
|
||||||
}
|
|
||||||
is DependsOn -> {}
|
is DependsOn -> {}
|
||||||
is InvalidScriptResolverAnnotation -> throw Exception("Invalid annotation ${it.name}", it.error)
|
is InvalidScriptResolverAnnotation -> throw Exception("Invalid annotation ${it.name}", it.error)
|
||||||
else -> throw Exception("Unknown annotation ${it.javaClass}")
|
else -> throw Exception("Unknown annotation ${it.javaClass}")
|
||||||
@@ -83,7 +83,7 @@ private fun classpathFromClassloader(classLoader: ClassLoader): List<File>? =
|
|||||||
|
|
||||||
private fun classpathFromClasspathProperty(): List<File>? =
|
private fun classpathFromClasspathProperty(): List<File>? =
|
||||||
System.getProperty("java.class.path")?.let {
|
System.getProperty("java.class.path")?.let {
|
||||||
it.split(String.format("\\%s", File.pathSeparatorChar).toRegex()).dropLastWhile(String::isEmpty).toTypedArray()
|
it.split(String.format("\\%s", File.pathSeparatorChar).toRegex()).dropLastWhile(String::isEmpty)
|
||||||
.map(::File)
|
.map(::File)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-10
@@ -104,14 +104,12 @@ done
|
|||||||
scriptFileName: String,
|
scriptFileName: String,
|
||||||
scriptTemplate: KClass<out Any>,
|
scriptTemplate: KClass<out Any>,
|
||||||
environment: Map<String, Any?>? = null,
|
environment: Map<String, Any?>? = null,
|
||||||
runIsolated: Boolean = true,
|
|
||||||
suppressOutput: Boolean = false): Class<*>? =
|
suppressOutput: Boolean = false): Class<*>? =
|
||||||
compileScriptImpl("src/test/resources/scripts/" + scriptFileName, KotlinScriptDefinitionFromAnnotatedTemplate(scriptTemplate, null, null, environment), runIsolated, suppressOutput)
|
compileScriptImpl("src/test/resources/scripts/" + scriptFileName, KotlinScriptDefinitionFromAnnotatedTemplate(scriptTemplate, null, null, environment), suppressOutput)
|
||||||
|
|
||||||
private fun compileScriptImpl(
|
private fun compileScriptImpl(
|
||||||
scriptPath: String,
|
scriptPath: String,
|
||||||
scriptDefinition: KotlinScriptDefinition,
|
scriptDefinition: KotlinScriptDefinition,
|
||||||
runIsolated: Boolean,
|
|
||||||
suppressOutput: Boolean): Class<*>?
|
suppressOutput: Boolean): Class<*>?
|
||||||
{
|
{
|
||||||
val paths = PathUtil.getKotlinPathsForDistDirectory()
|
val paths = PathUtil.getKotlinPathsForDistDirectory()
|
||||||
@@ -123,7 +121,7 @@ done
|
|||||||
try {
|
try {
|
||||||
val configuration = CompilerConfiguration().apply {
|
val configuration = CompilerConfiguration().apply {
|
||||||
addJvmClasspathRoots(PathUtil.getJdkClassesRoots())
|
addJvmClasspathRoots(PathUtil.getJdkClassesRoots())
|
||||||
val rtJar = System.getProperty("KOTLIN_JAVA_RUNTIME_JAR")
|
val rtJar = System.getProperty("kotlin.java.runtime.jar")
|
||||||
Assert.assertNotNull(rtJar)
|
Assert.assertNotNull(rtJar)
|
||||||
addJvmClasspathRoot(File(rtJar))
|
addJvmClasspathRoot(File(rtJar))
|
||||||
put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
|
put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
|
||||||
@@ -141,14 +139,12 @@ done
|
|||||||
put(CommonConfigurationKeys.MODULE_NAME, "kotlin-script-util-test")
|
put(CommonConfigurationKeys.MODULE_NAME, "kotlin-script-util-test")
|
||||||
add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition)
|
add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition)
|
||||||
put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
|
put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
|
||||||
put(JVMConfigurationKeys.INCLUDE_RUNTIME, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val environment = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
val environment = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return if (runIsolated) KotlinToJVMBytecodeCompiler.compileScript(environment, paths)
|
return KotlinToJVMBytecodeCompiler.compileScript(environment, paths)
|
||||||
else KotlinToJVMBytecodeCompiler.compileScript(environment, this.javaClass.classLoader)
|
|
||||||
}
|
}
|
||||||
catch (e: CompilationException) {
|
catch (e: CompilationException) {
|
||||||
messageCollector.report(CompilerMessageSeverity.EXCEPTION, OutputMessageUtil.renderException(e),
|
messageCollector.report(CompilerMessageSeverity.EXCEPTION, OutputMessageUtil.renderException(e),
|
||||||
@@ -183,9 +179,13 @@ done
|
|||||||
val outStream = ByteArrayOutputStream()
|
val outStream = ByteArrayOutputStream()
|
||||||
val prevOut = System.out
|
val prevOut = System.out
|
||||||
System.setOut(PrintStream(outStream))
|
System.setOut(PrintStream(outStream))
|
||||||
body()
|
try {
|
||||||
System.out.flush()
|
body()
|
||||||
System.setOut(prevOut)
|
}
|
||||||
|
finally {
|
||||||
|
System.out.flush()
|
||||||
|
System.setOut(prevOut)
|
||||||
|
}
|
||||||
return outStream.toString()
|
return outStream.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
|
|
||||||
println("Hello, world!")
|
|
||||||
|
|
||||||
if (bindings.isNotEmpty()) {
|
|
||||||
println(bindings.joinToString { "${it.key} = ${it.value}" })
|
|
||||||
}
|
|
||||||
|
|
||||||
println("done")
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
|
|
||||||
println("Hello, world!")
|
|
||||||
|
|
||||||
if (args.isNotEmpty()) {
|
|
||||||
println(args.joinToString())
|
|
||||||
}
|
|
||||||
|
|
||||||
println("done")
|
|
||||||
-8
@@ -1,8 +0,0 @@
|
|||||||
|
|
||||||
println("Hello, world!")
|
|
||||||
|
|
||||||
if (args.isNotEmpty()) {
|
|
||||||
println(args.joinToString())
|
|
||||||
}
|
|
||||||
|
|
||||||
println("done")
|
|
||||||
Reference in New Issue
Block a user