Implement repl, script and expressions evaluation support in kotlin runner
This commit is contained in:
@@ -36,19 +36,18 @@ object Main {
|
|||||||
|
|
||||||
private fun run(args: Array<String>) {
|
private fun run(args: Array<String>) {
|
||||||
val classpath = arrayListOf<URL>()
|
val classpath = arrayListOf<URL>()
|
||||||
|
val compilerClasspath = arrayListOf<URL>()
|
||||||
var runner: Runner? = null
|
var runner: Runner? = null
|
||||||
var collectingArguments = false
|
var collectingArguments = false
|
||||||
|
var collectingExpressions = false
|
||||||
|
var needsCompiler = false
|
||||||
val arguments = arrayListOf<String>()
|
val arguments = arrayListOf<String>()
|
||||||
|
val expressions = arrayListOf<String>()
|
||||||
var noReflect = false
|
var noReflect = false
|
||||||
|
|
||||||
var i = 0
|
var i = 0
|
||||||
while (i < args.size) {
|
while (i < args.size) {
|
||||||
val arg = args[i]
|
val arg = args[i]
|
||||||
if (collectingArguments) {
|
|
||||||
arguments.add(arg)
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fun next(): String {
|
fun next(): String {
|
||||||
if (++i == args.size) {
|
if (++i == args.size) {
|
||||||
@@ -57,6 +56,22 @@ object Main {
|
|||||||
return args[i]
|
return args[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (collectingExpressions) {
|
||||||
|
if ("-expression" == arg || "-e" == arg) {
|
||||||
|
expressions.add(next())
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
collectingArguments = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collectingArguments) {
|
||||||
|
arguments.add(arg)
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if ("-help" == arg || "-h" == arg) {
|
if ("-help" == arg || "-h" == arg) {
|
||||||
printUsageAndExit()
|
printUsageAndExit()
|
||||||
}
|
}
|
||||||
@@ -68,9 +83,15 @@ object Main {
|
|||||||
classpath.addPath(path)
|
classpath.addPath(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if ("-compiler-path" == arg) {
|
||||||
|
for (path in next().split(File.pathSeparator).filter(String::isNotEmpty)) {
|
||||||
|
compilerClasspath.addPath(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
else if ("-expression" == arg || "-e" == arg) {
|
else if ("-expression" == arg || "-e" == arg) {
|
||||||
runner = ExpressionRunner(next())
|
expressions.add(next())
|
||||||
collectingArguments = true
|
collectingExpressions = true
|
||||||
|
needsCompiler = true
|
||||||
}
|
}
|
||||||
else if ("-no-reflect" == arg) {
|
else if ("-no-reflect" == arg) {
|
||||||
noReflect = true
|
noReflect = true
|
||||||
@@ -85,6 +106,7 @@ object Main {
|
|||||||
else if (arg.endsWith(".kts")) {
|
else if (arg.endsWith(".kts")) {
|
||||||
runner = ScriptRunner(arg)
|
runner = ScriptRunner(arg)
|
||||||
collectingArguments = true
|
collectingArguments = true
|
||||||
|
needsCompiler = true
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
runner = MainClassRunner(arg)
|
runner = MainClassRunner(arg)
|
||||||
@@ -103,11 +125,20 @@ object Main {
|
|||||||
classpath.addPath(KOTLIN_HOME.toString() + "/lib/kotlin-reflect.jar")
|
classpath.addPath(KOTLIN_HOME.toString() + "/lib/kotlin-reflect.jar")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (runner == null) {
|
if (expressions.isNotEmpty()) {
|
||||||
|
runner = ExpressionRunner(expressions)
|
||||||
|
} else if (runner == null) {
|
||||||
runner = ReplRunner()
|
runner = ReplRunner()
|
||||||
|
needsCompiler = true
|
||||||
}
|
}
|
||||||
|
|
||||||
runner.run(classpath, arguments)
|
if (needsCompiler && compilerClasspath.isEmpty()) {
|
||||||
|
findCompilerJar(this::class.java, KOTLIN_HOME.resolve("lib")).forEach {
|
||||||
|
compilerClasspath.add(it.absoluteFile.toURI().toURL())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runner.run(classpath, arguments, compilerClasspath)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MutableList<URL>.addPath(path: String) {
|
private fun MutableList<URL>.addPath(path: String) {
|
||||||
|
|||||||
@@ -19,5 +19,9 @@ package org.jetbrains.kotlin.runner
|
|||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
interface Runner {
|
interface Runner {
|
||||||
fun run(classpath: List<URL>, arguments: List<String>)
|
fun run(
|
||||||
|
classpath: List<URL>,
|
||||||
|
arguments: List<String>,
|
||||||
|
compilerClasspath: List<URL>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.runner
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
fun findCompilerJar(classFromJarInTheSameLocation: Class<*>, kotlinHome: File): List<File> {
|
||||||
|
val baseDir = (tryGetResourcePathForClass(classFromJarInTheSameLocation)?.parentFile ?: kotlinHome).takeIf { it.isDirectory }
|
||||||
|
?: return emptyList()
|
||||||
|
val compilerJars = baseDir.listFiles { f: File ->
|
||||||
|
COMPILER_JARS.any { expected ->
|
||||||
|
f.matchMaybeVersionedFile(expected) && f.extension == "jar"
|
||||||
|
}
|
||||||
|
}?.takeIf { it.size >= COMPILER_JARS.size }?.toList()
|
||||||
|
return compilerJars ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val COMPILER_JARS = listOf("kotlin-compiler", "kotlin-stdlib", "kotlin-reflect")
|
||||||
|
|
||||||
|
// below is a copy from kotlin.script.experimental.jvm.impl, but we do not want to introduce dependency to that implementation, and
|
||||||
|
// there is no other good place for sharing this functionality yet
|
||||||
|
// TODO: find a good place and put the shared code into it
|
||||||
|
|
||||||
|
internal fun tryGetResourcePathForClass(aClass: Class<*>): File? {
|
||||||
|
val path = "/" + aClass.name.replace('.', '/') + ".class"
|
||||||
|
return getResourceRoot(aClass, path)?.let {
|
||||||
|
File(it).absoluteFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getResourceRoot(context: Class<*>, path: String): String? {
|
||||||
|
var url: URL? = context.getResource(path)
|
||||||
|
if (url == null) {
|
||||||
|
url = ClassLoader.getSystemResource(path.substring(1))
|
||||||
|
}
|
||||||
|
return if (url != null) extractRoot(url, path) else null
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val JAR_PROTOCOL = "jar"
|
||||||
|
private const val FILE_PROTOCOL = "file"
|
||||||
|
private const val JAR_SEPARATOR = "!/"
|
||||||
|
private const val SCHEME_SEPARATOR = "://"
|
||||||
|
|
||||||
|
private fun extractRoot(resourceURL: URL, resourcePath: String): String? {
|
||||||
|
if (!resourcePath.startsWith('/') || resourcePath.startsWith('\\')) return null
|
||||||
|
|
||||||
|
var resultPath: String? = null
|
||||||
|
val protocol = resourceURL.protocol
|
||||||
|
if (protocol == FILE_PROTOCOL) {
|
||||||
|
val path = resourceURL.toFileOrNull()!!.path
|
||||||
|
val testPath = path.replace('\\', '/')
|
||||||
|
val testResourcePath = resourcePath.replace('\\', '/')
|
||||||
|
if (testPath.endsWith(testResourcePath, ignoreCase = true)) {
|
||||||
|
resultPath = path.substring(0, path.length - resourcePath.length)
|
||||||
|
}
|
||||||
|
} else if (protocol == JAR_PROTOCOL) {
|
||||||
|
val paths = splitJarUrl(resourceURL.file)
|
||||||
|
if (paths?.first != null) {
|
||||||
|
resultPath = File(paths.first).canonicalPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultPath?.trimEnd(File.separatorChar)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun splitJarUrl(url: String): Pair<String, String>? {
|
||||||
|
val pivot = url.indexOf(JAR_SEPARATOR).takeIf { it >= 0 } ?: return null
|
||||||
|
|
||||||
|
val resourcePath = url.substring(pivot + 2)
|
||||||
|
var jarPath = url.substring(0, pivot)
|
||||||
|
|
||||||
|
if (jarPath.startsWith(JAR_PROTOCOL + ":")) {
|
||||||
|
jarPath = jarPath.substring(JAR_PROTOCOL.length + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jarPath.startsWith(FILE_PROTOCOL)) {
|
||||||
|
try {
|
||||||
|
jarPath = URL(jarPath).toFileOrNull()!!.path.replace('\\', '/')
|
||||||
|
} catch (e: Exception) {
|
||||||
|
jarPath = jarPath.substring(FILE_PROTOCOL.length)
|
||||||
|
if (jarPath.startsWith(SCHEME_SEPARATOR)) {
|
||||||
|
jarPath = jarPath.substring(SCHEME_SEPARATOR.length)
|
||||||
|
} else if (jarPath.startsWith(':')) {
|
||||||
|
jarPath = jarPath.substring(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return Pair(jarPath, resourcePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun URL.toFileOrNull() =
|
||||||
|
try {
|
||||||
|
File(toURI())
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
null
|
||||||
|
} catch (e: java.net.URISyntaxException) {
|
||||||
|
null
|
||||||
|
} ?: run {
|
||||||
|
if (protocol != "file") null
|
||||||
|
else File(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun File.matchMaybeVersionedFile(baseName: String) =
|
||||||
|
name == baseName ||
|
||||||
|
name == baseName.removeSuffix(".jar") || // for classes dirs
|
||||||
|
Regex(Regex.escape(baseName.removeSuffix(".jar")) + "(-\\d.*)?\\.jar").matches(name)
|
||||||
@@ -32,7 +32,7 @@ abstract class AbstractRunner : Runner {
|
|||||||
|
|
||||||
protected abstract fun createClassLoader(classpath: List<URL>): ClassLoader
|
protected abstract fun createClassLoader(classpath: List<URL>): ClassLoader
|
||||||
|
|
||||||
override fun run(classpath: List<URL>, arguments: List<String>) {
|
override fun run(classpath: List<URL>, arguments: List<String>, compilerClasspath: List<URL>) {
|
||||||
val classLoader = createClassLoader(classpath)
|
val classLoader = createClassLoader(classpath)
|
||||||
|
|
||||||
val mainClass = try {
|
val mainClass = try {
|
||||||
@@ -105,23 +105,58 @@ class JarRunner(private val path: String) : AbstractRunner() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ReplRunner : Runner {
|
abstract class RunnerWithCompiler : Runner {
|
||||||
override fun run(classpath: List<URL>, arguments: List<String>) {
|
|
||||||
// TODO: run REPL instead
|
fun runCompiler(compilerClasspath: List<URL>, arguments: List<String>) {
|
||||||
throw RunnerException("please specify at least one name or file to run")
|
val classLoader =
|
||||||
|
if (arguments.isEmpty()) RunnerWithCompiler::class.java.classLoader
|
||||||
|
else URLClassLoader(compilerClasspath.toTypedArray(), null)
|
||||||
|
val compilerClass = classLoader.loadClass("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler")
|
||||||
|
val mainMethod = compilerClass.getMethod("main", Array<String>::class.java)
|
||||||
|
mainMethod.invoke(null, arguments.toTypedArray())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScriptRunner(private val path: String) : Runner {
|
private fun MutableList<String>.addClasspathArgIfNeeded(classpath: List<URL>) {
|
||||||
override fun run(classpath: List<URL>, arguments: List<String>) {
|
if (classpath.isNotEmpty()) {
|
||||||
// TODO
|
add("-cp")
|
||||||
throw RunnerException("running Kotlin scripts is not yet supported")
|
add(classpath.map {
|
||||||
|
if (it.protocol == "file") it.path
|
||||||
|
else it.toExternalForm()
|
||||||
|
}.joinToString(File.pathSeparator))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExpressionRunner(private val code: String) : Runner {
|
class ReplRunner : RunnerWithCompiler() {
|
||||||
override fun run(classpath: List<URL>, arguments: List<String>) {
|
override fun run(classpath: List<URL>, arguments: List<String>, compilerClasspath: List<URL>) {
|
||||||
// TODO
|
val compilerArgs = ArrayList<String>()
|
||||||
throw RunnerException("evaluating expressions is not yet supported")
|
compilerArgs.addClasspathArgIfNeeded(classpath)
|
||||||
|
runCompiler(compilerClasspath, compilerArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScriptRunner(private val path: String) : RunnerWithCompiler() {
|
||||||
|
override fun run(classpath: List<URL>, arguments: List<String>, compilerClasspath: List<URL>) {
|
||||||
|
val compilerArgs = ArrayList<String>().apply {
|
||||||
|
addClasspathArgIfNeeded(classpath)
|
||||||
|
add("-script")
|
||||||
|
add(path)
|
||||||
|
addAll(arguments)
|
||||||
|
}
|
||||||
|
runCompiler(compilerClasspath, compilerArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExpressionRunner(private val code: List<String>) : RunnerWithCompiler() {
|
||||||
|
override fun run(classpath: List<URL>, arguments: List<String>, compilerClasspath: List<URL>) {
|
||||||
|
val compilerArgs = ArrayList<String>().apply {
|
||||||
|
addClasspathArgIfNeeded(classpath)
|
||||||
|
code.forEach {
|
||||||
|
add("-expression")
|
||||||
|
add(it)
|
||||||
|
}
|
||||||
|
addAll(arguments)
|
||||||
|
}
|
||||||
|
runCompiler(compilerClasspath, compilerArgs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user