PR-1021: Merge Keplin project scripting code into Kotlin core.

Overhauls the scripting layers (GenericRepl and related, and JSR223 and related)
Adds repeating modes (none, only latest eval'd line, or random order)
Also adds better thread-safe IO capturing, default imports, SimpleRepl wrapper, more unit tests

NOTE: the script-util part of the pull request was rejected due to various problems and incompatibilities.
It may be incorporated into the code later.

(originally cherry picked from commit 6f7d517)
This commit is contained in:
apatrida
2017-01-18 04:17:41 +01:00
committed by Ilya Chernikov
parent 8caf607378
commit 5ad06e1e92
29 changed files with 1146 additions and 548 deletions
@@ -1,124 +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.cli.common.repl
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import java.io.File
import java.net.URLClassLoader
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
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 = makeReplClassLoader(baseClassloader, baseClasspath)
private val classLoaderLock = ReentrantReadWriteLock()
// TODO: consider to expose it as a part of (evaluator, invoker) interface
private val evalStateLock = ReentrantReadWriteLock()
private val compiledLoadedClassesHistory = ReplHistory<ClassWithInstance>()
override fun eval(codeLine: ReplCodeLine,
history: List<ReplCodeLine>,
compiledClasses: List<CompiledClassData>,
hasResult: Boolean,
classpathAddendum: List<File>,
invokeWrapper: InvokeWrapper?
): ReplEvalResult = evalStateLock.write {
checkAndUpdateReplHistoryCollection(compiledLoadedClassesHistory, history)?.let {
return@eval ReplEvalResult.HistoryMismatch(compiledLoadedClassesHistory.lines, it)
}
var mainLineClassName: String? = null
fun classNameFromPath(path: String) = JvmClassName.byInternalName(path.replaceFirst("\\.class$".toRegex(), ""))
fun processCompiledClasses() {
val expectedClassName = "Line${codeLine.no}"
compiledClasses.filter { it.path.endsWith(".class") }
.forEach {
val className = classNameFromPath(it.path)
if (className.internalName == expectedClassName || className.internalName.endsWith("/$expectedClassName")) {
mainLineClassName = className.internalName.replace('/', '.')
}
classLoader.addClass(className, it.bytes)
}
}
classLoaderLock.read {
if (classpathAddendum.isNotEmpty()) {
classLoaderLock.write {
classLoader = makeReplClassLoader(classLoader, classpathAddendum)
}
}
processCompiledClasses()
}
fun compiledClassesNames() = compiledClasses.map { classNameFromPath(it.path).fqNameForClassNameWithoutDollars.asString() }
val scriptClass = classLoaderLock.read {
try {
classLoader.loadClass(mainLineClassName!!)
}
catch (e: Throwable) {
return ReplEvalResult.Error.Runtime(compiledLoadedClassesHistory.lines, "Error loading class $mainLineClassName: known classes: ${compiledClassesNames()}",
e as? Exception)
}
}
fun getConstructorParams(): Array<Class<*>> =
(compiledLoadedClassesHistory.values.map { it.klass.java } +
(scriptArgs?.mapIndexed { i, it -> scriptArgsTypes?.getOrNull(i) ?: it?.javaClass ?: Any::class.java } ?: emptyList())
).toTypedArray()
fun getConstructorArgs() = (compiledLoadedClassesHistory.values.map { it.instance } + scriptArgs.orEmpty()).toTypedArray()
val constructorParams: Array<Class<*>> = getConstructorParams()
val constructorArgs: Array<Any?> = getConstructorArgs()
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
val scriptInstance =
try {
invokeWrapper?.invoke { scriptInstanceConstructor.newInstance(*constructorArgs) } ?: scriptInstanceConstructor.newInstance(*constructorArgs)
}
catch (e: Throwable) {
// ignore everything in the stack trace until this constructor call
return ReplEvalResult.Error.Runtime(compiledLoadedClassesHistory.lines, renderReplStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}.<init>"), e as? Exception)
}
compiledLoadedClassesHistory.add(codeLine, ClassWithInstance(scriptClass.kotlin, scriptInstance))
val rvField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true }
val rv: Any? = rvField.get(scriptInstance)
return if (hasResult) ReplEvalResult.ValueResult(compiledLoadedClassesHistory.lines, rv) else ReplEvalResult.UnitResult(compiledLoadedClassesHistory.lines)
}
override val lastEvaluatedScript: ClassWithInstance? get() = evalStateLock.read { compiledLoadedClassesHistory.values.lastOrNull() }
companion object {
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
}
}
private fun makeReplClassLoader(baseClassloader: ClassLoader?, baseClasspath: Iterable<File>) =
ReplClassLoader(URLClassLoader(baseClasspath.map { it.toURI().toURL() }.toTypedArray(), baseClassloader))
@@ -0,0 +1,112 @@
/*
* Copyright 2010-2017 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.cli.common.repl
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
class GenericReplCompilingEvaluator(val compiler: ReplCompiler,
baseClasspath: Iterable<File>,
baseClassloader: ClassLoader?,
protected val fallbackScriptArgs: ScriptArgsWithTypes? = null,
repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT,
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : ReplFullEvaluator {
val evaluator = GenericReplEvaluator(baseClasspath, baseClassloader, fallbackScriptArgs, repeatingMode, stateLock)
override fun compileAndEval(codeLine: ReplCodeLine, scriptArgs: ScriptArgsWithTypes?, verifyHistory: List<ReplCodeLine>?, invokeWrapper: InvokeWrapper?): ReplEvalResult {
return stateLock.write {
@Suppress("DEPRECATION")
val compiled = compiler.compile(codeLine, verifyHistory)
when (compiled) {
is ReplCompileResult.Error -> ReplEvalResult.Error.CompileTime(compiled.compiledHistory, compiled.message, compiled.location)
is ReplCompileResult.HistoryMismatch -> ReplEvalResult.HistoryMismatch(compiled.compiledHistory, compiled.lineNo)
is ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete(compiled.compiledHistory)
is ReplCompileResult.CompiledClasses -> {
@Suppress("DEPRECATION")
val result = eval(compiled, scriptArgs, invokeWrapper)
when (result) {
is ReplEvalResult.Error,
is ReplEvalResult.HistoryMismatch,
is ReplEvalResult.Incomplete -> {
result.completedEvalHistory.lastOrNull()?.let { compiler.resetToLine(it) }
result
}
is ReplEvalResult.ValueResult,
is ReplEvalResult.UnitResult ->
result
else -> throw IllegalStateException("Unknown evaluator result type ${compiled}")
}
}
else -> throw IllegalStateException("Unknown compiler result type ${compiled}")
}
}
}
override fun resetToLine(lineNumber: Int): List<ReplCodeLine> {
stateLock.write {
val removedCompiledLines = compiler.resetToLine(lineNumber)
val removedEvaluatorLines = evaluator.resetToLine(lineNumber)
removedCompiledLines.zip(removedEvaluatorLines).forEach {
if (it.first != it.second) {
throw IllegalStateException("History mistmatch when resetting lines")
}
}
return removedCompiledLines
}
}
override fun resetToLine(line: ReplCodeLine): List<ReplCodeLine> = resetToLine(line.no)
override val lastEvaluatedScripts: List<EvalHistoryType> get() = evaluator.lastEvaluatedScripts
override val history: List<ReplCodeLine> get() = evaluator.history
override val currentClasspath: List<File> get() = evaluator.currentClasspath
override val compiledHistory: List<ReplCodeLine> get() = compiler.history
override val evaluatedHistory: List<ReplCodeLine> get() = evaluator.history
override fun eval(compileResult: ReplCompileResult.CompiledClasses, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult {
return evaluator.eval(compileResult, scriptArgs, invokeWrapper)
}
override fun check(codeLine: ReplCodeLine): ReplCheckResult {
return compiler.check(codeLine)
}
override fun compileToEvaluable(codeLine: ReplCodeLine, defaultScriptArgs: ScriptArgsWithTypes?, verifyHistory: List<ReplCodeLine>?): Pair<ReplCompileResult, Evaluable?> {
val compiled = compiler.compile(codeLine, verifyHistory)
return if (compiled is ReplCompileResult.CompiledClasses) {
Pair(compiled, DelayedEvaluation(compiled, stateLock, evaluator, defaultScriptArgs ?: fallbackScriptArgs))
}
else {
Pair(compiled, null)
}
}
class DelayedEvaluation(override val compiledCode: ReplCompileResult.CompiledClasses,
private val stateLock: ReentrantReadWriteLock,
private val evaluator: ReplEvaluator,
private val defaultScriptArgs: ScriptArgsWithTypes?) : Evaluable {
override fun eval(scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult {
return stateLock.write { evaluator.eval(compiledCode, scriptArgs ?: defaultScriptArgs, invokeWrapper) }
}
}
}
@@ -0,0 +1,209 @@
/*
* 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.cli.common.repl
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import java.io.File
import java.net.URLClassLoader
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
open class GenericReplEvaluator(baseClasspath: Iterable<File>,
baseClassloader: ClassLoader?,
protected val fallbackScriptArgs: ScriptArgsWithTypes? = null,
protected val repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT,
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : ReplEvaluator {
private val topClassLoader: ReplClassLoader = makeReplClassLoader(baseClassloader, baseClasspath)
private val evaluatedHistory = ReplHistory<EvalClassWithInstanceAndLoader>()
override fun resetToLine(lineNumber: Int): List<ReplCodeLine> {
return stateLock.write {
evaluatedHistory.resetToLine(lineNumber)
}.map { it.first }
}
override val history: List<ReplCodeLine> get() = stateLock.read { evaluatedHistory.copySources() }
override val currentClasspath: List<File> get() = stateLock.read {
evaluatedHistory.copyValues().lastOrNull()?.let { it.classLoader.listAllUrlsAsFiles() }
?: topClassLoader.listAllUrlsAsFiles()
}
private class HistoryActions(val effectiveHistory: List<EvalClassWithInstanceAndLoader>,
val verify: (compareHistory: SourceList?) -> Int?,
val addPlaceholder: (line: CompiledReplCodeLine, value: EvalClassWithInstanceAndLoader) -> Unit,
val removePlaceholder: (line: CompiledReplCodeLine) -> Boolean,
val addFinal: (line: CompiledReplCodeLine, value: EvalClassWithInstanceAndLoader) -> Unit,
val processClasses: (compileResult: ReplCompileResult.CompiledClasses) -> Pair<ClassLoader, Class<out Any>>)
private fun prependClassLoaderWithNewClasses(effectiveHistory: List<EvalClassWithInstanceAndLoader>, compileResult: ReplCompileResult.CompiledClasses): Pair<ClassLoader, Class<out Any>> {
return stateLock.write {
var mainLineClassName: String? = null
val classLoader = makeReplClassLoader(effectiveHistory.lastOrNull()?.classLoader ?: topClassLoader, compileResult.classpathAddendum)
fun classNameFromPath(path: String) = JvmClassName.byInternalName(path.replaceFirst("\\.class$".toRegex(), ""))
fun compiledClassesNames() = compileResult.classes.map { classNameFromPath(it.path).fqNameForClassNameWithoutDollars.asString() }
val expectedClassName = compileResult.generatedClassname
compileResult.classes.filter { it.path.endsWith(".class") }
.forEach {
val className = classNameFromPath(it.path)
if (className.internalName == expectedClassName || className.internalName.endsWith("/$expectedClassName")) {
mainLineClassName = className.internalName.replace('/', '.')
}
classLoader.addClass(className, it.bytes)
}
val scriptClass = try {
classLoader.loadClass(mainLineClassName!!)
}
catch (t: Throwable) {
throw Exception("Error loading class $mainLineClassName: known classes: ${compiledClassesNames()}", t)
}
Pair(classLoader, scriptClass)
}
}
override fun eval(compileResult: ReplCompileResult.CompiledClasses,
scriptArgs: ScriptArgsWithTypes?,
invokeWrapper: InvokeWrapper?): ReplEvalResult {
stateLock.write {
val verifyHistory = compileResult.compiledHistory.dropLast(1)
val defaultHistoryActor = HistoryActions(
effectiveHistory = evaluatedHistory.copyValues(),
verify = { line -> evaluatedHistory.firstMismatchingHistory(line) },
addPlaceholder = { line, value -> evaluatedHistory.add(line, value) },
removePlaceholder = { line -> evaluatedHistory.removeLast(line) },
addFinal = { line, value -> evaluatedHistory.add(line, value) },
processClasses = { compiled ->
prependClassLoaderWithNewClasses(evaluatedHistory.copyValues(), compiled)
})
val historyActor: HistoryActions = when (repeatingMode) {
ReplRepeatingMode.NONE -> defaultHistoryActor
ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT -> {
val lastItem = evaluatedHistory.lastItem()
if (lastItem == null || lastItem.first.source != compileResult.compiledCodeLine.source) {
defaultHistoryActor
}
else {
val trimmedHistory = ReplHistory(evaluatedHistory.copyAll().dropLast(1))
HistoryActions(
effectiveHistory = trimmedHistory.copyValues(),
verify = { trimmedHistory.firstMismatchingHistory(it) },
addPlaceholder = { _, _ -> NO_ACTION() },
removePlaceholder = { NO_ACTION_THAT_RETURNS(true) },
addFinal = { line, value ->
evaluatedHistory.removeLast(line)
evaluatedHistory.add(line, value)
},
processClasses = { _ ->
Pair(lastItem.second.classLoader, lastItem.second.klass.java)
})
}
}
ReplRepeatingMode.REPEAT_ANY_PREVIOUS -> {
if (evaluatedHistory.isEmpty() || !evaluatedHistory.contains(compileResult.compiledCodeLine.source)) {
defaultHistoryActor
}
else {
val historyCopy = evaluatedHistory.copyAll()
val matchingItem = historyCopy.first { it.first.source == compileResult.compiledCodeLine.source }
val trimmedHistory = ReplHistory(evaluatedHistory.copyAll().takeWhile { it != matchingItem })
HistoryActions(
effectiveHistory = trimmedHistory.copyValues(),
verify = { trimmedHistory.firstMismatchingHistory(it) },
addPlaceholder = { _, _ -> NO_ACTION() },
removePlaceholder = { NO_ACTION_THAT_RETURNS(true) },
addFinal = { line, value ->
val extraLines = evaluatedHistory.resetToLine(line)
evaluatedHistory.removeLast(line)
evaluatedHistory.add(line, value)
extraLines.forEach {
evaluatedHistory.add(it.first, it.second)
}
},
processClasses = { _ ->
Pair(matchingItem.second.classLoader, matchingItem.second.klass.java)
})
}
}
}
val firstMismatch = historyActor.verify(verifyHistory)
if (firstMismatch != null) {
return@eval ReplEvalResult.HistoryMismatch(evaluatedHistory.copySources(), firstMismatch)
}
val (classLoader, scriptClass) = try {
historyActor.processClasses(compileResult)
}
catch (e: Exception) {
return@eval ReplEvalResult.Error.Runtime(evaluatedHistory.copySources(),
e.message!!, e)
}
val currentScriptArgs = scriptArgs ?: fallbackScriptArgs
val useScriptArgs = currentScriptArgs?.scriptArgs
val useScriptArgsTypes = currentScriptArgs?.scriptArgsTypes?.map { it.java }
val constructorParams: Array<Class<*>> = (historyActor.effectiveHistory.map { it.klass.java } +
(useScriptArgs?.mapIndexed { i, it -> useScriptArgsTypes?.getOrNull(i) ?: it?.javaClass ?: Any::class.java } ?: emptyList())
).toTypedArray()
val constructorArgs: Array<Any?> = (historyActor.effectiveHistory.map { it.instance } + useScriptArgs.orEmpty()).toTypedArray()
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
historyActor.addPlaceholder(compileResult.compiledCodeLine, EvalClassWithInstanceAndLoader(scriptClass.kotlin, null, classLoader, invokeWrapper))
val scriptInstance =
try {
if (invokeWrapper != null) invokeWrapper.invoke { scriptInstanceConstructor.newInstance(*constructorArgs) }
else scriptInstanceConstructor.newInstance(*constructorArgs)
}
catch (e: Throwable) {
historyActor.removePlaceholder(compileResult.compiledCodeLine)
// ignore everything in the stack trace until this constructor call
return@eval ReplEvalResult.Error.Runtime(evaluatedHistory.copySources(),
renderReplStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}.<init>"), e as? Exception)
}
historyActor.removePlaceholder(compileResult.compiledCodeLine)
historyActor.addFinal(compileResult.compiledCodeLine, EvalClassWithInstanceAndLoader(scriptClass.kotlin, scriptInstance, classLoader, invokeWrapper))
val resultField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true }
val resultValue: Any? = resultField.get(scriptInstance)
return if (compileResult.hasResult) ReplEvalResult.ValueResult(evaluatedHistory.copySources(), resultValue)
else ReplEvalResult.UnitResult(evaluatedHistory.copySources())
}
}
override val lastEvaluatedScripts: List<EvalHistoryType> get() {
return stateLock.read { evaluatedHistory.copyAll() }
}
companion object {
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
}
private fun makeReplClassLoader(baseClassloader: ClassLoader?, baseClasspath: Iterable<File>) =
ReplClassLoader(URLClassLoader(baseClasspath.map { it.toURI().toURL() }.toTypedArray(), baseClassloader))
}
@@ -17,60 +17,117 @@
package org.jetbrains.kotlin.cli.common.repl
import org.jetbrains.kotlin.utils.tryCreateCallableMapping
import java.lang.reflect.Proxy
import javax.script.Invocable
import javax.script.ScriptException
import kotlin.reflect.*
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.full.functions
import kotlin.reflect.full.safeCast
@Suppress("unused") // used externally (kotlin.script.utils)
interface KotlinJsr223JvmInvocableScriptEngine : Invocable {
val replScriptEvaluator: ReplEvaluatorBase
val replScriptEvaluator: ReplEvaluatorExposedInternalHistory
fun <T: Any> getInterface(klass: KClass<T>): Any? {
val (_, instance) = replScriptEvaluator.lastEvaluatedScript ?: throw IllegalArgumentException("no script ")
return getInterface(instance, klass)
}
fun <T: Any> getInterface(receiver: Any, klass: KClass<T>): Any? {
return klass.safeCast(receiver)
private fun prioritizedHistory(receiverClass: KClass<*>?, receiverInstance: Any?): List<EvalClassWithInstanceAndLoader> {
return replScriptEvaluator.lastEvaluatedScripts.map { it.second }.filter { it.instance != null }.reversed().assertNotEmpty("no script ").let { history ->
if (receiverInstance != null) {
val receiverKlass = receiverClass ?: receiverInstance.javaClass.kotlin
val receiverInHistory = history.find { it.instance == receiverInstance } ?:
EvalClassWithInstanceAndLoader(receiverKlass, receiverInstance, receiverKlass.java.classLoader, history.first().invokeWrapper)
listOf(receiverInHistory) + history.filterNot { it == receiverInHistory }
}
else {
history
}
}
}
override fun invokeFunction(name: String?, vararg args: Any?): Any? {
if (name == null) throw java.lang.NullPointerException("function name cannot be null")
val (klass, instance) = replScriptEvaluator.lastEvaluatedScript ?: throw IllegalArgumentException("no script ")
return invokeImpl(klass, instance, name, args, invokeWrapper = null)
return invokeImpl(prioritizedHistory(null, null), name, args)
}
override fun invokeMethod(thiz: Any?, name: String?, vararg args: Any?): Any? {
if (name == null) throw java.lang.NullPointerException("method name cannot be null")
if (thiz == null) throw IllegalArgumentException("cannot invoke method on the null object")
return invokeImpl(thiz.javaClass.kotlin, thiz, name, args, invokeWrapper = null)
return invokeImpl(prioritizedHistory(thiz.javaClass.kotlin, thiz), name, args)
}
private fun invokeImpl(prioritizedCallOrder: List<EvalClassWithInstanceAndLoader>, name: String, args: Array<out Any?>): Any? {
// TODO: cache the method lookups?
val (fn, mapping, invokeWrapper) = prioritizedCallOrder.asSequence().map { attempt ->
val candidates = attempt.klass.functions.filter { it.name == name }
candidates.findMapping(listOf<Any?>(attempt.instance) + args)?.let {
Triple(it.first, it.second, attempt.invokeWrapper)
}
}.filterNotNull().firstOrNull() ?: throw NoSuchMethodException("no suitable function '$name' found")
val res = try {
if (invokeWrapper != null) {
invokeWrapper.invoke {
fn.callBy(mapping)
}
}
else {
fn.callBy(mapping)
}
}
catch (e: Throwable) {
// ignore everything in the stack trace until this constructor call
throw ScriptException(renderReplStackTrace(e.cause!!, startFromMethodName = fn.name))
}
return if (fn.returnType.classifier == Unit::class) Unit else res
}
override fun <T : Any> getInterface(clasz: Class<T>?): T? {
val (_, instance) = replScriptEvaluator.lastEvaluatedScript ?: throw IllegalArgumentException("no script ")
return getInterface(instance, clasz)
return proxyInterface(null, clasz)
}
override fun <T : Any> getInterface(thiz: Any?, clasz: Class<T>?): T? {
if (thiz == null) throw IllegalArgumentException("object cannot be null")
return proxyInterface(thiz, clasz)
}
private fun <T : Any> proxyInterface(thiz: Any?, clasz: Class<T>?): T? {
replScriptEvaluator.lastEvaluatedScripts.assertNotEmpty("no script")
val priority = prioritizedHistory(thiz?.javaClass?.kotlin, thiz)
if (clasz == null) throw IllegalArgumentException("class object cannot be null")
if (!clasz.isInterface) throw IllegalArgumentException("expecting interface")
return clasz.kotlin.safeCast(thiz)
// TODO: cache the method lookups?
val proxy = Proxy.newProxyInstance(Thread.currentThread().contextClassLoader, arrayOf(clasz)) { _, method, args ->
invokeImpl(priority, method.name, args ?: emptyArray())
}
return clasz.kotlin.safeCast(proxy)
}
}
private fun invokeImpl(receiverClass: KClass<*>, receiverInstance: Any, name: String, args: Array<out Any?>, invokeWrapper: InvokeWrapper?): Any? {
private fun invokeImpl(prioritizedCallOrder: List<EvalClassWithInstanceAndLoader>, name: String, args: Array<out Any?>): Any? {
// TODO: cache the method lookups?
val (fn, mapping, invokeWrapper) = prioritizedCallOrder.asSequence().map { attempt ->
val candidates = attempt.klass.functions.filter { it.name == name }
candidates.findMapping(listOf<Any?>(attempt.instance) + args)?.let {
Triple(it.first, it.second, attempt.invokeWrapper)
}
}.filterNotNull().firstOrNull() ?: throw NoSuchMethodException("no suitable function '$name' found")
val candidates = receiverClass.functions.filter { it.name == name }
val (fn, mapping) = candidates.findMapping(listOf<Any?>(receiverInstance) + args) ?:
throw NoSuchMethodException("no suitable function '$name' found")
val res = try {
invokeWrapper?.invoke {
if (invokeWrapper != null) {
invokeWrapper.invoke {
fn.callBy(mapping)
}
}
else {
fn.callBy(mapping)
} ?: fn.callBy(mapping)
}
}
catch (e: Throwable) {
// ignore everything in the stack trace until this constructor call
@@ -18,25 +18,27 @@ package org.jetbrains.kotlin.cli.common.repl
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import java.io.Reader
import java.util.concurrent.atomic.AtomicInteger
import javax.script.*
val KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY = "kotlin.script.history"
// TODO consider additional error handling
@Suppress("UNCHECKED_CAST")
val Bindings.kotlinScriptHistory: MutableList<ReplCodeLine>
get() = getOrPut(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, { arrayListOf<ReplCodeLine>() }) as MutableList<ReplCodeLine>
abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEngineFactory) : AbstractScriptEngine(), ScriptEngine, Compilable {
private var lineCount = 0
protected var codeLineNumber = AtomicInteger(0)
protected abstract val replCompiler: ReplCompiler
protected abstract val replCompiler: ReplCompileAction
protected abstract val replScriptEvaluator: ReplFullEvaluator
protected abstract val replEvaluator: ReplCompiledEvaluator
override fun eval(script: String, context: ScriptContext): Any? = compileAndEval(script, context)
override fun eval(script: String, context: ScriptContext): Any? = compile(script, context).eval(context)
override fun eval(script: Reader, context: ScriptContext): Any? = compile(script.readText(), context).eval()
override fun eval(script: Reader, context: ScriptContext): Any? = compileAndEval(script.readText(), context)
override fun compile(script: String): CompiledScript = compile(script, getContext())
@@ -46,42 +48,56 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn
override fun getFactory(): ScriptEngineFactory = myFactory
open fun compile(script: String, context: ScriptContext): CompiledScript {
lineCount += 1
fun nextCodeLine(code: String) = ReplCodeLine(codeLineNumber.incrementAndGet(), code)
val codeLine = ReplCodeLine(lineCount, script)
open fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = null
open fun compileAndEval(script: String, context: ScriptContext): Any? {
val codeLine = nextCodeLine(script)
val history = context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory
val result = replScriptEvaluator.compileAndEval(codeLine, scriptArgs = overrideScriptArgs(context), verifyHistory = history)
val ret = when (result) {
is ReplEvalResult.ValueResult -> result.value
is ReplEvalResult.UnitResult -> null
is ReplEvalResult.Error -> throw ScriptException(result.message)
is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code")
is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}")
}
context.getBindings(ScriptContext.ENGINE_SCOPE).put(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, ArrayList(result.completedEvalHistory))
return ret
}
open fun compile(script: String, context: ScriptContext): CompiledScript {
val codeLine = nextCodeLine(script)
val history = context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory
val compileResult = replCompiler.compile(codeLine, history)
val compiled = when (compileResult) {
is ReplCompileResult.Error -> throw ScriptException("Error${compileResult.locationString()}: ${compileResult.message}")
val result = replCompiler.compile(codeLine, history)
val compiled = when (result) {
is ReplCompileResult.Error -> throw ScriptException("Error${result.locationString()}: ${result.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
is ReplCompileResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}")
is ReplCompileResult.CompiledClasses -> result
}
context.getBindings(ScriptContext.ENGINE_SCOPE).put(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, ArrayList(result.compiledHistory))
return CompiledKotlinScript(this, codeLine, compiled)
}
open fun eval(compiledScript: CompiledKotlinScript, context: ScriptContext): Any? {
val history = context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory
val evalResult = try {
replEvaluator.eval(compiledScript.codeLine, history, compiledScript.compiledData.classes, compiledScript.compiledData.hasResult, compiledScript.compiledData.classpathAddendum)
val result = try {
replScriptEvaluator.eval(compiledScript.compiledData, scriptArgs = overrideScriptArgs(context))
}
catch (e: Exception) {
throw ScriptException(e)
}
val ret = when (evalResult) {
is ReplEvalResult.ValueResult -> evalResult.value
val ret = when (result) {
is ReplEvalResult.ValueResult -> result.value
is ReplEvalResult.UnitResult -> null
is ReplEvalResult.Error -> throw ScriptException(evalResult.message)
is ReplEvalResult.Error -> throw ScriptException(result.message)
is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code")
is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${evalResult.lineNo}")
is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}")
}
history.add(compiledScript.codeLine)
context.getBindings(ScriptContext.ENGINE_SCOPE).put(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, ArrayList(result.completedEvalHistory))
return ret
}
@@ -28,105 +28,173 @@ data class ReplCodeLine(val no: Int, val code: String) : Serializable {
}
}
data class ClassWithInstance(val klass: KClass<*>, val instance: Any)
// TODO: consider storing code hash where source is not needed
data class CompiledReplCodeLine(val className: String, val source: ReplCodeLine) : Serializable {
companion object {
private val serialVersionUID: Long = 8228307678L
}
}
data class CompiledClassData(val path: String, val bytes: ByteArray) : Serializable {
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)
companion object {
private val serialVersionUID: Long = 8228357578L
}
}
sealed class ReplCheckResult(val updatedHistory: List<ReplCodeLine>) : Serializable {
class Ok(updatedHistory: List<ReplCodeLine>) : ReplCheckResult(updatedHistory)
class Incomplete(updatedHistory: List<ReplCodeLine>) : ReplCheckResult(updatedHistory)
class Error(updatedHistory: List<ReplCodeLine>,
val message: String,
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION
) : ReplCheckResult(updatedHistory)
{
interface ReplCheckAction {
fun check(codeLine: ReplCodeLine): ReplCheckResult
}
sealed class ReplCheckResult : Serializable {
class Ok : ReplCheckResult()
class Incomplete : ReplCheckResult()
class Error(val message: String,
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : ReplCheckResult() {
override fun toString(): String = "Error(message = \"$message\""
}
companion object {
private val serialVersionUID: Long = 8228357578L
private val serialVersionUID: Long = 8228307678L
}
}
sealed class ReplCompileResult(val updatedHistory: List<ReplCodeLine>) : Serializable {
class CompiledClasses(updatedHistory: List<ReplCodeLine>,
interface ReplResettableCodeLine {
fun resetToLine(lineNumber: Int): List<ReplCodeLine>
fun resetToLine(line: ReplCodeLine): List<ReplCodeLine> = resetToLine(line.no)
}
interface ReplCodeLineHistory {
val history: List<ReplCodeLine>
}
interface ReplCombinedHistory {
val compiledHistory: List<ReplCodeLine>
val evaluatedHistory: List<ReplCodeLine>
}
interface ReplCompileAction {
fun compile(codeLine: ReplCodeLine, verifyHistory: List<ReplCodeLine>? = null): ReplCompileResult
}
sealed class ReplCompileResult(val compiledHistory: List<ReplCodeLine>) : Serializable {
class CompiledClasses(compiledHistory: List<ReplCodeLine>,
val compiledCodeLine: CompiledReplCodeLine,
val generatedClassname: String,
val classes: List<CompiledClassData>,
val hasResult: Boolean,
val classpathAddendum: List<File>
) : ReplCompileResult(updatedHistory)
class Incomplete(updatedHistory: List<ReplCodeLine>) : ReplCompileResult(updatedHistory)
class HistoryMismatch(updatedHistory: List<ReplCodeLine>, val lineNo: Int): ReplCompileResult(updatedHistory)
class Error(updatedHistory: List<ReplCodeLine>,
val classpathAddendum: List<File>) : ReplCompileResult(compiledHistory)
class Incomplete(compiledHistory: List<ReplCodeLine>) : ReplCompileResult(compiledHistory)
class HistoryMismatch(compiledHistory: List<ReplCodeLine>, val lineNo: Int) : ReplCompileResult(compiledHistory)
class Error(compiledHistory: List<ReplCodeLine>,
val message: String,
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION
) : ReplCompileResult(updatedHistory)
{
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : ReplCompileResult(compiledHistory) {
override fun toString(): String = "Error(message = \"$message\""
}
companion object {
private val serialVersionUID: Long = 8228357578L
private val serialVersionUID: Long = 8228307678L
}
}
sealed class ReplEvalResult(val updatedHistory: List<ReplCodeLine>) : Serializable {
class ValueResult(updatedHistory: List<ReplCodeLine>, val value: Any?) : ReplEvalResult(updatedHistory) {
interface ReplCompiler : ReplResettableCodeLine, ReplCodeLineHistory, ReplCompileAction, ReplCheckAction
typealias EvalHistoryType = Pair<CompiledReplCodeLine, EvalClassWithInstanceAndLoader>
interface ReplEvaluatorExposedInternalHistory {
val lastEvaluatedScripts: List<EvalHistoryType>
}
interface ReplClasspath {
val currentClasspath: List<File>
}
data class EvalClassWithInstanceAndLoader(val klass: KClass<*>, val instance: Any?, val classLoader: ClassLoader, val invokeWrapper: InvokeWrapper?)
interface ReplEvalAction {
fun eval(compileResult: ReplCompileResult.CompiledClasses,
scriptArgs: ScriptArgsWithTypes? = null,
invokeWrapper: InvokeWrapper? = null): ReplEvalResult
}
sealed class ReplEvalResult(val completedEvalHistory: List<ReplCodeLine>) : Serializable {
class ValueResult(completedEvalHistory: List<ReplCodeLine>, val value: Any?) : ReplEvalResult(completedEvalHistory) {
override fun toString(): String = "Result: $value"
}
class UnitResult(updatedHistory: List<ReplCodeLine>) : ReplEvalResult(updatedHistory)
class Incomplete(updatedHistory: List<ReplCodeLine>) : ReplEvalResult(updatedHistory)
class HistoryMismatch(updatedHistory: List<ReplCodeLine>, val lineNo: Int): ReplEvalResult(updatedHistory)
sealed class Error(updatedHistory: List<ReplCodeLine>, val message: String) : ReplEvalResult(updatedHistory) {
class Runtime(updatedHistory: List<ReplCodeLine>, message: String, val cause: Exception? = null) : Error(updatedHistory, message)
class CompileTime(updatedHistory: List<ReplCodeLine>,
class UnitResult(completedEvalHistory: List<ReplCodeLine>) : ReplEvalResult(completedEvalHistory)
class Incomplete(completedEvalHistory: List<ReplCodeLine>) : ReplEvalResult(completedEvalHistory)
class HistoryMismatch(completedEvalHistory: List<ReplCodeLine>, val lineNo: Int) : ReplEvalResult(completedEvalHistory)
sealed class Error(completedEvalHistory: List<ReplCodeLine>, val message: String) : ReplEvalResult(completedEvalHistory) {
class Runtime(completedEvalHistory: List<ReplCodeLine>, message: String, val cause: Exception? = null) : Error(completedEvalHistory, message)
class CompileTime(completedEvalHistory: List<ReplCodeLine>,
message: String,
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION
) : Error(updatedHistory, message)
val location: CompilerMessageLocation = CompilerMessageLocation.NO_LOCATION) : Error(completedEvalHistory, message)
override fun toString(): String = "${this::class.simpleName}Error(message = \"$message\""
}
companion object {
private val serialVersionUID: Long = 8228357578L
private val serialVersionUID: Long = 8228307678L
}
}
interface ReplChecker {
fun check(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplCheckResult
interface ReplEvaluator : ReplResettableCodeLine, ReplCodeLineHistory, ReplEvaluatorExposedInternalHistory, ReplEvalAction, ReplClasspath
interface ReplAtomicEvalAction {
fun compileAndEval(codeLine: ReplCodeLine,
scriptArgs: ScriptArgsWithTypes? = null,
verifyHistory: List<ReplCodeLine>? = null,
invokeWrapper: InvokeWrapper? = null): ReplEvalResult
}
interface ReplCompiler : ReplChecker {
fun compile(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplCompileResult
interface ReplAtomicEvaluator : ReplResettableCodeLine, ReplCombinedHistory, ReplEvaluatorExposedInternalHistory, ReplAtomicEvalAction, ReplCheckAction, ReplClasspath
interface ReplDelayedEvalAction {
fun compileToEvaluable(codeLine: ReplCodeLine, defaultScriptArgs: ScriptArgsWithTypes? = null, verifyHistory: List<ReplCodeLine>?): Pair<ReplCompileResult, Evaluable?>
}
interface ReplEvaluatorBase {
val lastEvaluatedScript: ClassWithInstance?
interface Evaluable {
val compiledCode: ReplCompileResult.CompiledClasses
fun eval(scriptArgs: ScriptArgsWithTypes? = null, invokeWrapper: InvokeWrapper? = null): ReplEvalResult
}
interface ReplCompiledEvaluator : ReplEvaluatorBase {
interface ReplFullEvaluator : ReplEvaluator, ReplAtomicEvaluator, ReplDelayedEvalAction, ReplCombinedHistory
fun eval(codeLine: ReplCodeLine,
history: List<ReplCodeLine>,
compiledClasses: List<CompiledClassData>,
hasResult: Boolean,
classpathAddendum: List<File>,
invokeWrapper: InvokeWrapper? = null
): ReplEvalResult
/**
* Keep args and arg types together, so as a whole they are present or absent
*/
class ScriptArgsWithTypes(val scriptArgs: Array<out Any?>, val scriptArgsTypes: Array<out KClass<out Any>>) : Serializable {
companion object {
private val serialVersionUID: Long = 8529357500L
}
}
interface ReplEvaluator : ReplChecker, ReplEvaluatorBase {
fun eval(codeLine: ReplCodeLine,
history: List<ReplCodeLine>,
invokeWrapper: InvokeWrapper? = null
): ReplEvalResult
interface ScriptTemplateEmptyArgsProvider {
val defaultEmptyArgs: ScriptArgsWithTypes?
}
class SimpleScriptTemplateEmptyArgsProvider(override val defaultEmptyArgs: ScriptArgsWithTypes? = null) : ScriptTemplateEmptyArgsProvider
enum class ReplRepeatingMode {
NONE,
REPEAT_ONLY_MOST_RECENT,
REPEAT_ANY_PREVIOUS
}
interface InvokeWrapper {
operator fun<T> invoke(body: () -> T): T // e.g. for capturing io
operator fun <T> invoke(body: () -> T): T // e.g. for capturing io
}
@@ -0,0 +1,105 @@
/*
* Copyright 2010-2017 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.cli.common.repl
import java.io.Serializable
import java.util.concurrent.ConcurrentLinkedDeque
typealias CompiledHistoryItem<T> = Pair<CompiledReplCodeLine, T>
typealias SourceHistoryItem<T> = Pair<ReplCodeLine, T>
typealias CompiledHistoryStorage<T> = ConcurrentLinkedDeque<CompiledHistoryItem<T>>
typealias CompiledHistoryList<T> = List<CompiledHistoryItem<T>>
typealias SourceHistoryList<T> = List<SourceHistoryItem<T>>
typealias SourceList = List<ReplCodeLine>
/*
Not thread safe, the caller is assumed to lock access. Even though ConcurrentLinkedDeque is used, there are mutliple
actions that require locking.
*/
class ReplHistory<T>(startingHistory: CompiledHistoryList<T> = emptyList()) : Serializable {
private val history: CompiledHistoryStorage<T> = ConcurrentLinkedDeque(startingHistory)
fun isEmpty(): Boolean = history.isEmpty()
fun isNotEmpty(): Boolean = history.isNotEmpty()
fun add(line: CompiledReplCodeLine, value: T) {
history.add(line to value)
}
/* remove last line only if it is the line we think it is */
fun removeLast(line: CompiledReplCodeLine): Boolean {
return if (history.peekLast().first == line) {
history.removeLast()
true
}
else {
false
}
}
/* resets back to a previous line number and returns the lines removed */
fun resetToLine(lineNumber: Int): SourceHistoryList<T> {
val removed = arrayListOf<SourceHistoryItem<T>>()
while ((history.peekLast()?.first?.source?.no ?: -1) > lineNumber) {
removed.add(history.removeLast().let { Pair(it.first.source, it.second) })
}
return removed.reversed()
}
fun resetToLine(line: ReplCodeLine): SourceHistoryList<T> {
return resetToLine(line.no)
}
fun resetToLine(line: CompiledReplCodeLine): CompiledHistoryList<T> {
val removed = arrayListOf<CompiledHistoryItem<T>>()
while ((history.peekLast()?.first?.source?.no ?: -1) > line.source.no) {
removed.add(history.removeLast())
}
return removed.reversed()
}
fun contains(line: ReplCodeLine): Boolean = history.any { it.first.source == line }
fun contains(line: CompiledReplCodeLine): Boolean = history.any { it.first == line }
fun lastItem(): CompiledHistoryItem<T>? = history.peekLast()
fun lastCodeLine(): CompiledReplCodeLine? = lastItem()?.first
fun lastValue(): T? = lastItem()?.second
fun checkHistoryIsInSync(compareHistory: SourceList?): Boolean {
return firstMismatchingHistory(compareHistory) == null
}
// return from the compareHistory the first line that does not match or null
fun firstMismatchingHistory(compareHistory: SourceList?): Int? {
if (compareHistory == null) return null
val firstMismatch = history.zip(compareHistory).firstOrNull { it.first.first.source != it.second }?.second?.no
if (compareHistory.size == history.size) return firstMismatch
if (compareHistory.size > history.size) return compareHistory[history.size].no
return history.toList()[compareHistory.size].first.source.no
}
fun copySources(): SourceList = history.map { it.first.source }
fun copyValues(): List<T> = history.map { it.second }
fun copyAll(): CompiledHistoryList<T> = history.toList()
companion object {
private val serialVersionUID: Long = 8328353000L
}
}
@@ -17,44 +17,11 @@
package org.jetbrains.kotlin.cli.common.repl
import com.google.common.base.Throwables
import java.io.Serializable
import java.io.File
import java.net.URLClassLoader
// TODO: thread safety!!
data class ReplHistory<T>(
val lines: MutableList<ReplCodeLine> = arrayListOf(),
val values: MutableList<T> = arrayListOf()
) : Serializable
{
init { assert(isValid()) }
fun isValid() = lines.size == values.size
fun add(line: ReplCodeLine, value: T) {
lines.add(line)
values.add(value)
}
fun trimAt(idx: Int) {
lines.dropLast(lines.size - idx)
values.dropLast(lines.size - idx)
}
companion object {
private val serialVersionUID: Long = 8228357578L
}
}
fun <T> checkAndUpdateReplHistoryCollection(history: ReplHistory<T>, linesHistory: Iterable<ReplCodeLine>): Int? {
assert(history.isValid())
val linesHistoryIt = linesHistory.iterator()
var idx = 0
while (linesHistoryIt.hasNext()) {
val curLine = linesHistoryIt.next()
if (history.lines[idx] != curLine) return curLine.no
idx += 1
}
history.trimAt(idx)
return null
}
fun makeSriptBaseName(codeLine: ReplCodeLine, generation: Long) =
"Line_${codeLine.no}" + if (generation > 1) "_gen_${generation}" else ""
fun renderReplStackTrace(cause: Throwable, startFromMethodName: String): String {
val newTrace = arrayListOf<StackTraceElement>()
@@ -76,3 +43,22 @@ fun renderReplStackTrace(cause: Throwable, startFromMethodName: String): String
return Throwables.getStackTraceAsString(cause)
}
fun NO_ACTION(): Unit = Unit
fun <T> NO_ACTION_THAT_RETURNS(v: T): T = v
internal fun ClassLoader.listAllUrlsAsFiles(): List<File> {
val parents = generateSequence(this) { loader -> loader.parent }.filterIsInstance(URLClassLoader::class.java)
return parents.fold(emptyList<File>()) { accum, loader ->
loader.listLocalUrlsAsFiles() + accum
}.distinct()
}
internal fun URLClassLoader.listLocalUrlsAsFiles(): List<File> {
return this.urLs.map { it.toString().removePrefix("file:") }.filterNotNull().map { File(it) }
}
internal fun <T : Any> List<T>.assertNotEmpty(error: String): List<T> {
if (this.isEmpty()) throw IllegalStateException(error)
return this
}
@@ -17,43 +17,71 @@
package org.jetbrains.kotlin.cli.jvm.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.diagnostic.Logger
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import java.io.File
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantReadWriteLock
private val logger = Logger.getInstance(GenericRepl::class.java)
open class GenericRepl(
open class GenericRepl protected constructor(
disposable: Disposable,
scriptDefinition: KotlinScriptDefinition,
compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector,
baseClassloader: ClassLoader?,
scriptArgs: Array<Any?>? = null,
scriptArgsTypes: Array<Class<*>>? = null
) : ReplEvaluator, GenericReplCompiler(disposable, scriptDefinition, compilerConfiguration, messageCollector) {
protected val fallbackScriptArgs: ScriptArgsWithTypes?,
protected val repeatingMode: ReplRepeatingMode = ReplRepeatingMode.NONE,
protected val stateLock: ReentrantReadWriteLock
) : ReplCompiler, ReplEvaluator, ReplAtomicEvaluator {
constructor (
disposable: Disposable,
scriptDefinition: KotlinScriptDefinition,
compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector,
baseClassloader: ClassLoader?,
fallbackScriptArgs: ScriptArgsWithTypes? = null
) : this(disposable, scriptDefinition, compilerConfiguration, messageCollector, baseClassloader, fallbackScriptArgs, stateLock = ReentrantReadWriteLock())
private val compiledEvaluator = GenericReplCompiledEvaluator(compilerConfiguration.jvmClasspathRoots, baseClassloader, scriptArgs, scriptArgsTypes)
protected val compiler: ReplCompiler by lazy { GenericReplCompiler(disposable, scriptDefinition, compilerConfiguration, messageCollector, stateLock) }
protected val evaluator: ReplFullEvaluator by lazy { GenericReplCompilingEvaluator(compiler, compilerConfiguration.jvmClasspathRoots, baseClassloader, fallbackScriptArgs, repeatingMode, stateLock) }
protected var codeLineNumber = AtomicInteger(0)
override val lastEvaluatedScript: ClassWithInstance? get() = compiledEvaluator.lastEvaluatedScript
override fun resetToLine(lineNumber: Int): List<ReplCodeLine> {
return evaluator.resetToLine(lineNumber)
}
@Synchronized
override fun eval(codeLine: ReplCodeLine, history: List<ReplCodeLine>, invokeWrapper: InvokeWrapper?): ReplEvalResult =
compileAndEval(this, compiledEvaluator, codeLine, history, invokeWrapper)
}
override fun resetToLine(line: ReplCodeLine): List<ReplCodeLine> {
return evaluator.resetToLine(line)
}
override val history: List<ReplCodeLine> get() = evaluator.history
override val compiledHistory: List<ReplCodeLine> get() = compiler.history
override val evaluatedHistory: List<ReplCodeLine> get() = evaluator.history
fun compileAndEval(replCompiler: ReplCompiler, replCompiledEvaluator: ReplCompiledEvaluator, codeLine: ReplCodeLine, history: List<ReplCodeLine>, invokeWrapper: InvokeWrapper?): ReplEvalResult =
replCompiler.compile(codeLine, history).let {
when (it) {
is ReplCompileResult.Incomplete -> ReplEvalResult.Incomplete(it.updatedHistory)
is ReplCompileResult.HistoryMismatch -> ReplEvalResult.HistoryMismatch(it.updatedHistory, it.lineNo)
is ReplCompileResult.Error -> ReplEvalResult.Error.CompileTime(it.updatedHistory, it.message, it.location)
is ReplCompileResult.CompiledClasses -> replCompiledEvaluator.eval(codeLine, history, it.classes, it.hasResult, it.classpathAddendum, invokeWrapper)
}
}
override val currentClasspath: List<File>
get() = evaluator.currentClasspath
override val lastEvaluatedScripts: List<EvalHistoryType>
get() = evaluator.lastEvaluatedScripts
override fun check(codeLine: ReplCodeLine): ReplCheckResult {
return compiler.check(codeLine)
}
override fun compile(codeLine: ReplCodeLine, verifyHistory: List<ReplCodeLine>?): ReplCompileResult {
return compiler.compile(codeLine, verifyHistory)
}
override fun eval(compileResult: ReplCompileResult.CompiledClasses, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult {
return evaluator.eval(compileResult, scriptArgs, invokeWrapper)
}
override fun compileAndEval(codeLine: ReplCodeLine, scriptArgs: ScriptArgsWithTypes?, verifyHistory: List<ReplCodeLine>?, invokeWrapper: InvokeWrapper?): ReplEvalResult {
return evaluator.compileAndEval(codeLine, scriptArgs, verifyHistory, invokeWrapper)
}
fun nextCodeLine(code: String) = ReplCodeLine(codeLineNumber.incrementAndGet(), code)
}
@@ -14,9 +14,14 @@
* limitations under the License.
*/
@file:Suppress("UNUSED_PARAMETER")
package org.jetbrains.kotlin.cli.jvm.repl
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.cli.common.repl.CompiledReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ReplHistory
import org.jetbrains.kotlin.cli.common.repl.ReplResettableCodeLine
import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport
import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
@@ -30,25 +35,36 @@ import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfoFactory
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
import org.jetbrains.kotlin.resolve.lazy.*
import org.jetbrains.kotlin.resolve.lazy.data.KtClassLikeInfo
import org.jetbrains.kotlin.resolve.lazy.declarations.*
import org.jetbrains.kotlin.resolve.repl.ReplState
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyScriptDescriptor
import org.jetbrains.kotlin.resolve.scopes.ImportingScope
import org.jetbrains.kotlin.resolve.scopes.utils.parentsWithSelf
import org.jetbrains.kotlin.resolve.scopes.utils.replaceImportingScopes
import org.jetbrains.kotlin.script.ScriptPriorities
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
class CliReplAnalyzerEngine(environment: KotlinCoreEnvironment) {
class GenericReplAnalyzer(environment: KotlinCoreEnvironment,
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : ReplResettableCodeLine {
private val topDownAnalysisContext: TopDownAnalysisContext
private val topDownAnalyzer: LazyTopDownAnalyzer
private val resolveSession: ResolveSession
private val scriptDeclarationFactory: ScriptMutableDeclarationProviderFactory
private val replState = ResetableReplState()
val module: ModuleDescriptorImpl
private val replState = ReplState()
get() = stateLock.read { field }
val trace: BindingTraceContext = CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace()
get() = stateLock.read { field }
init {
// Module source scope is empty because all binary classes are in the dependency module, and all source classes are guaranteed
// to be found via ResolveSession. The latter is true as long as light classes are not needed in REPL (which is currently true
// because no symbol declared in the REPL session can be used from Java)
// to be found via ResolveSession. The latter is true as long as light classes are not needed in NONE (which is currently true
// because no symbol declared in the NONE session can be used from Java)
val container = TopDownAnalyzerFacadeForJVM.createContainer(
environment.project,
emptyList(),
@@ -78,18 +94,26 @@ class CliReplAnalyzerEngine(environment: KotlinCoreEnvironment) {
}
}
fun analyzeReplLine(psiFile: KtFile, priority: Int): ReplLineAnalysisResult {
topDownAnalysisContext.scripts.clear()
trace.clearDiagnostics()
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, priority)
return doAnalyze(psiFile)
override fun resetToLine(lineNumber: Int): List<ReplCodeLine> {
return stateLock.write { replState.resetToLine(lineNumber) }
}
private fun doAnalyze(linePsi: KtFile): ReplLineAnalysisResult {
override fun resetToLine(line: ReplCodeLine): List<ReplCodeLine> = resetToLine(line.no)
fun analyzeReplLine(psiFile: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult {
return stateLock.write {
topDownAnalysisContext.scripts.clear()
trace.clearDiagnostics()
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
doAnalyze(psiFile, codeLine)
}
}
private fun doAnalyze(linePsi: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult {
scriptDeclarationFactory.setDelegateFactory(FileBasedDeclarationProviderFactory(resolveSession.storageManager, listOf(linePsi)))
replState.submitLine(linePsi)
replState.submitLine(linePsi, codeLine)
val context = topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi))
@@ -100,12 +124,12 @@ class CliReplAnalyzerEngine(environment: KotlinCoreEnvironment) {
val diagnostics = trace.bindingContext.diagnostics
val hasErrors = diagnostics.any { it.severity == Severity.ERROR }
if (hasErrors) {
replState.lineFailure(linePsi)
replState.lineFailure(linePsi, codeLine)
return ReplLineAnalysisResult.WithErrors(diagnostics)
}
else {
val scriptDescriptor = context.scripts[linePsi.script]!!
replState.lineSuccess(linePsi, scriptDescriptor)
replState.lineSuccess(linePsi, codeLine, scriptDescriptor)
return ReplLineAnalysisResult.Successful(scriptDescriptor, diagnostics)
}
@@ -153,4 +177,60 @@ class CliReplAnalyzerEngine(environment: KotlinCoreEnvironment) {
}
}
}
class ResetableReplState() {
private val successfulLines = ReplHistory<LineInfo.SuccessfulLine>()
private val submittedLines = hashMapOf<KtFile, LineInfo>()
fun resetToLine(lineNumber: Int): List<ReplCodeLine> {
val removed = successfulLines.resetToLine(lineNumber)
removed.forEach { submittedLines.remove(it.second.linePsi) }
return removed.map { it.first }
}
fun resetToLine(line: ReplCodeLine): List<ReplCodeLine> = resetToLine(line.no)
fun submitLine(ktFile: KtFile, codeLine: ReplCodeLine) {
val line = LineInfo.SubmittedLine(ktFile, successfulLines.lastValue())
submittedLines[ktFile] = line
ktFile.fileScopesCustomizer = object : FileScopesCustomizer {
override fun createFileScopes(fileScopeFactory: FileScopeFactory): FileScopes {
return lineInfo(ktFile)?.let { computeFileScopes(it, fileScopeFactory) } ?: fileScopeFactory.createScopesForFile(ktFile)
}
}
}
fun lineSuccess(ktFile: KtFile, codeLine: ReplCodeLine, scriptDescriptor: LazyScriptDescriptor) {
val successfulLine = LineInfo.SuccessfulLine(ktFile, successfulLines.lastValue(), scriptDescriptor)
submittedLines[ktFile] = successfulLine
successfulLines.add(CompiledReplCodeLine(ktFile.name, codeLine), successfulLine)
}
fun lineFailure(ktFile: KtFile, codeLine: ReplCodeLine) {
submittedLines[ktFile] = LineInfo.FailedLine(ktFile, successfulLines.lastValue())
}
private fun lineInfo(ktFile: KtFile) = submittedLines[ktFile]
// use sealed?
private sealed class LineInfo {
abstract val linePsi: KtFile
abstract val parentLine: SuccessfulLine?
class SubmittedLine(override val linePsi: KtFile, override val parentLine: SuccessfulLine?) : LineInfo()
class SuccessfulLine(override val linePsi: KtFile, override val parentLine: SuccessfulLine?, val lineDescriptor: LazyScriptDescriptor) : LineInfo()
class FailedLine(override val linePsi: KtFile, override val parentLine: SuccessfulLine?) : LineInfo()
}
private fun computeFileScopes(lineInfo: LineInfo, fileScopeFactory: FileScopeFactory): FileScopes? {
// create scope that wraps previous line lexical scope and adds imports from this line
val lexicalScopeAfterLastLine = lineInfo.parentLine?.lineDescriptor?.scopeForInitializerResolution ?: return null
val lastLineImports = lexicalScopeAfterLastLine.parentsWithSelf.first { it is ImportingScope } as ImportingScope
val scopesForThisLine = fileScopeFactory.createScopesForFile(lineInfo.linePsi, lastLineImports)
val combinedLexicalScopes = lexicalScopeAfterLastLine.replaceImportingScopes(scopesForThisLine.importingScope)
return FileScopes(combinedLexicalScopes, scopesForThisLine.importingScope, scopesForThisLine.importResolver)
}
}
}
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.cli.jvm.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.psi.PsiFileFactory
@@ -25,8 +26,8 @@ import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.repl.ReplCheckResult
import org.jetbrains.kotlin.cli.common.repl.ReplChecker
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.makeSriptBaseName
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.repl.messages.DiagnosticMessageHolder
@@ -37,14 +38,18 @@ import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
open class GenericReplChecker(
disposable: Disposable,
val scriptDefinition: KotlinScriptDefinition,
val compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector
) : ReplChecker {
protected val environment = run {
messageCollector: MessageCollector,
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) {
internal val environment = run {
compilerConfiguration.apply {
add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition)
put<MessageCollector>(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
@@ -53,41 +58,43 @@ open class GenericReplChecker(
KotlinCoreEnvironment.createForProduction(disposable, compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
}
protected val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
private val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
// "line" - is the unit of evaluation here, could in fact consists of several character lines
protected class LineState(
internal class LineState(
val codeLine: ReplCodeLine,
val psiFile: KtFile,
val errorHolder: DiagnosticMessageHolder)
protected var lineState: LineState? = null
private var _lineState: LineState? = null
internal val lineState: LineState? get() = stateLock.read { _lineState }
fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder()
@Synchronized
override fun check(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplCheckResult {
val virtualFile =
LightVirtualFile("line${codeLine.no}${KotlinParserDefinition.STD_SCRIPT_EXT}", KotlinLanguage.INSTANCE, codeLine.code).apply {
charset = CharsetToolkit.UTF8_CHARSET
}
val psiFile: KtFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
?: error("Script file not analyzed at line ${codeLine.no}: ${codeLine.code}")
fun check(codeLine: ReplCodeLine, generation: Long): ReplCheckResult {
stateLock.write {
val scriptFileName = makeSriptBaseName(codeLine, generation)
val virtualFile =
LightVirtualFile("${scriptFileName}${KotlinParserDefinition.STD_SCRIPT_EXT}", KotlinLanguage.INSTANCE, codeLine.code).apply {
charset = CharsetToolkit.UTF8_CHARSET
}
val psiFile: KtFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
?: 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.reportSyntaxErrors(psiFile, errorHolder)
if (!syntaxErrorReport.isHasErrors) {
lineState = LineState(codeLine, psiFile, errorHolder)
}
if (!syntaxErrorReport.isHasErrors) {
_lineState = LineState(codeLine, psiFile, errorHolder)
}
return when {
syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof -> ReplCheckResult.Incomplete(history)
syntaxErrorReport.isHasErrors -> ReplCheckResult.Error(history, errorHolder.renderedDiagnostics)
else -> ReplCheckResult.Ok(history)
return when {
syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof -> ReplCheckResult.Incomplete()
syntaxErrorReport.isHasErrors -> ReplCheckResult.Error(errorHolder.renderedDiagnostics)
else -> ReplCheckResult.Ok()
}
}
}
}
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.cli.jvm.repl
import com.intellij.openapi.Disposable
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
@@ -29,78 +30,115 @@ import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.KotlinScriptExternalDependencies
import java.io.File
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
open class GenericReplCompiler(
disposable: Disposable,
scriptDefinition: KotlinScriptDefinition,
compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector
) : ReplCompiler, GenericReplChecker(disposable, scriptDefinition, compilerConfiguration, messageCollector) {
private val analyzerEngine = CliReplAnalyzerEngine(environment)
open class GenericReplCompiler(disposable: Disposable,
protected val scriptDefinition: KotlinScriptDefinition,
protected val compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector,
protected val stateLock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : ReplCompiler {
private val checker = GenericReplChecker(disposable, scriptDefinition, compilerConfiguration, messageCollector, stateLock)
private val analyzerEngine = GenericReplAnalyzer(checker.environment, stateLock)
private var lastDependencies: KotlinScriptExternalDependencies? = null
private val descriptorsHistory = ReplHistory<ScriptDescriptor>()
@Synchronized
override fun compile(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplCompileResult {
checkAndUpdateReplHistoryCollection(descriptorsHistory, history)?.let {
return@compile ReplCompileResult.HistoryMismatch(descriptorsHistory.lines, it)
}
private val generation = AtomicLong(1)
val (psiFile, errorHolder) = run {
if (lineState == null || lineState!!.codeLine != codeLine) {
val res = check(codeLine, history)
when (res) {
is ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete(res.updatedHistory)
is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(res.updatedHistory, res.message, res.location)
is ReplCheckResult.Ok -> {} // continue
override fun resetToLine(lineNumber: Int): List<ReplCodeLine> {
return stateLock.write {
generation.incrementAndGet()
val removedCompiledLines = descriptorsHistory.resetToLine(lineNumber)
val removedAnalyzedLines = analyzerEngine.resetToLine(lineNumber)
removedCompiledLines.zip(removedAnalyzedLines).forEach {
if (it.first.first != it.second) {
throw IllegalStateException("History mistmatch when resetting lines")
}
}
Pair(lineState!!.psiFile, lineState!!.errorHolder)
removedCompiledLines
}.map { it.first }
}
override val history: List<ReplCodeLine> get() = stateLock.read { descriptorsHistory.copySources() }
override fun check(codeLine: ReplCodeLine): ReplCheckResult {
return checker.check(codeLine, generation.get())
}
override fun compile(codeLine: ReplCodeLine, verifyHistory: List<ReplCodeLine>?): ReplCompileResult {
stateLock.write {
val firstMismatch = descriptorsHistory.firstMismatchingHistory(verifyHistory)
if (firstMismatch != null) {
return@compile ReplCompileResult.HistoryMismatch(descriptorsHistory.copySources(), firstMismatch)
}
val currentGeneration = generation.get()
val (psiFile, errorHolder) = run {
if (checker.lineState == null || checker.lineState!!.codeLine != codeLine) {
val res = checker.check(codeLine, currentGeneration)
when (res) {
is ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete(descriptorsHistory.copySources())
is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(descriptorsHistory.copySources(), res.message, res.location)
is ReplCheckResult.Ok -> NO_ACTION()
}
}
Pair(checker.lineState!!.psiFile, checker.lineState!!.errorHolder)
}
val newDependencies = scriptDefinition.getDependenciesFor(psiFile, checker.environment.project, lastDependencies)
var classpathAddendum: List<File>? = null
if (lastDependencies != newDependencies) {
lastDependencies = newDependencies
classpathAddendum = newDependencies?.let { checker.environment.updateClasspath(it.classpath.map(::JvmClasspathRoot)) }
}
val analysisResult = analyzerEngine.analyzeReplLine(psiFile, codeLine)
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
val scriptDescriptor = when (analysisResult) {
is GenericReplAnalyzer.ReplLineAnalysisResult.WithErrors -> return ReplCompileResult.Error(descriptorsHistory.copySources(), errorHolder.renderedDiagnostics)
is GenericReplAnalyzer.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.copyValues()
state.beforeCompile()
KotlinCodegenFacade.generatePackage(
state,
psiFile.script!!.getContainingKtFile().packageFqName,
setOf(psiFile.script!!.getContainingKtFile()),
org.jetbrains.kotlin.codegen.CompilationErrorHandler.THROW_EXCEPTION)
val generatedClassname = makeSriptBaseName(codeLine, currentGeneration)
val compiledCodeLine = CompiledReplCodeLine(generatedClassname, codeLine)
descriptorsHistory.add(compiledCodeLine, scriptDescriptor)
return ReplCompileResult.CompiledClasses(descriptorsHistory.copySources(),
compiledCodeLine,
generatedClassname,
state.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) },
state.replSpecific.hasResult,
classpathAddendum ?: emptyList())
}
val newDependencies = scriptDefinition.getDependenciesFor(psiFile, environment.project, lastDependencies)
var classpathAddendum: List<File>? = null
if (lastDependencies != newDependencies) {
lastDependencies = newDependencies
classpathAddendum = newDependencies?.let { environment.updateClasspath(it.classpath.map(::JvmClasspathRoot)) }
}
val analysisResult = analyzerEngine.analyzeReplLine(psiFile, codeLine.no)
AnalyzerWithCompilerReport.Companion.reportDiagnostics(analysisResult.diagnostics, errorHolder)
val scriptDescriptor = when (analysisResult) {
is CliReplAnalyzerEngine.ReplLineAnalysisResult.WithErrors -> return ReplCompileResult.Error(descriptorsHistory.lines, 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.values
state.beforeCompile()
KotlinCodegenFacade.generatePackage(
state,
psiFile.script!!.getContainingKtFile().packageFqName,
setOf(psiFile.script!!.getContainingKtFile()),
org.jetbrains.kotlin.codegen.CompilationErrorHandler.THROW_EXCEPTION)
descriptorsHistory.add(codeLine, scriptDescriptor)
return ReplCompileResult.CompiledClasses(descriptorsHistory.lines,
state.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) },
state.replSpecific.hasResult,
classpathAddendum ?: emptyList())
}
companion object {
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
}
}
}
@@ -24,11 +24,10 @@ import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.repl.ReplClassLoader
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.cli.jvm.repl.CliReplAnalyzerEngine.ReplLineAnalysisResult.Successful
import org.jetbrains.kotlin.cli.jvm.repl.CliReplAnalyzerEngine.ReplLineAnalysisResult.WithErrors
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.CompilationErrorHandler
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
@@ -65,7 +64,7 @@ class ReplInterpreter(
}
private val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
private val analyzerEngine = CliReplAnalyzerEngine(environment)
private val analyzerEngine = GenericReplAnalyzer(environment)
fun eval(line: String): LineResult {
++lineNumber
@@ -99,11 +98,11 @@ class ReplInterpreter(
return LineResult.Error.CompileTime(errorHolder.renderedDiagnostics)
}
val analysisResult = analyzerEngine.analyzeReplLine(psiFile, lineNumber)
val analysisResult = analyzerEngine.analyzeReplLine(psiFile, ReplCodeLine(lineNumber, "fake line"))
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
val scriptDescriptor = when (analysisResult) {
is WithErrors -> return LineResult.Error.CompileTime(errorHolder.renderedDiagnostics)
is Successful -> analysisResult.scriptDescriptor
is GenericReplAnalyzer.ReplLineAnalysisResult.WithErrors -> return LineResult.Error.CompileTime(errorHolder.renderedDiagnostics)
is GenericReplAnalyzer.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
else -> error("Unexpected result ${analysisResult.javaClass}")
}
@@ -35,15 +35,15 @@ open class KotlinRemoteReplClientBase(
targetPlatform: CompileService.TargetPlatform,
templateClasspath: List<File>,
templateClassName: String,
scriptArgs: Array<Any?>? = null,
scriptArgsTypes: Array<Class<*>>? = null,
scriptArgs: Array<out Any?>? = null,
scriptArgsTypes: Array<out Class<out Any>>? = null,
compilerMessagesOutputStream: OutputStream = System.err,
evalOutputStream: OutputStream? = null,
evalErrorStream: OutputStream? = null,
evalInputStream: InputStream? = null,
port: Int = SOCKET_ANY_FREE_PORT,
operationsTracer: RemoteOperationsTracer? = null
) : ReplChecker {
) {
val sessionId = compileService.leaseReplSession(
clientAliveFlagFile?.absolutePath,
@@ -70,10 +70,6 @@ open class KotlinRemoteReplClientBase(
}
})
}
override fun check(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplCheckResult {
return compileService.remoteReplLineCheck(sessionId, codeLine, history).get()
}
}
class KotlinRemoteReplCompiler(
@@ -99,10 +95,20 @@ class KotlinRemoteReplCompiler(
evalInputStream = null,
port = port,
operationsTracer = operationsTracer
), ReplCompiler {
), ReplCompiler, ReplCheckAction {
override fun resetToLine(lineNumber: Int): List<ReplCodeLine> {
return emptyList() // TODO: not implemented, no current need
}
override fun compile(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplCompileResult {
return compileService.remoteReplLineCompile(sessionId, codeLine, history).get()
override val history: List<ReplCodeLine>
get() = emptyList() // TODO: not implemented, no current need
override fun check(codeLine: ReplCodeLine): ReplCheckResult {
return compileService.remoteReplLineCheck(sessionId, codeLine).get()
}
override fun compile(codeLine: ReplCodeLine, verifyHistory: List<ReplCodeLine>?): ReplCompileResult {
return compileService.remoteReplLineCompile(sessionId, codeLine, verifyHistory).get()
}
}
@@ -114,8 +120,8 @@ class KotlinRemoteReplEvaluator(
targetPlatform: CompileService.TargetPlatform,
templateClasspath: List<File>,
templateClassName: String,
scriptArgs: Array<Any?>?,
scriptArgsTypes: Array<Class<*>>?,
scriptArgs: Array<out Any?>? = null,
scriptArgsTypes: Array<out Class<out Any>>? = null,
compilerMessagesOutputStream: OutputStream,
evalOutputStream: OutputStream?,
evalErrorStream: OutputStream?,
@@ -137,12 +143,20 @@ class KotlinRemoteReplEvaluator(
evalInputStream = evalInputStream,
port = port,
operationsTracer = operationsTracer
), ReplEvaluator {
), ReplAtomicEvalAction, ReplEvaluatorExposedInternalHistory, ReplCheckAction {
override val lastEvaluatedScript: ClassWithInstance? = null // not implemented, no need so far
override val lastEvaluatedScripts: List<EvalHistoryType> = emptyList() // not implemented, no need so far
// TODO: invokeWrapper is ignored here, and in the daemon the session wrapper is used instead; So consider to make it per call (avoid performance penalties though)
override fun eval(codeLine: ReplCodeLine, history: List<ReplCodeLine>, invokeWrapper: InvokeWrapper?): ReplEvalResult {
return compileService.remoteReplLineEval(sessionId, codeLine, history).get()
// TODO: scriptArgs are ignored here, they should be passed through
override fun compileAndEval(codeLine: ReplCodeLine,
scriptArgs: ScriptArgsWithTypes?,
verifyHistory: List<ReplCodeLine>?,
invokeWrapper: InvokeWrapper?): ReplEvalResult {
return compileService.remoteReplLineEval(sessionId, codeLine, verifyHistory).get()
}
override fun check(codeLine: ReplCodeLine): ReplCheckResult {
return compileService.remoteReplLineCheck(sessionId, codeLine).get()
}
}
@@ -16,8 +16,10 @@
package org.jetbrains.kotlin.daemon.common
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.common.repl.ReplCheckResult
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 java.io.File
import java.io.Serializable
import java.rmi.Remote
@@ -148,8 +150,8 @@ interface CompileService : Remote {
servicesFacade: CompilerCallbackServicesFacade,
templateClasspath: List<File>,
templateClassName: String,
scriptArgs: Array<Any?>?,
scriptArgsTypes: Array<Class<*>>?,
scriptArgs: Array<out Any?>?,
scriptArgsTypes: Array<out Class<out Any>>?,
compilerMessagesOutputStream: RemoteOutputStream,
evalOutputStream: RemoteOutputStream?,
evalErrorStream: RemoteOutputStream?,
@@ -163,21 +165,20 @@ interface CompileService : Remote {
@Throws(RemoteException::class)
fun remoteReplLineCheck(
sessionId: Int,
codeLine: ReplCodeLine,
history: List<ReplCodeLine>
codeLine: ReplCodeLine
): CallResult<ReplCheckResult>
@Throws(RemoteException::class)
fun remoteReplLineCompile(
sessionId: Int,
codeLine: ReplCodeLine,
history: List<ReplCodeLine>
history: List<ReplCodeLine>?
): CallResult<ReplCompileResult>
@Throws(RemoteException::class)
fun remoteReplLineEval(
sessionId: Int,
codeLine: ReplCodeLine,
history: List<ReplCodeLine>
history: List<ReplCodeLine>?
): CallResult<ReplEvalResult>
}
@@ -23,29 +23,36 @@ import com.intellij.openapi.vfs.impl.jar.CoreJarFileSystem
import org.jetbrains.kotlin.cli.common.CLICompiler
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY
import org.jetbrains.kotlin.cli.common.arguments.*
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.K2MetadataCompilerArguments
import org.jetbrains.kotlin.cli.common.messages.*
import org.jetbrains.kotlin.cli.common.modules.ModuleXmlParser
import org.jetbrains.kotlin.cli.common.repl.ReplCheckResult
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.cli.common.repl.*
import org.jetbrains.kotlin.cli.js.K2JSCompiler
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.metadata.K2MetadataCompiler
import org.jetbrains.kotlin.config.IncrementalCompilation
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.incremental.*
import org.jetbrains.kotlin.daemon.report.*
import org.jetbrains.kotlin.daemon.incremental.RemoteAnnotationsFileUpdater
import org.jetbrains.kotlin.daemon.incremental.RemoteArtifactChangesProvider
import org.jetbrains.kotlin.daemon.incremental.RemoteChangesRegostry
import org.jetbrains.kotlin.daemon.report.CompileServicesFacadeMessageCollector
import org.jetbrains.kotlin.daemon.report.DaemonMessageReporter
import org.jetbrains.kotlin.daemon.report.DaemonMessageReporterPrintStreamAdapter
import org.jetbrains.kotlin.daemon.report.RemoteICReporter
import org.jetbrains.kotlin.incremental.*
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
import org.jetbrains.kotlin.modules.Module
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
import org.jetbrains.kotlin.utils.addToStdlib.check
import org.jetbrains.kotlin.utils.stackTraceStr
import java.io.*
import java.io.BufferedOutputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.PrintStream
import java.rmi.NoSuchObjectException
import java.rmi.registry.Registry
import java.rmi.server.UnicastRemoteObject
@@ -445,8 +452,8 @@ class CompileServiceImpl(
servicesFacade: CompilerCallbackServicesFacade,
templateClasspath: List<File>,
templateClassName: String,
scriptArgs: Array<Any?>?,
scriptArgsTypes: Array<Class<*>>?,
scriptArgs: Array<out Any?>?,
scriptArgsTypes: Array<out Class<out Any>>?,
compilerMessagesOutputStream: RemoteOutputStream,
evalOutputStream: RemoteOutputStream?,
evalErrorStream: RemoteOutputStream?,
@@ -457,7 +464,9 @@ class CompileServiceImpl(
CompileService.CallResult.Error("Sorry, only JVM target platform is supported now")
else {
val disposable = Disposer.newDisposable()
val repl = KotlinJvmReplService(disposable, templateClasspath, templateClassName, scriptArgs, scriptArgsTypes, compilerMessagesOutputStream, operationsTracer)
val repl = KotlinJvmReplService(disposable, templateClasspath, templateClassName,
scriptArgs?.let { ScriptArgsWithTypes(it, scriptArgsTypes?.map { it.kotlin }?.toTypedArray() ?: emptyArray()) },
compilerMessagesOutputStream, operationsTracer)
val sessionId = state.sessions.leaseSession(ClientOrSessionProxy(aliveFlagPath, repl, disposable))
CompileService.CallResult.Good(sessionId)
@@ -467,14 +476,14 @@ class CompileServiceImpl(
// TODO: add more checks (e.g. is it a repl session)
override fun releaseReplSession(sessionId: Int): CompileService.CallResult<Nothing> = releaseCompileSession(sessionId)
override fun remoteReplLineCheck(sessionId: Int, codeLine: ReplCodeLine, history: List<ReplCodeLine>): CompileService.CallResult<ReplCheckResult> =
override fun remoteReplLineCheck(sessionId: Int, codeLine: ReplCodeLine): CompileService.CallResult<ReplCheckResult> =
ifAlive(minAliveness = Aliveness.Alive) {
withValidRepl(sessionId) {
check(codeLine, history)
check(codeLine)
}
}
override fun remoteReplLineCompile(sessionId: Int, codeLine: ReplCodeLine, history: List<ReplCodeLine>): CompileService.CallResult<ReplCompileResult> =
override fun remoteReplLineCompile(sessionId: Int, codeLine: ReplCodeLine, history: List<ReplCodeLine>?): CompileService.CallResult<ReplCompileResult> =
ifAlive(minAliveness = Aliveness.Alive) {
withValidRepl(sessionId) {
compile(codeLine, history)
@@ -484,11 +493,11 @@ class CompileServiceImpl(
override fun remoteReplLineEval(
sessionId: Int,
codeLine: ReplCodeLine,
history: List<ReplCodeLine>
history: List<ReplCodeLine>?
): CompileService.CallResult<ReplEvalResult> =
ifAlive(minAliveness = Aliveness.Alive) {
withValidRepl(sessionId) {
eval(codeLine, history)
compileAndEval(codeLine, verifyHistory = history)
}
}
@@ -22,7 +22,6 @@ 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.repl.GenericReplCompiler
import org.jetbrains.kotlin.cli.jvm.repl.compileAndEval
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.daemon.common.DummyProfiler
@@ -40,12 +39,10 @@ open class KotlinJvmReplService(
disposable: Disposable,
templateClasspath: List<File>,
templateClassName: String,
scriptArgs: Array<Any?>?,
scriptArgsTypes: Array<Class<*>>?,
protected val fallbackScriptArgs: ScriptArgsWithTypes?,
compilerOutputStreamProxy: RemoteOutputStream,
val operationsTracer: RemoteOperationsTracer?
) : ReplCompiler, ReplEvaluator {
protected val operationsTracer: RemoteOperationsTracer?
) : ReplCompileAction, ReplAtomicEvalAction, ReplCheckAction, ReplEvaluatorExposedInternalHistory {
protected val compilerMessagesStream = PrintStream(BufferedOutputStream(RemoteOutputStreamClient(compilerOutputStreamProxy, DummyProfiler()), REMOTE_STREAM_BUFFER_SIZE))
protected class KeepFirstErrorMessageCollector(compilerMessagesStream: PrintStream) : MessageCollector {
@@ -105,23 +102,24 @@ open class KotlinJvmReplService(
private val scriptDef = makeScriptDefinition(templateClasspath, templateClassName)
private val replCompiler : GenericReplCompiler? by lazy {
private val replCompiler: ReplCompiler? by lazy {
if (scriptDef == null) null
else GenericReplCompiler(disposable, scriptDef, configuration, messageCollector)
}
private val compiledEvaluator : GenericReplCompiledEvaluator by lazy {
GenericReplCompiledEvaluator(configuration.jvmClasspathRoots, null, scriptArgs, scriptArgsTypes)
private val replEvaluator: ReplFullEvaluator? by lazy {
replCompiler?.let { compiler ->
GenericReplCompilingEvaluator(compiler, configuration.jvmClasspathRoots, null, fallbackScriptArgs, ReplRepeatingMode.NONE)
}
}
override val lastEvaluatedScript: ClassWithInstance? get() = compiledEvaluator.lastEvaluatedScript
override val lastEvaluatedScripts: List<EvalHistoryType> get() = replEvaluator?.lastEvaluatedScripts ?: emptyList()
override fun check(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplCheckResult {
override fun check(codeLine: ReplCodeLine): ReplCheckResult {
operationsTracer?.before("check")
try {
return replCompiler?.check(codeLine, history)
?: ReplCheckResult.Error(history,
messageCollector.firstErrorMessage ?: "Unknown error",
return replCompiler?.check(codeLine)
?: ReplCheckResult.Error(messageCollector.firstErrorMessage ?: "Unknown error",
messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION)
}
finally {
@@ -129,11 +127,11 @@ open class KotlinJvmReplService(
}
}
override fun compile(codeLine: ReplCodeLine, history: List<ReplCodeLine>): ReplCompileResult {
override fun compile(codeLine: ReplCodeLine, verifyHistory: List<ReplCodeLine>?): ReplCompileResult {
operationsTracer?.before("compile")
try {
return replCompiler?.compile(codeLine, history)
?: ReplCompileResult.Error(history,
return replCompiler?.compile(codeLine, verifyHistory)
?: ReplCompileResult.Error(verifyHistory ?: emptyList(),
messageCollector.firstErrorMessage ?: "Unknown error",
messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION)
}
@@ -142,13 +140,13 @@ open class KotlinJvmReplService(
}
}
override fun eval(codeLine: ReplCodeLine, history: List<ReplCodeLine>, invokeWrapper: InvokeWrapper?): ReplEvalResult = synchronized(this) {
override fun compileAndEval(codeLine: ReplCodeLine, scriptArgs: ScriptArgsWithTypes?, verifyHistory: List<ReplCodeLine>?, invokeWrapper: InvokeWrapper?): ReplEvalResult {
operationsTracer?.before("eval")
try {
return replCompiler?.let { compileAndEval(it, compiledEvaluator, codeLine, history, invokeWrapper) }
?: ReplEvalResult.Error.CompileTime(history,
messageCollector.firstErrorMessage ?: "Unknown error",
messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION)
return replEvaluator?.compileAndEval(codeLine, scriptArgs ?: fallbackScriptArgs, verifyHistory, invokeWrapper)
?: ReplEvalResult.Error.CompileTime(verifyHistory ?: replEvaluator?.history ?: emptyList(),
messageCollector.firstErrorMessage ?: "Unknown error",
messageCollector.firstErrorLocation ?: CompilerMessageLocation.NO_LOCATION)
}
finally {
operationsTracer?.after("eval")
@@ -35,7 +35,6 @@ import java.io.File
import java.net.URLClassLoader
class GenericReplTest : TestCase() {
@Test
fun testReplBasics() {
@@ -45,7 +44,7 @@ class GenericReplTest : TestCase() {
listOf(File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-runtime.jar")),
"kotlin.script.templates.standard.ScriptTemplateWithArgs")
val res1 = repl.replCompiler?.check(ReplCodeLine(0, "val x ="), emptyList())
val res1 = repl.replCompiler?.check(ReplCodeLine(0, "val x ="))
TestCase.assertTrue("Unexpected check results: $res1", res1 is ReplCheckResult.Incomplete)
val codeLine0 = ReplCodeLine(0, "val l1 = listOf(1 + 2)\nl1.first()")
@@ -53,7 +52,7 @@ class GenericReplTest : TestCase() {
val res2c = res2 as? ReplCompileResult.CompiledClasses
TestCase.assertNotNull("Unexpected compile result: $res2", res2c)
val res21 = repl.compiledEvaluator.eval(codeLine0, emptyList(), res2c!!.classes, res2c.hasResult, res2c.classpathAddendum)
val res21 = repl.compiledEvaluator.eval(res2c!!)
val res21e = res21 as? ReplEvalResult.ValueResult
TestCase.assertNotNull("Unexpected eval result: $res21", res21e)
TestCase.assertEquals(3, res21e!!.value)
@@ -63,7 +62,7 @@ class GenericReplTest : TestCase() {
val res3c = res3 as? ReplCompileResult.CompiledClasses
TestCase.assertNotNull("Unexpected compile result: $res3", res3c)
val res31 = repl.compiledEvaluator.eval(codeLine1, listOf(codeLine0), res3c!!.classes, res3c.hasResult, res3c.classpathAddendum)
val res31 = repl.compiledEvaluator.eval(res3c!!)
val res31e = res31 as? ReplEvalResult.UnitResult
TestCase.assertNotNull("Unexpected eval result: $res31", res31e)
@@ -75,7 +74,7 @@ class GenericReplTest : TestCase() {
val res4c = res4 as? ReplCompileResult.CompiledClasses
TestCase.assertNotNull("Unexpected compile result: $res4", res4c)
val res41 = repl.compiledEvaluator.eval(codeLine2, emptyList(), res4c!!.classes, res4c.hasResult, res4c.classpathAddendum)
val res41 = repl.compiledEvaluator.eval(res4c!!)
val res41e = res41 as? ReplEvalResult.ValueResult
TestCase.assertNotNull("Unexpected eval result: $res41", res41e)
TestCase.assertEquals(7, res41e!!.value)
@@ -97,7 +96,7 @@ class GenericReplTest : TestCase() {
val res1c = res1 as? ReplCompileResult.CompiledClasses
TestCase.assertNotNull("Unexpected compile result: $res1", res1c)
val res11 = repl.compiledEvaluator.eval(codeLine1, emptyList(), res1c!!.classes, res1c.hasResult, res1c.classpathAddendum)
val res11 = repl.compiledEvaluator.eval(res1c!!)
val res11e = res11 as? ReplEvalResult.ValueResult
TestCase.assertNotNull("Unexpected eval result: $res11", res11e)
TestCase.assertEquals(3, res11e!!.value)
@@ -107,7 +106,7 @@ class GenericReplTest : TestCase() {
val res2c = res2 as? ReplCompileResult.CompiledClasses
TestCase.assertNotNull("Unexpected compile result: $res2", res2c)
val res21 = repl.compiledEvaluator.eval(codeLine2, listOf(codeLine1), res2c!!.classes, res2c.hasResult, res2c.classpathAddendum)
val res21 = repl.compiledEvaluator.eval(res2c!!)
val res21e = res21 as? ReplEvalResult.ValueResult
TestCase.assertNotNull("Unexpected eval result: $res21", res21e)
TestCase.assertEquals(5, res21e!!.value)
@@ -121,6 +120,8 @@ internal class TestRepl(
templateClasspath: List<File>,
templateClassName: String
) {
val emptyScriptArgs = ScriptArgsWithTypes(arrayOf(emptyArray<String>()), arrayOf(Array<String>::class))
private val configuration = KotlinTestUtils.newConfiguration(ConfigurationKind.ALL, TestJdkKind.MOCK_JDK, *templateClasspath.toTypedArray()).apply {
put(CommonConfigurationKeys.MODULE_NAME, "kotlin-script")
}
@@ -137,8 +138,8 @@ internal class TestRepl(
GenericReplCompiler(disposable, scriptDef, configuration, PrintingMessageCollector(System.out, MessageRenderer.WITHOUT_PATHS, false))
}
val compiledEvaluator : GenericReplCompiledEvaluator by lazy {
GenericReplCompiledEvaluator(configuration.jvmClasspathRoots, null, arrayOf(emptyArray<String>()))
val compiledEvaluator: ReplEvaluator by lazy {
GenericReplEvaluator(configuration.jvmClasspathRoots, null, emptyScriptArgs, ReplRepeatingMode.NONE)
}
}
@@ -486,7 +486,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
ScriptWithNoParam::class.qualifiedName!!,
System.err)
val localEvaluator = GenericReplCompiledEvaluator(emptyList(), Thread.currentThread().contextClassLoader)
val localEvaluator = GenericReplEvaluator(emptyList(), Thread.currentThread().contextClassLoader)
doReplTestWithLocalEval(repl, localEvaluator)
}
@@ -501,17 +501,16 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
"kotlin.script.templates.standard.ScriptTemplateWithArgs",
System.err)
val localEvaluator = GenericReplCompiledEvaluator(emptyList(),
Thread.currentThread().contextClassLoader,
arrayOf(emptyArray<String>()))
val localEvaluator = GenericReplEvaluator(emptyList(), Thread.currentThread().contextClassLoader,
ScriptArgsWithTypes(arrayOf(emptyArray<String>()), arrayOf(Array<String>::class)))
doReplTestWithLocalEval(repl, localEvaluator)
}
}
}
private fun doReplTestWithLocalEval(repl: KotlinRemoteReplCompiler, localEvaluator: GenericReplCompiledEvaluator) {
val res0 = repl.check(ReplCodeLine(0, "val x ="), emptyList())
private fun doReplTestWithLocalEval(repl: KotlinRemoteReplCompiler, localEvaluator: ReplEvaluator) {
val res0 = repl.check(ReplCodeLine(0, "val x ="))
TestCase.assertTrue("Unexpected check results: $res0", res0 is ReplCheckResult.Incomplete)
val codeLine1 = ReplCodeLine(1, "val lst = listOf(1)\nval x = 5")
@@ -519,7 +518,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
val res1c = res1 as? ReplCompileResult.CompiledClasses
TestCase.assertNotNull("Unexpected compile result: $res1", res1c)
val res11 = localEvaluator.eval(codeLine1, emptyList(), res1c!!.classes, res1c.hasResult, res1c.classpathAddendum)
val res11 = localEvaluator.eval(res1c!!)
val res11e = res11 as? ReplEvalResult.UnitResult
TestCase.assertNotNull("Unexpected eval result: $res11", res11e)
@@ -531,7 +530,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
val res2c = res2 as? ReplCompileResult.CompiledClasses
TestCase.assertNotNull("Unexpected compile result: $res2", res2c)
val res21 = localEvaluator.eval(codeLine2, listOf(codeLine1), res2c!!.classes, res2c.hasResult, res2c.classpathAddendum)
val res21 = localEvaluator.eval(res2c!!)
val res21e = res21 as? ReplEvalResult.ValueResult
TestCase.assertNotNull("Unexpected eval result: $res21", res21e)
TestCase.assertEquals(7, res21e!!.value)
@@ -548,22 +547,23 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
val repl = KotlinRemoteReplEvaluator(disposable, daemon!!, null, CompileService.TargetPlatform.JVM,
listOf(File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-runtime.jar")),
"kotlin.script.templates.standard.ScriptTemplateWithArgs",
arrayOf(emptyArray<String>()), null,
arrayOf(emptyArray<String>()),
arrayOf(Array<String>::class.java),
System.err, evalOut, evalErr, evalIn)
val res0 = repl.check(ReplCodeLine(0, "val x ="), emptyList())
val res0 = repl.check(ReplCodeLine(0, "val x ="))
TestCase.assertTrue("Unexpected check results: $res0", res0 is ReplCheckResult.Incomplete)
val codeLine1 = ReplCodeLine(1, "val x = 5")
val res1 = repl.eval(codeLine1, emptyList())
val res1 = repl.compileAndEval(codeLine1, verifyHistory = emptyList())
val res1e = res1 as? ReplEvalResult.UnitResult
TestCase.assertNotNull("Unexpected eval result: $res1", res1e)
val codeLine2 = ReplCodeLine(2, "x + 2")
val res2x = repl.eval(codeLine2, listOf(codeLine2))
val res2x = repl.compileAndEval(codeLine2, verifyHistory = listOf(codeLine2))
TestCase.assertNotNull("Unexpected compile result: $res2x", res2x as? ReplEvalResult.HistoryMismatch)
val res2 = repl.eval(codeLine2, listOf(codeLine1))
val res2 = repl.compileAndEval(codeLine2, verifyHistory = listOf(codeLine1))
val res2e = res2 as? ReplEvalResult.ValueResult
TestCase.assertNotNull("Unexpected eval result: $res2", res2e)
TestCase.assertEquals(7, res2e!!.value)
@@ -592,8 +592,8 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() {
// use repl compiler for >> 1s, making sure that idle/unused timeouts are not firing
for (attempts in 1..10) {
val codeLine1 = ReplCodeLine(1, "3 + 5")
val res1 = repl.compile(codeLine1, emptyList())
val codeLine1 = ReplCodeLine(attempts, "3 + 5")
val res1 = repl.compile(codeLine1)
val res1c = res1 as? ReplCompileResult.CompiledClasses
TestCase.assertNotNull("Unexpected compile result: $res1", res1c)
Thread.sleep(200)
@@ -17,10 +17,7 @@
package org.jetbrains.kotlin.jsr223
import com.intellij.openapi.Disposable
import org.jetbrains.kotlin.cli.common.repl.GenericReplCompiledEvaluator
import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineBase
import org.jetbrains.kotlin.cli.common.repl.ReplCompiledEvaluator
import org.jetbrains.kotlin.cli.common.repl.ReplCompiler
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.daemon.client.DaemonReportMessage
import org.jetbrains.kotlin.daemon.client.DaemonReportingTargets
import org.jetbrains.kotlin.daemon.client.KotlinCompilerClient
@@ -31,14 +28,15 @@ import java.io.File
import javax.script.ScriptContext
import javax.script.ScriptEngineFactory
import javax.script.ScriptException
import kotlin.reflect.KClass
class KotlinJsr223JvmScriptEngine4Idea(
disposable: Disposable,
factory: ScriptEngineFactory,
templateClasspath: List<File>,
templateClassName: String,
getScriptArgs: (ScriptContext) -> Array<Any?>?,
scriptArgsTypes: Array<Class<*>>?
private val getScriptArgs: (ScriptContext, Array<out KClass<out Any>>?) -> ScriptArgsWithTypes?,
private val scriptArgsTypes: Array<out KClass<out Any>>?
) : KotlinJsr223JvmScriptEngineBase(factory) {
private val daemon by lazy {
@@ -66,8 +64,10 @@ class KotlinJsr223JvmScriptEngine4Idea(
}
}
// 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: ReplCompiledEvaluator by lazy { GenericReplCompiledEvaluator(templateClasspath, Thread.currentThread().contextClassLoader, getScriptArgs(getContext()), scriptArgsTypes) }
override fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? =
getScriptArgs(getContext(), scriptArgsTypes)
override val replEvaluator: ReplCompiledEvaluator get() = localEvaluator
val localEvaluator: ReplFullEvaluator by lazy { GenericReplCompilingEvaluator(replCompiler, templateClasspath, Thread.currentThread().contextClassLoader) }
override val replScriptEvaluator: ReplFullEvaluator get() = localEvaluator
}
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.jsr223
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineFactoryBase
import org.jetbrains.kotlin.cli.common.repl.ScriptArgsWithTypes
import org.jetbrains.kotlin.utils.PathUtil
import org.jetbrains.kotlin.utils.PathUtil.KOTLIN_JAVA_RUNTIME_JAR
import org.jetbrains.kotlin.utils.PathUtil.KOTLIN_JAVA_SCRIPT_RUNTIME_JAR
@@ -37,8 +38,8 @@ class KotlinJsr223StandardScriptEngineFactory4Idea : KotlinJsr223JvmScriptEngine
this,
scriptCompilationClasspathFromContext(Thread.currentThread().contextClassLoader),
"kotlin.script.templates.standard.ScriptTemplateWithBindings",
{ ctx -> arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)) },
arrayOf(Map::class.java)
{ ctx, argTypes -> ScriptArgsWithTypes(arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)), argTypes ?: emptyArray()) },
arrayOf(Map::class)
)
}
@@ -28,6 +28,7 @@ import java.io.OutputStream
import javax.script.ScriptContext
import javax.script.ScriptEngineFactory
import javax.script.ScriptException
import kotlin.reflect.KClass
class KotlinJsr223JvmDaemonCompileScriptEngine(
disposable: Disposable,
@@ -35,8 +36,8 @@ class KotlinJsr223JvmDaemonCompileScriptEngine(
compilerJar: File,
templateClasspath: List<File>,
templateClassName: String,
getScriptArgs: (ScriptContext) -> Array<Any?>?,
scriptArgsTypes: Array<Class<*>>?,
getScriptArgs: (ScriptContext, Array<out KClass<out Any>>?) -> ScriptArgsWithTypes?,
scriptArgsTypes: Array<out KClass<out Any>>?,
compilerOut: OutputStream = System.err
) : KotlinJsr223JvmScriptEngineBase(factory), KotlinJsr223JvmInvocableScriptEngine {
@@ -56,20 +57,18 @@ class KotlinJsr223JvmDaemonCompileScriptEngine(
}
// 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) }
val localEvaluator by lazy { GenericReplCompilingEvaluator(replCompiler, templateClasspath, Thread.currentThread().contextClassLoader, getScriptArgs(getContext(), scriptArgsTypes)) }
override val replEvaluator: ReplCompiledEvaluator get() = localEvaluator
override val replScriptEvaluator: ReplEvaluatorBase get() = localEvaluator
}
override val replScriptEvaluator: ReplFullEvaluator get() = localEvaluator
private fun connectToCompileService(compilerJar: File): CompileService {
val compilerId = CompilerId.makeCompilerId(compilerJar)
val daemonOptions = configureDaemonOptions()
val daemonJVMOptions = DaemonJVMOptions()
private fun connectToCompileService(compilerJar: File): CompileService {
val compilerId = CompilerId.makeCompilerId(compilerJar)
val daemonOptions = configureDaemonOptions()
val daemonJVMOptions = DaemonJVMOptions()
val daemonReportMessages = arrayListOf<DaemonReportMessage>()
val daemonReportMessages = arrayListOf<DaemonReportMessage>()
return 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}" })
}
return 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}" })
}
}
@@ -31,14 +31,15 @@ import java.io.File
import java.net.URLClassLoader
import javax.script.ScriptContext
import javax.script.ScriptEngineFactory
import kotlin.reflect.KClass
class KotlinJsr223JvmLocalScriptEngine(
disposable: Disposable,
factory: ScriptEngineFactory,
val templateClasspath: List<File>,
templateClassName: String,
getScriptArgs: (ScriptContext) -> Array<Any?>?,
scriptArgsTypes: Array<Class<*>>?
getScriptArgs: (ScriptContext, Array<out KClass<out Any>>?) -> ScriptArgsWithTypes?,
scriptArgsTypes: Array<out KClass<out Any>>?
) : KotlinJsr223JvmScriptEngineBase(factory), KotlinJsr223JvmInvocableScriptEngine {
override val replCompiler: ReplCompiler by lazy {
@@ -49,10 +50,9 @@ class KotlinJsr223JvmLocalScriptEngine(
PrintingMessageCollector(System.out, MessageRenderer.WITHOUT_PATHS, false))
}
// 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 val localEvaluator by lazy { GenericReplCompilingEvaluator(replCompiler, templateClasspath, Thread.currentThread().contextClassLoader, getScriptArgs(getContext(), scriptArgsTypes)) }
override val replEvaluator: ReplCompiledEvaluator get() = localEvaluator
override val replScriptEvaluator: ReplEvaluatorBase get() = localEvaluator
override val replScriptEvaluator: ReplFullEvaluator get() = localEvaluator
private fun makeScriptDefinition(templateClasspath: List<File>, templateClassName: String): KotlinScriptDefinition {
val classloader = URLClassLoader(templateClasspath.map { it.toURI().toURL() }.toTypedArray(), this.javaClass.classLoader)
@@ -20,6 +20,7 @@ package org.jetbrains.kotlin.script.jsr223
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineFactoryBase
import org.jetbrains.kotlin.cli.common.repl.ScriptArgsWithTypes
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.script.util.classpathFromClass
import org.jetbrains.kotlin.script.util.classpathFromClassloader
@@ -40,8 +41,8 @@ class KotlinJsr223JvmLocalScriptEngineFactory : KotlinJsr223JvmScriptEngineFacto
this,
scriptCompilationClasspathFromContext(),
"kotlin.script.templates.standard.ScriptTemplateWithBindings",
{ ctx -> arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)) },
arrayOf(Map::class.java)
{ ctx, types -> ScriptArgsWithTypes(arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)), types ?: emptyArray()) },
arrayOf(Map::class)
)
}
@@ -54,33 +55,15 @@ class KotlinJsr223JvmDaemonLocalEvalScriptEngineFactory : KotlinJsr223JvmScriptE
kotlinCompilerJar,
scriptCompilationClasspathFromContext(),
"kotlin.script.templates.standard.ScriptTemplateWithBindings",
{ ctx -> arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)) },
arrayOf(Map::class.java)
{ ctx, types -> ScriptArgsWithTypes(arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)), types ?: emptyArray()) },
arrayOf(Map::class)
)
}
private fun makeSerializableArgumentsForTemplateWithBindings(ctx: ScriptContext): Array<Any?> {
val bindings = ctx.getBindings(ScriptContext.ENGINE_SCOPE)
val serializableBindings = linkedMapOf<String, Any>()
// TODO: consider deeper analysis and copying to serializable data if possible
serializableBindings.putAll(bindings)
return arrayOf(serializableBindings)
}
private fun File.existsOrNull(): File? = existsAndCheckOrNull { true }
private inline fun File.existsAndCheckOrNull(check: (File.() -> Boolean)): File? = if (exists() && check()) this else null
private val kotlinCompilerJar: File by lazy {
// highest prio - explicit property
System.getProperty("kotlin.compiler.jar")?.let(::File)?.existsOrNull()
// search classpath from context classloader and `java.class.path` property
?: (classpathFromClass(Thread.currentThread().contextClassLoader, K2JVMCompiler::class)
?: contextClasspath(KOTLIN_COMPILER_JAR, Thread.currentThread().contextClassLoader)
?: classpathFromClasspathProperty()
)?.firstOrNull { it.matchMaybeVersionedFile(KOTLIN_COMPILER_JAR) }
?: throw FileNotFoundException("Cannot find kotlin compiler jar, set kotlin.compiler.jar property to proper location")
}
private fun <T> Iterable<T>.anyOrNull(predicate: (T) -> Boolean) = if (any(predicate)) this else null
private fun File.matchMaybeVersionedFile(baseName: String) =
@@ -89,20 +72,30 @@ private fun File.matchMaybeVersionedFile(baseName: String) =
name.startsWith(baseName.removeSuffix(".jar") + "-")
private fun contextClasspath(keyName: String, classLoader: ClassLoader): List<File>? =
( classpathFromClassloader(classLoader)?.anyOrNull { it.matchMaybeVersionedFile(keyName) }
?: manifestClassPath(classLoader)?.anyOrNull { it.matchMaybeVersionedFile(keyName) }
(classpathFromClassloader(classLoader)?.anyOrNull { it.matchMaybeVersionedFile(keyName) }
?: manifestClassPath(classLoader)?.anyOrNull { it.matchMaybeVersionedFile(keyName) }
)?.toList()
private fun scriptCompilationClasspathFromContext(classLoader: ClassLoader = Thread.currentThread().contextClassLoader): List<File> =
( System.getProperty("kotlin.script.classpath")?.split(File.pathSeparator)?.map(::File)
?: contextClasspath(KOTLIN_JAVA_RUNTIME_JAR, classLoader)
?: listOf(kotlinRuntimeJar, kotlinScriptRuntimeJar)
(System.getProperty("kotlin.script.classpath")?.split(File.pathSeparator)?.map(::File)
?: contextClasspath(KOTLIN_JAVA_RUNTIME_JAR, classLoader)
?: listOf(kotlinRuntimeJar, kotlinScriptRuntimeJar)
)
.map { it?.canonicalFile }
.distinct()
.mapNotNull { it?.existsOrNull() }
.map { it?.canonicalFile }
.distinct()
.mapNotNull { it?.existsOrNull() }
private val kotlinCompilerJar: File by lazy {
// highest prio - explicit property
System.getProperty("kotlin.compiler.jar")?.let(::File)?.existsOrNull()
// search classpath from context classloader and `java.class.path` property
?: (classpathFromClass(Thread.currentThread().contextClassLoader, K2JVMCompiler::class)
?: contextClasspath(KOTLIN_COMPILER_JAR, Thread.currentThread().contextClassLoader)
?: classpathFromClasspathProperty()
)?.firstOrNull { it.matchMaybeVersionedFile(KOTLIN_COMPILER_JAR) }
?: throw FileNotFoundException("Cannot find kotlin compiler jar, set kotlin.compiler.jar property to proper location")
}
private val kotlinRuntimeJar: File? by lazy {
System.getProperty("kotlin.java.runtime.jar")?.let(::File)?.existsOrNull()
@@ -117,3 +110,4 @@ private val kotlinScriptRuntimeJar: File? by lazy {
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
* Copyright 2010-2017 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
* Copyright 2010-2017 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
* Copyright 2010-2017 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
* Copyright 2010-2017 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
* Copyright 2010-2017 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.