Implement JSR 223 script template with eval functions, some fixes
This commit is contained in:
@@ -55,7 +55,7 @@ open class BasicReplStageHistory<T>(override val lock: ReentrantReadWriteLock =
|
||||
if (idx < 0) throw java.util.NoSuchElementException("Cannot rest to inexistent line ${id.no}")
|
||||
if (idx < lastIndex) {
|
||||
val removed = asSequence().drop(idx + 1).map { it.id }.toList()
|
||||
removeRange(idx + 1, lastIndex)
|
||||
removeRange(idx + 1, size)
|
||||
currentGeneration.incrementAndGet()
|
||||
return removed
|
||||
}
|
||||
|
||||
+3
-1
@@ -46,7 +46,9 @@ class GenericReplCompilingEvaluator(val compiler: ReplCompiler,
|
||||
aggregatedState.apply {
|
||||
lock.write {
|
||||
if (state1.history.size > state2.history.size) {
|
||||
state2.history.peek()?.let { state1.history.resetTo(it.id) }
|
||||
state2.history.peek()?.let {
|
||||
state1.history.resetTo(it.id)
|
||||
}
|
||||
assert(state1.history.size == state2.history.size)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-4
@@ -21,7 +21,8 @@ import java.io.Reader
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import javax.script.*
|
||||
|
||||
val KOTLIN_SCRIPT_STATE_BINDINGS_KEY = "kotlin.script.state"
|
||||
const val KOTLIN_SCRIPT_STATE_BINDINGS_KEY = "kotlin.script.state"
|
||||
const val KOTLIN_SCRIPT_ENGINE_BINDINGS_KEY = "kotlin.script.engine"
|
||||
|
||||
abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEngineFactory) : AbstractScriptEngine(), ScriptEngine, Compilable {
|
||||
|
||||
@@ -36,16 +37,22 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn
|
||||
|
||||
override fun compile(script: Reader): CompiledScript = compile(script.readText(), getContext())
|
||||
|
||||
override fun createBindings(): Bindings = SimpleBindings()
|
||||
override fun createBindings(): Bindings = SimpleBindings().apply { put(KOTLIN_SCRIPT_ENGINE_BINDINGS_KEY, this) }
|
||||
|
||||
override fun getFactory(): ScriptEngineFactory = myFactory
|
||||
|
||||
// the parameter could be used in the future when we decide to keep state completely in the context and solve appropriate problems (now e.g. replCompiler keeps separate state)
|
||||
fun nextCodeLine(context: ScriptContext, code: String) = getCurrentState(context).let { ReplCodeLine(it.getNextLineNo(), it.currentGeneration, code) }
|
||||
|
||||
protected open fun createState(lock: ReentrantReadWriteLock = ReentrantReadWriteLock()): IReplStageState<*> = AggregatedReplStageState(replCompiler.createState(lock), replEvaluator.createState(lock), lock)
|
||||
protected abstract fun createState(lock: ReentrantReadWriteLock = ReentrantReadWriteLock()): IReplStageState<*>
|
||||
|
||||
protected fun getCurrentState(context: ScriptContext) = context.getBindings(ScriptContext.ENGINE_SCOPE).getOrPut(KOTLIN_SCRIPT_STATE_BINDINGS_KEY, { replEvaluator.createState() }) as IReplStageState<*>
|
||||
protected fun getCurrentState(context: ScriptContext) =
|
||||
context.getBindings(ScriptContext.ENGINE_SCOPE)
|
||||
.getOrPut(KOTLIN_SCRIPT_STATE_BINDINGS_KEY, {
|
||||
// TODO: check why createBinding is not called on creating default context, so the engine is not set
|
||||
context.getBindings(ScriptContext.ENGINE_SCOPE).put(KOTLIN_SCRIPT_ENGINE_BINDINGS_KEY, this@KotlinJsr223JvmScriptEngineBase)
|
||||
createState()
|
||||
}) as IReplStageState<*>
|
||||
|
||||
open fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = null
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.daemon.client.KotlinRemoteReplCompilerClient
|
||||
import org.jetbrains.kotlin.daemon.common.*
|
||||
import org.jetbrains.kotlin.utils.PathUtil
|
||||
import java.io.File
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import javax.script.ScriptContext
|
||||
import javax.script.ScriptEngineFactory
|
||||
import javax.script.ScriptException
|
||||
@@ -76,6 +77,8 @@ class KotlinJsr223JvmScriptEngine4Idea(
|
||||
|
||||
override val replEvaluator: ReplFullEvaluator get() = localEvaluator
|
||||
|
||||
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = replEvaluator.createState(lock)
|
||||
|
||||
private class MyMessageCollector : MessageCollector {
|
||||
|
||||
var _hasErrors: Boolean = false
|
||||
|
||||
+11
@@ -111,6 +111,17 @@ obj
|
||||
})
|
||||
Assert.assertEquals(111, result2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimpleEvalInEval() {
|
||||
val engine = ScriptEngineManager().getEngineByExtension("kts")!!
|
||||
val res1 = engine.eval("val x = 3")
|
||||
Assert.assertNull(res1)
|
||||
val res2 = engine.eval("eval(\"\$x + 2\")")
|
||||
Assert.assertEquals(5, res2)
|
||||
val res3 = engine.eval("x + 4")
|
||||
Assert.assertEquals(7, res3)
|
||||
}
|
||||
}
|
||||
|
||||
fun assertThrows(exceptionClass: Class<*>, body: () -> Unit) {
|
||||
|
||||
+11
@@ -111,6 +111,17 @@ obj
|
||||
})
|
||||
Assert.assertEquals(111, result2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimpleEvalInEval() {
|
||||
val engine = ScriptEngineManager().getEngineByExtension("kts")!!
|
||||
val res1 = engine.eval("val x = 3")
|
||||
Assert.assertNull(res1)
|
||||
val res2 = engine.eval("eval(\"\$x + 2\")")
|
||||
Assert.assertEquals(5, res2)
|
||||
val res3 = engine.eval("x + 4")
|
||||
Assert.assertEquals(7, res3)
|
||||
}
|
||||
}
|
||||
|
||||
fun assertThrows(exceptionClass: Class<*>, body: () -> Unit) {
|
||||
|
||||
+3
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.daemon.common.*
|
||||
import java.io.File
|
||||
import java.io.OutputStream
|
||||
import java.io.PrintStream
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import javax.script.ScriptContext
|
||||
import javax.script.ScriptEngineFactory
|
||||
import javax.script.ScriptException
|
||||
@@ -68,6 +69,8 @@ class KotlinJsr223JvmDaemonCompileScriptEngine(
|
||||
|
||||
override val state: IReplStageState<*> get() = getCurrentState(getContext())
|
||||
|
||||
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = replEvaluator.createState(lock)
|
||||
|
||||
override fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = getScriptArgs(context, scriptArgsTypes)
|
||||
|
||||
private fun connectToCompileService(compilerJar: File): CompileService {
|
||||
|
||||
+3
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate
|
||||
import org.jetbrains.kotlin.utils.PathUtil
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import javax.script.ScriptContext
|
||||
import javax.script.ScriptEngineFactory
|
||||
import kotlin.reflect.KClass
|
||||
@@ -56,6 +57,8 @@ class KotlinJsr223JvmLocalScriptEngine(
|
||||
|
||||
override val state: IReplStageState<*> get() = getCurrentState(getContext())
|
||||
|
||||
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = replEvaluator.createState(lock)
|
||||
|
||||
override fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = getScriptArgs(context, scriptArgsTypes)
|
||||
|
||||
private fun makeScriptDefinition(templateClasspath: List<File>, templateClassName: String): KotlinScriptDefinition {
|
||||
|
||||
+9
-8
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.script.util.manifestClassPath
|
||||
import org.jetbrains.kotlin.utils.PathUtil.*
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import javax.script.Bindings
|
||||
import javax.script.ScriptContext
|
||||
import javax.script.ScriptEngine
|
||||
import kotlin.script.templates.standard.ScriptTemplateWithArgs
|
||||
@@ -39,10 +40,10 @@ class KotlinJsr223JvmLocalScriptEngineFactory : KotlinJsr223JvmScriptEngineFacto
|
||||
KotlinJsr223JvmLocalScriptEngine(
|
||||
Disposer.newDisposable(),
|
||||
this,
|
||||
scriptCompilationClasspathFromContext(),
|
||||
"kotlin.script.templates.standard.ScriptTemplateWithBindings",
|
||||
scriptCompilationClasspathFromContext("kotlin-script-util.jar"),
|
||||
KotlinStandardJsr223ScriptTemplate::class.qualifiedName!!,
|
||||
{ ctx, types -> ScriptArgsWithTypes(arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)), types ?: emptyArray()) },
|
||||
arrayOf(Map::class)
|
||||
arrayOf(Bindings::class)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -53,10 +54,10 @@ class KotlinJsr223JvmDaemonLocalEvalScriptEngineFactory : KotlinJsr223JvmScriptE
|
||||
Disposer.newDisposable(),
|
||||
this,
|
||||
kotlinCompilerJar,
|
||||
scriptCompilationClasspathFromContext(),
|
||||
"kotlin.script.templates.standard.ScriptTemplateWithBindings",
|
||||
scriptCompilationClasspathFromContext("kotlin-script-util.jar"),
|
||||
KotlinStandardJsr223ScriptTemplate::class.qualifiedName!!,
|
||||
{ ctx, types -> ScriptArgsWithTypes(arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)), types ?: emptyArray()) },
|
||||
arrayOf(Map::class)
|
||||
arrayOf(Bindings::class)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -77,9 +78,9 @@ private fun contextClasspath(keyName: String, classLoader: ClassLoader): List<Fi
|
||||
)?.toList()
|
||||
|
||||
|
||||
private fun scriptCompilationClasspathFromContext(classLoader: ClassLoader = Thread.currentThread().contextClassLoader): List<File> =
|
||||
private fun scriptCompilationClasspathFromContext(keyName: String, classLoader: ClassLoader = Thread.currentThread().contextClassLoader): List<File> =
|
||||
(System.getProperty("kotlin.script.classpath")?.split(File.pathSeparator)?.map(::File)
|
||||
?: classpathFromClassloader(classLoader)
|
||||
?: contextClasspath(keyName, classLoader)
|
||||
).let {
|
||||
it?.plus(kotlinScriptStandardJars) ?: kotlinScriptStandardJars
|
||||
}
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package org.jetbrains.kotlin.script.jsr223
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.repl.KOTLIN_SCRIPT_ENGINE_BINDINGS_KEY
|
||||
import org.jetbrains.kotlin.cli.common.repl.KOTLIN_SCRIPT_STATE_BINDINGS_KEY
|
||||
import org.jetbrains.kotlin.script.ScriptTemplateDefinition
|
||||
import javax.script.Bindings
|
||||
import javax.script.ScriptEngine
|
||||
|
||||
@Suppress("unused")
|
||||
@ScriptTemplateDefinition
|
||||
abstract class KotlinStandardJsr223ScriptTemplate(val bindings: Bindings) {
|
||||
|
||||
private val myEngine: ScriptEngine? get() = bindings[KOTLIN_SCRIPT_ENGINE_BINDINGS_KEY]?.let { it as? ScriptEngine }
|
||||
|
||||
private inline fun<T> withMyEngine(body: (ScriptEngine) -> T): T =
|
||||
myEngine?.let(body) ?: throw IllegalStateException("Script engine for `eval` call is not found")
|
||||
|
||||
fun eval(script: String, newBindings: Bindings): Any? =
|
||||
withMyEngine {
|
||||
val savedState = newBindings[KOTLIN_SCRIPT_STATE_BINDINGS_KEY]?.takeIf { it === this.bindings[KOTLIN_SCRIPT_STATE_BINDINGS_KEY] }?.apply {
|
||||
newBindings[KOTLIN_SCRIPT_STATE_BINDINGS_KEY] = null
|
||||
}
|
||||
val res = it.eval(script, newBindings)
|
||||
savedState?.apply {
|
||||
newBindings[KOTLIN_SCRIPT_STATE_BINDINGS_KEY] = savedState
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fun eval(script: String): Any? =
|
||||
withMyEngine {
|
||||
val savedState = bindings.remove(KOTLIN_SCRIPT_STATE_BINDINGS_KEY)
|
||||
val res = it.eval(script, bindings)
|
||||
savedState?.apply {
|
||||
bindings[KOTLIN_SCRIPT_STATE_BINDINGS_KEY] = savedState
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fun createBindings(): Bindings = withMyEngine { it.createBindings() }
|
||||
}
|
||||
Reference in New Issue
Block a user