Skip to content

Commit

Permalink
Added completion
Browse files Browse the repository at this point in the history
  • Loading branch information
ileasile committed Nov 18, 2019
1 parent fdf1a3d commit 4c9653e
Show file tree
Hide file tree
Showing 13 changed files with 825 additions and 34 deletions.
4 changes: 3 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
buildscript {
ext.shadowJarVersion = "5.2.0"
//ext.kotlinVersion = '1.3.60-dev-2180-83'
ext.kotlinVersion = '1.3-SNAPSHOT'
ext.kotlinVersion = '1.3.70-dev-1590'
repositories {
jcenter()
mavenLocal()
Expand All @@ -23,6 +23,7 @@ allprojects {
jcenter()
mavenLocal()
mavenCentral()
maven { url '/home/ileasile/maven_repo/repo_1/maven' }
// only when using Kotlin EAP releases ...
maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
maven { url 'https://dl.bintray.com/kotlin/kotlin-dev' }
Expand Down Expand Up @@ -50,6 +51,7 @@ configurations {

dependencies {
compile project(":jupyter-lib")
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
compile "org.jetbrains.kotlin:kotlin-scripting-jvm-host-embeddable:$kotlinVersion"
compile "org.jetbrains.kotlin:kotlin-scripting-common:$kotlinVersion"
compile "org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:$kotlinVersion"
Expand Down
24 changes: 16 additions & 8 deletions src/main/kotlin/org/jetbrains/kotlin/jupyter/protocol.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fun JupyterConnection.Socket.shellMessagesHandler(msg: Message, repl: ReplForJup
.map { Pair("${it.name}_port", connection.config.ports[it.ordinal]) })))
"execute_request" -> {
connection.contextMessage = msg
var count = executionCount.getAndIncrement()
val count = executionCount.getAndIncrement()
val startedTime = ISO8601DateNow

connection.iopub.send(makeReplyMessage(msg, "status", content = jsonObject("execution_state" to "busy")))
Expand Down Expand Up @@ -122,18 +122,26 @@ fun JupyterConnection.Socket.shellMessagesHandler(msg: Message, repl: ReplForJup
connection.iopub.send(makeReplyMessage(msg, "status", content = jsonObject("execution_state" to "idle")))
connection.contextMessage = null
}
"complete_request" -> {
val code = msg.content["code"].toString()
val cursor = msg.content["cursor_pos"] as Int
val result = repl?.complete(code, cursor)?.toJson()
if (result == null) {
System.err.println("Repl is not yet initialized on complete request")
return
}
send(makeReplyMessage(msg, "complete_reply", content = result))
}
"is_complete_request" -> {
val code = msg.content["code"].toString()
val resStr = if (isCommand(code)) "complete" else {
val result = try {
val check = repl?.checkComplete(executionCount.get(), code)
if (check == null) {
"error: no repl"
} else if (check.isComplete) {
"complete"
} else {
"incomplete"
}
when {
check == null -> "error: no repl"
check.isComplete -> "complete"
else -> "incomplete"
}
} catch (ex: ReplCompilerException) {
"invalid"
}
Expand Down
74 changes: 49 additions & 25 deletions src/main/kotlin/org/jetbrains/kotlin/jupyter/repl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import jupyter.kotlin.ScriptTemplateWithDisplayHelpers
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.config.KotlinCompilerVersion
import org.jetbrains.kotlin.jupyter.repl.building.ReplBuilding
import org.jetbrains.kotlin.jupyter.repl.completion.CompletionResult
import org.jetbrains.kotlin.jupyter.repl.completion.KotlinCompleter
import org.jetbrains.kotlin.jupyter.repl.context.KotlinContext
import org.jetbrains.kotlin.jupyter.repl.context.KotlinReceiver
import org.jetbrains.kotlin.jupyter.repl.reflect.ContextUpdater
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.script.dependencies.ScriptContents
Expand Down Expand Up @@ -59,6 +65,8 @@ class ReplForJupyter(val classpath: List<File> = emptyList(), val config: Resolv
}
}

private val receiver = KotlinReceiver()

private val compilerConfiguration by lazy {
ScriptCompilationConfiguration {
hostConfiguration.update { it.withDefaultsFrom(defaultJvmScriptingHostConfiguration) }
Expand All @@ -71,10 +79,18 @@ class ReplForJupyter(val classpath: List<File> = emptyList(), val config: Resolv
refineConfiguration {
onAnnotations(DependsOn::class, Repository::class, handler = { configureMavenDepsOnAnnotations(it) })
}

val kt = KotlinType(receiver.javaClass.canonicalName)
implicitReceivers.invoke(listOf(kt))

val receiverClassPath = receiver.javaClass.protectionDomain.codeSource.location.path
compilerOptions.invoke(listOf("-classpath", receiverClassPath))
}
}

private val evaluatorConfiguration = ScriptEvaluationConfiguration { }
private val evaluatorConfiguration = ScriptEvaluationConfiguration {
implicitReceivers.invoke(receiver)
}

private var executionCounter = 0

Expand All @@ -88,14 +104,19 @@ class ReplForJupyter(val classpath: List<File> = emptyList(), val config: Resolv

private val stateLock = ReentrantReadWriteLock()

private val state = compiler.createState(stateLock)

private val compilerState = compiler.createState(stateLock)
private val evaluatorState = evaluator.createState(stateLock)

private val state = AggregatedReplStageState(compilerState, evaluatorState, stateLock)

private val ctx = KotlinContext()
private val contextUpdater = ContextUpdater(state, ctx.vars, ctx.functions)

private val completer = KotlinCompleter(ctx)

fun checkComplete(executionNumber: Long, code: String): CheckResult {
val codeLine = ReplCodeLine(executionNumber.toInt(), 0, code)
var result = compiler.check(state, codeLine)
return when(result) {
return when(val result = compiler.check(compilerState, codeLine)) {
is ReplCheckResult.Error -> throw ReplCompilerException(result)
is ReplCheckResult.Ok -> CheckResult(LineId(codeLine), true)
is ReplCheckResult.Incomplete -> CheckResult(LineId(codeLine), false)
Expand Down Expand Up @@ -130,31 +151,34 @@ class ReplForJupyter(val classpath: List<File> = emptyList(), val config: Resolv
return EvalResult(result, displays)
}


fun complete(code: String, cursor: Int): CompletionResult = completer.complete(code, cursor)

private fun doEval(code: String): Any? {
synchronized(this) {
val codeLine = ReplCodeLine(executionCounter++, 0, code)
val compileResult = compiler.compile(state, codeLine)
when (compileResult) {
is ReplCompileResult.CompiledClasses -> {
var result = evaluator.eval(evaluatorState, compileResult)
return when (result) {
is ReplEvalResult.Error.CompileTime -> throw ReplCompilerException(result)
is ReplEvalResult.Error.Runtime -> throw ReplEvalRuntimeException(result)
is ReplEvalResult.Incomplete -> throw ReplCompilerException(result)
is ReplEvalResult.HistoryMismatch -> throw ReplCompilerException(result)
is ReplEvalResult.UnitResult -> {
Unit
}
is ReplEvalResult.ValueResult -> {
result.value
}
else -> throw IllegalStateException("Unknown eval result type ${this}")
synchronized(this) {
val codeLine = ReplCodeLine(executionCounter++, 0, code)
when (val compileResult = compiler.compile(compilerState, codeLine)) {
is ReplCompileResult.CompiledClasses -> {
val result = evaluator.eval(evaluatorState, compileResult)
contextUpdater.update()
return when (result) {
is ReplEvalResult.Error.CompileTime -> throw ReplCompilerException(result)
is ReplEvalResult.Error.Runtime -> throw ReplEvalRuntimeException(result)
is ReplEvalResult.Incomplete -> throw ReplCompilerException(result)
is ReplEvalResult.HistoryMismatch -> throw ReplCompilerException(result)
is ReplEvalResult.UnitResult -> {
Unit
}
is ReplEvalResult.ValueResult -> {
result.value
}
else -> throw IllegalStateException("Unknown eval result type ${this}")
}
is ReplCompileResult.Error -> throw ReplCompilerException(compileResult)
is ReplCompileResult.Incomplete -> throw ReplCompilerException(compileResult)
}
is ReplCompileResult.Error -> throw ReplCompilerException(compileResult)
is ReplCompileResult.Incomplete -> throw ReplCompilerException(compileResult)
}
}
}

init {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.jupyter.repl.building

import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import java.io.File
import java.util.ArrayList
import java.util.Collections
import java.util.HashSet
import org.jetbrains.kotlin.jupyter.repl.context.KotlinReceiver

/**
* Class that holds properties for Kotlin REPL creation,
* namely implicit receiver, classpath, preloaded code, directory for class bytecode output,
* max result limit and shortening types flag.
*
* Set its parameters by chaining corresponding methods, e.g.
* properties.outputDir(dir).shortenTypes(false)
*
* Get its parameters via getters.
*/
class KotlinReplProperties {

val hostConf = defaultJvmScriptingHostConfiguration

var receiver: KotlinReceiver? = null
private set
private val classpath: MutableSet<String>
private val codeOnLoad: MutableList<String>
var outputDir: String? = null
private set
var maxResult = 1000
private set
var shortenTypes = true
private set

init {
this.receiver = KotlinReceiver()

this.classpath = HashSet()
val javaClasspath = System.getProperty("java.class.path").split(File.pathSeparator.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
Collections.addAll(classpath, *javaClasspath)

this.codeOnLoad = ArrayList()
}

fun receiver(receiver: KotlinReceiver): KotlinReplProperties {
this.receiver = receiver
return this
}

fun classPath(path: String): KotlinReplProperties {
this.classpath.add(path)
return this
}

fun classPath(paths: Collection<String>): KotlinReplProperties {
this.classpath.addAll(paths)
return this
}

fun codeOnLoad(code: String): KotlinReplProperties {
this.codeOnLoad.add(code)
return this
}

fun codeOnLoad(code: Collection<String>): KotlinReplProperties {
this.codeOnLoad.addAll(code)
return this
}

fun outputDir(outputDir: String): KotlinReplProperties {
this.outputDir = outputDir
return this
}

fun maxResult(maxResult: Int): KotlinReplProperties {
this.maxResult = maxResult
return this
}

fun shortenTypes(shortenTypes: Boolean): KotlinReplProperties {
this.shortenTypes = shortenTypes
return this
}

fun getClasspath(): Set<String> {
return classpath
}

fun getCodeOnLoad(): List<String> {
return codeOnLoad
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.jupyter.repl.building

import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerImpl
import java.io.File
import java.util.StringJoiner
import kotlin.script.experimental.api.*
import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator
import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.host.withDefaultsFrom
import kotlin.script.experimental.jvmhost.repl.JvmReplCompiler
import kotlin.script.experimental.jvmhost.repl.JvmReplEvaluator

/**
* Util class for building REPL components.
*/
object ReplBuilding {
fun buildCompiler(properties: KotlinReplProperties): JvmReplCompiler {
val receiverClassPath = properties.receiver!!.javaClass
.protectionDomain.codeSource.location.path
properties.classPath(receiverClassPath)

val compilerImpl = KJvmReplCompilerImpl(properties.hostConf)

return JvmReplCompiler(
buildCompilationConfiguration(properties),
properties.hostConf,
compilerImpl)
}

fun buildEvaluator(properties: KotlinReplProperties): JvmReplEvaluator {
return JvmReplEvaluator(
buildEvaluationConfiguration(properties),
BasicJvmScriptEvaluator())
}

private fun buildClassPath(p: KotlinReplProperties): String {
val joiner = StringJoiner(File.pathSeparator)
for (path in p.getClasspath()) {
if (path != "") {
joiner.add(path)
}
}
return joiner.toString()
}

private fun buildCompilationConfiguration(
p: KotlinReplProperties): ScriptCompilationConfiguration {
return ScriptCompilationConfiguration {
hostConfiguration.invoke(p.hostConf)

val jvmBuilder = jvm
jvmBuilder.dependenciesFromCurrentContext(wholeClasspath = true, unpackJarCollections = false)

val compilerOptions = listOf("-classpath", buildClassPath(p))

this.compilerOptions.invoke(compilerOptions)

val kt = KotlinType(p.receiver!!.javaClass.canonicalName)
val receivers = listOf(kt)
implicitReceivers.invoke(receivers)

Unit
}
}

private fun buildEvaluationConfiguration(
p: KotlinReplProperties): ScriptEvaluationConfiguration {
return ScriptEvaluationConfiguration {
hostConfiguration.invoke(p.hostConf)

val receivers = listOf<Any?>(p.receiver)
implicitReceivers.invoke(receivers)

Unit
}
}
}
Loading

0 comments on commit 4c9653e

Please sign in to comment.