Reuse script args substitution for replacing bindings in JSR 223 sample engines, fixes KT-15450
This commit is contained in:
+22
-12
@@ -22,18 +22,20 @@ import java.util.concurrent.atomic.AtomicInteger
|
|||||||
import javax.script.*
|
import javax.script.*
|
||||||
|
|
||||||
val KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY = "kotlin.script.history"
|
val KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY = "kotlin.script.history"
|
||||||
|
val KOTLIN_SCRIPT_LINE_NUMBER_BINDINGS_KEY = "kotlin.script.line.number"
|
||||||
|
|
||||||
// TODO consider additional error handling
|
// TODO consider additional error handling
|
||||||
val Bindings.kotlinScriptHistory: MutableList<ReplCodeLine>
|
var Bindings.kotlinScriptHistory: MutableList<ReplCodeLine>
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
get() = getOrPut(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, { arrayListOf<ReplCodeLine>() }) as MutableList<ReplCodeLine>
|
get() = getOrPut(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, { arrayListOf<ReplCodeLine>() }) as MutableList<ReplCodeLine>
|
||||||
|
set(v) { put(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, v) }
|
||||||
|
|
||||||
|
val Bindings.kotlinScriptLineNumber: AtomicInteger
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
get() = getOrPut(KOTLIN_SCRIPT_LINE_NUMBER_BINDINGS_KEY, { AtomicInteger(0) }) as AtomicInteger
|
||||||
|
|
||||||
abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEngineFactory) : AbstractScriptEngine(), ScriptEngine, Compilable {
|
abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEngineFactory) : AbstractScriptEngine(), ScriptEngine, Compilable {
|
||||||
|
|
||||||
protected var codeLineNumber = AtomicInteger(0)
|
|
||||||
|
|
||||||
protected abstract val replCompiler: ReplCompileAction
|
protected abstract val replCompiler: ReplCompileAction
|
||||||
protected abstract val replScriptEvaluator: ReplFullEvaluator
|
protected abstract val replScriptEvaluator: ReplFullEvaluator
|
||||||
|
|
||||||
@@ -49,13 +51,20 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn
|
|||||||
|
|
||||||
override fun getFactory(): ScriptEngineFactory = myFactory
|
override fun getFactory(): ScriptEngineFactory = myFactory
|
||||||
|
|
||||||
fun nextCodeLine(code: String) = ReplCodeLine(codeLineNumber.incrementAndGet(), code)
|
// 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(@Suppress("UNUSED_PARAMETER") context: ScriptContext, code: String) = ReplCodeLine(this.context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptLineNumber.incrementAndGet(), code)
|
||||||
|
|
||||||
|
private fun getCurrentHistory(@Suppress("UNUSED_PARAMETER") context: ScriptContext) = this.context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory
|
||||||
|
|
||||||
|
private fun setContextHistory(@Suppress("UNUSED_PARAMETER") context: ScriptContext, history: ArrayList<ReplCodeLine>) {
|
||||||
|
this.context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory = history
|
||||||
|
}
|
||||||
|
|
||||||
open fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = null
|
open fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = null
|
||||||
|
|
||||||
open fun compileAndEval(script: String, context: ScriptContext): Any? {
|
open fun compileAndEval(script: String, context: ScriptContext): Any? {
|
||||||
val codeLine = nextCodeLine(script)
|
val codeLine = nextCodeLine(context, script)
|
||||||
val history = context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory
|
val history = getCurrentHistory(context)
|
||||||
val result = replScriptEvaluator.compileAndEval(codeLine, scriptArgs = overrideScriptArgs(context), verifyHistory = history)
|
val result = replScriptEvaluator.compileAndEval(codeLine, scriptArgs = overrideScriptArgs(context), verifyHistory = history)
|
||||||
val ret = when (result) {
|
val ret = when (result) {
|
||||||
is ReplEvalResult.ValueResult -> result.value
|
is ReplEvalResult.ValueResult -> result.value
|
||||||
@@ -64,13 +73,13 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn
|
|||||||
is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code")
|
is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code")
|
||||||
is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}")
|
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))
|
setContextHistory(context, ArrayList(result.completedEvalHistory))
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun compile(script: String, context: ScriptContext): CompiledScript {
|
open fun compile(script: String, context: ScriptContext): CompiledScript {
|
||||||
val codeLine = nextCodeLine(script)
|
val codeLine = nextCodeLine(context, script)
|
||||||
val history = context.getBindings(ScriptContext.ENGINE_SCOPE).kotlinScriptHistory
|
val history = getCurrentHistory(context)
|
||||||
|
|
||||||
val result = replCompiler.compile(codeLine, history)
|
val result = replCompiler.compile(codeLine, history)
|
||||||
val compiled = when (result) {
|
val compiled = when (result) {
|
||||||
@@ -79,7 +88,8 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn
|
|||||||
is ReplCompileResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}")
|
is ReplCompileResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}")
|
||||||
is ReplCompileResult.CompiledClasses -> result
|
is ReplCompileResult.CompiledClasses -> result
|
||||||
}
|
}
|
||||||
context.getBindings(ScriptContext.ENGINE_SCOPE).put(KOTLIN_SCRIPT_HISTORY_BINDINGS_KEY, ArrayList(result.compiledHistory))
|
// TODO: check if it is ok to keep compiled history in the same place as compiledEval one
|
||||||
|
setContextHistory(context, ArrayList(result.compiledHistory))
|
||||||
return CompiledKotlinScript(this, codeLine, compiled)
|
return CompiledKotlinScript(this, codeLine, compiled)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +108,7 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn
|
|||||||
is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code")
|
is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code")
|
||||||
is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${result.lineNo}")
|
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))
|
setContextHistory(context, ArrayList(result.completedEvalHistory))
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ open class GenericReplCompiler(disposable: Disposable,
|
|||||||
|
|
||||||
removedCompiledLines.zip(removedAnalyzedLines).forEach {
|
removedCompiledLines.zip(removedAnalyzedLines).forEach {
|
||||||
if (it.first.first != it.second) {
|
if (it.first.first != it.second) {
|
||||||
throw IllegalStateException("History mistmatch when resetting lines")
|
throw IllegalStateException("History mismatch when resetting lines")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
@@ -92,6 +92,25 @@ obj
|
|||||||
val res3 = invocator!!.invokeMethod(res1, "fn1", 3)
|
val res3 = invocator!!.invokeMethod(res1, "fn1", 3)
|
||||||
Assert.assertEquals(6, res3)
|
Assert.assertEquals(6, res3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testEvalWithContext() {
|
||||||
|
val engine = ScriptEngineManager().getEngineByExtension("kts")!!
|
||||||
|
|
||||||
|
engine.put("z", 33)
|
||||||
|
|
||||||
|
engine.eval("""val x = 10 + bindings["z"] as Int""")
|
||||||
|
|
||||||
|
val result = engine.eval("""x + 20""")
|
||||||
|
Assert.assertEquals(63, result)
|
||||||
|
|
||||||
|
// in the current implementation the history is shared between contexts, so "x" could also be used in this line,
|
||||||
|
// but this behaviour probably will not be preserved in the future, since contexts may become completely isolated
|
||||||
|
val result2 = engine.eval("""11 + bindings["boundValue"] as Int""", engine.createBindings().apply {
|
||||||
|
put("boundValue", 100)
|
||||||
|
})
|
||||||
|
Assert.assertEquals(111, result2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertThrows(exceptionClass: Class<*>, body: () -> Unit) {
|
fun assertThrows(exceptionClass: Class<*>, body: () -> Unit) {
|
||||||
|
|||||||
+19
@@ -92,6 +92,25 @@ obj
|
|||||||
val res3 = invocator!!.invokeMethod(res1, "fn1", 3)
|
val res3 = invocator!!.invokeMethod(res1, "fn1", 3)
|
||||||
Assert.assertEquals(6, res3)
|
Assert.assertEquals(6, res3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testEvalWithContext() {
|
||||||
|
val engine = ScriptEngineManager().getEngineByExtension("kts")!!
|
||||||
|
|
||||||
|
engine.put("z", 33)
|
||||||
|
|
||||||
|
engine.eval("""val x = 10 + bindings["z"] as Int""")
|
||||||
|
|
||||||
|
val result = engine.eval("""x + 20""")
|
||||||
|
Assert.assertEquals(63, result)
|
||||||
|
|
||||||
|
// in the current implementation the history is shared between contexts, so "x" could also be used in this line,
|
||||||
|
// but this behaviour probably will not be preserved in the future, since contexts may become completely isolated
|
||||||
|
val result2 = engine.eval("""11 + bindings["boundValue"] as Int""", engine.createBindings().apply {
|
||||||
|
put("boundValue", 100)
|
||||||
|
})
|
||||||
|
Assert.assertEquals(111, result2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertThrows(exceptionClass: Class<*>, body: () -> Unit) {
|
fun assertThrows(exceptionClass: Class<*>, body: () -> Unit) {
|
||||||
|
|||||||
+4
-2
@@ -36,8 +36,8 @@ class KotlinJsr223JvmDaemonCompileScriptEngine(
|
|||||||
compilerJar: File,
|
compilerJar: File,
|
||||||
templateClasspath: List<File>,
|
templateClasspath: List<File>,
|
||||||
templateClassName: String,
|
templateClassName: String,
|
||||||
getScriptArgs: (ScriptContext, Array<out KClass<out Any>>?) -> ScriptArgsWithTypes?,
|
val getScriptArgs: (ScriptContext, Array<out KClass<out Any>>?) -> ScriptArgsWithTypes?,
|
||||||
scriptArgsTypes: Array<out KClass<out Any>>?,
|
val scriptArgsTypes: Array<out KClass<out Any>>?,
|
||||||
compilerOut: OutputStream = System.err
|
compilerOut: OutputStream = System.err
|
||||||
) : KotlinJsr223JvmScriptEngineBase(factory), KotlinJsr223JvmInvocableScriptEngine {
|
) : KotlinJsr223JvmScriptEngineBase(factory), KotlinJsr223JvmInvocableScriptEngine {
|
||||||
|
|
||||||
@@ -61,6 +61,8 @@ class KotlinJsr223JvmDaemonCompileScriptEngine(
|
|||||||
|
|
||||||
override val replScriptEvaluator: ReplFullEvaluator get() = localEvaluator
|
override val replScriptEvaluator: ReplFullEvaluator get() = localEvaluator
|
||||||
|
|
||||||
|
override fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = getScriptArgs(context, scriptArgsTypes)
|
||||||
|
|
||||||
private fun connectToCompileService(compilerJar: File): CompileService {
|
private fun connectToCompileService(compilerJar: File): CompileService {
|
||||||
val compilerId = CompilerId.makeCompilerId(compilerJar)
|
val compilerId = CompilerId.makeCompilerId(compilerJar)
|
||||||
val daemonOptions = configureDaemonOptions()
|
val daemonOptions = configureDaemonOptions()
|
||||||
|
|||||||
+4
-2
@@ -38,8 +38,8 @@ class KotlinJsr223JvmLocalScriptEngine(
|
|||||||
factory: ScriptEngineFactory,
|
factory: ScriptEngineFactory,
|
||||||
val templateClasspath: List<File>,
|
val templateClasspath: List<File>,
|
||||||
templateClassName: String,
|
templateClassName: String,
|
||||||
getScriptArgs: (ScriptContext, Array<out KClass<out Any>>?) -> ScriptArgsWithTypes?,
|
val getScriptArgs: (ScriptContext, Array<out KClass<out Any>>?) -> ScriptArgsWithTypes?,
|
||||||
scriptArgsTypes: Array<out KClass<out Any>>?
|
val scriptArgsTypes: Array<out KClass<out Any>>?
|
||||||
) : KotlinJsr223JvmScriptEngineBase(factory), KotlinJsr223JvmInvocableScriptEngine {
|
) : KotlinJsr223JvmScriptEngineBase(factory), KotlinJsr223JvmInvocableScriptEngine {
|
||||||
|
|
||||||
override val replCompiler: ReplCompiler by lazy {
|
override val replCompiler: ReplCompiler by lazy {
|
||||||
@@ -54,6 +54,8 @@ class KotlinJsr223JvmLocalScriptEngine(
|
|||||||
|
|
||||||
override val replScriptEvaluator: ReplFullEvaluator get() = localEvaluator
|
override val replScriptEvaluator: ReplFullEvaluator get() = localEvaluator
|
||||||
|
|
||||||
|
override fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = getScriptArgs(context, scriptArgsTypes)
|
||||||
|
|
||||||
private fun makeScriptDefinition(templateClasspath: List<File>, templateClassName: String): KotlinScriptDefinition {
|
private fun makeScriptDefinition(templateClasspath: List<File>, templateClassName: String): KotlinScriptDefinition {
|
||||||
val classloader = URLClassLoader(templateClasspath.map { it.toURI().toURL() }.toTypedArray(), this.javaClass.classLoader)
|
val classloader = URLClassLoader(templateClasspath.map { it.toURI().toURL() }.toTypedArray(), this.javaClass.classLoader)
|
||||||
val cls = classloader.loadClass(templateClassName)
|
val cls = classloader.loadClass(templateClassName)
|
||||||
|
|||||||
Reference in New Issue
Block a user