Compare commits

...

28 Commits

Author SHA1 Message Date
Yan Zhulanow
9a2457ca58 ~ 2018-04-19 23:40:14 +03:00
Yan Zhulanow
aec1f79217 REPL: Prettify javadoc/kdoc output 2018-01-30 20:22:15 +03:00
Yan Zhulanow
63a02f9247 ~ Support reference variants 2018-01-30 20:22:14 +03:00
Yan Zhulanow
f6e9c2507d REPL: Find sources for kotlin-stdlib and JDK automatically 2018-01-30 20:22:13 +03:00
Yan Zhulanow
55a82615c8 REPL: Use idea-core functionality in documentation 2018-01-30 20:22:12 +03:00
Yan Zhulanow
64230721ae Put ide-common to dist/kotlinc 2018-01-30 20:22:11 +03:00
Yan Zhulanow
72fd50cd69 Refactoring: Extract base functionality of ResolutionFacade to ResolutionFacadeBase 2018-01-30 20:22:11 +03:00
Yan Zhulanow
eefe6cdfbc REPL: Show documentation for external libraries (if sources JAR is present in classpath) 2018-01-30 20:22:10 +03:00
Yan Zhulanow
a92b58d7ea REPL: Add ':doc' comment (for now only for REPL declarations) 2018-01-30 20:22:09 +03:00
Yan Zhulanow
1c7169e6b8 REPL, Minor: Rename ':dump bytecode' to ':classes' 2018-01-30 20:22:08 +03:00
Yan Zhulanow
8017da80da REPL, Minor: Refactor help message 2018-01-30 20:22:07 +03:00
Yan Zhulanow
2914b11097 REPL: Use single classloader in all REPL places 2018-01-30 20:22:06 +03:00
Yan Zhulanow
95ea14feb1 REPL: Reuse classloader between snippets 2018-01-30 20:22:06 +03:00
Yan Zhulanow
079360a4fe REPL: Ignore empty lines (KT-21611) 2018-01-30 20:22:05 +03:00
Yan Zhulanow
047b222240 REPL: Forbid 'this' in REPL lines (KT-14851, KT-12569) 2018-01-30 20:22:04 +03:00
Yan Zhulanow
e772054a37 REPL: Get rid of passing all previous line classses via constructor parameters 2018-01-30 20:22:03 +03:00
Yan Zhulanow
8c6ea5135b J2K: Script Codegen (step 2) 2018-01-30 20:22:02 +03:00
Yan Zhulanow
6075927624 J2K: Script Codegen (step 1) 2018-01-30 20:22:02 +03:00
Yan Zhulanow
ba526c91c9 REPL, Refactoring: Small code style fixes 2018-01-30 20:22:01 +03:00
Yan Zhulanow
1e2de8b113 REPL, Refactoring: A number of trivial renames/moves 2018-01-30 20:22:00 +03:00
Yan Zhulanow
648f9aae52 REPL, Refactoring, Minor: Lower property visibility 2018-01-30 20:21:59 +03:00
Yan Zhulanow
a1d68f4a79 REPL, Refactoring: Get rid of nasty enum-like String literals 2018-01-30 20:21:58 +03:00
Yan Zhulanow
08590c676c REPL, Refactoring: Make ReplFromTerminal and ReplInterpreter not to implement ReplConfiguration 2018-01-30 20:21:58 +03:00
Yan Zhulanow
668570e6b0 REPL, Refactoring: Replace strange callback function with an execution interceptor 2018-01-30 20:21:57 +03:00
Yan Zhulanow
0205c7d153 Minor: Delete an unused class 2018-01-30 20:21:56 +03:00
Yan Zhulanow
8351567f46 Minor: Add "repl" to dictionary 2018-01-30 20:21:55 +03:00
Yan Zhulanow
c5137be433 REPL, Refactoring: Rename ReplErrorLogger to ReplExceptionReporter
Also rethrow exceptions explicitly.
2018-01-30 20:21:54 +03:00
Yan Zhulanow
0238575a36 REPL: Fix crash on trying to :load with incorrect filename (KT-12037) 2018-01-30 20:21:54 +03:00
71 changed files with 2336 additions and 1047 deletions

View File

@@ -5,6 +5,7 @@
<w>impls</w>
<w>kapt</w>
<w>parceler</w>
<w>repl</w>
<w>uast</w>
</words>
</dictionary>

View File

@@ -58,6 +58,16 @@ public class FieldInfo {
return new FieldInfo(owner, fieldType, fieldName, false);
}
@NotNull
public static FieldInfo createForHiddenField(
@NotNull Type owner,
@NotNull Type fieldType,
@NotNull String fieldName,
@NotNull boolean isStatic
) {
return new FieldInfo(owner, fieldType, fieldName, isStatic);
}
private final Type fieldType;
private final Type ownerType;
private final String fieldName;

View File

@@ -1,242 +0,0 @@
/*
* 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.codegen;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.codegen.context.CodegenContext;
import org.jetbrains.kotlin.codegen.context.MethodContext;
import org.jetbrains.kotlin.codegen.context.ScriptContext;
import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor;
import org.jetbrains.kotlin.descriptors.ScriptDescriptor;
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKt;
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature;
import org.jetbrains.org.objectweb.asm.MethodVisitor;
import org.jetbrains.org.objectweb.asm.Type;
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
import java.util.Collections;
import java.util.List;
import static org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE;
import static org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin.NO_ORIGIN;
import static org.jetbrains.org.objectweb.asm.Opcodes.*;
public class ScriptCodegen extends MemberCodegen<KtScript> {
public static ScriptCodegen createScriptCodegen(
@NotNull KtScript declaration,
@NotNull GenerationState state,
@NotNull CodegenContext parentContext
) {
BindingContext bindingContext = state.getBindingContext();
ScriptDescriptor scriptDescriptor = bindingContext.get(BindingContext.SCRIPT, declaration);
assert scriptDescriptor != null;
Type classType = state.getTypeMapper().mapType(scriptDescriptor);
ClassBuilder builder = state.getFactory().newVisitor(JvmDeclarationOriginKt.OtherOrigin(declaration, scriptDescriptor),
classType, declaration.getContainingFile());
List<ScriptDescriptor> earlierScripts = state.getReplSpecific().getEarlierScriptsForReplInterpreter();
ScriptContext scriptContext = parentContext.intoScript(
scriptDescriptor,
earlierScripts == null ? Collections.emptyList() : earlierScripts,
scriptDescriptor,
state.getTypeMapper()
);
return new ScriptCodegen(declaration, state, scriptContext, builder);
}
private final KtScript scriptDeclaration;
private final ScriptContext context;
private final ScriptDescriptor scriptDescriptor;
private final Type classAsmType;
private ScriptCodegen(
@NotNull KtScript scriptDeclaration,
@NotNull GenerationState state,
@NotNull ScriptContext context,
@NotNull ClassBuilder builder
) {
super(state, null, context, scriptDeclaration, builder);
this.scriptDeclaration = scriptDeclaration;
this.context = context;
this.scriptDescriptor = context.getScriptDescriptor();
classAsmType = typeMapper.mapClass(context.getContextDescriptor());
}
@Override
protected void generateDeclaration() {
v.defineClass(scriptDeclaration,
state.getClassFileVersion(),
ACC_PUBLIC | ACC_SUPER,
classAsmType.getInternalName(),
null,
typeMapper.mapSupertype(DescriptorUtilsKt.getSuperClassOrAny(scriptDescriptor).getDefaultType(), null).getInternalName(),
CodegenUtilKt.mapSupertypesNames(typeMapper, DescriptorUtilsKt.getSuperInterfaces(scriptDescriptor), null));
}
@Override
protected void generateBody() {
genMembers();
genFieldsForParameters(v);
genConstructor(scriptDescriptor, v,
context.intoFunction(scriptDescriptor.getUnsubstitutedPrimaryConstructor()));
}
@Override
protected void generateSyntheticPartsBeforeBody() {
generatePropertyMetadataArrayFieldIfNeeded(classAsmType);
}
@Override
protected void generateSyntheticPartsAfterBody() {
}
@Override
protected void generateKotlinMetadataAnnotation() {
generateKotlinClassMetadataAnnotation(scriptDescriptor, true);
}
private void genConstructor(
@NotNull ScriptDescriptor scriptDescriptor,
@NotNull ClassBuilder classBuilder,
@NotNull MethodContext methodContext
) {
JvmMethodSignature jvmSignature = typeMapper.mapScriptSignature(scriptDescriptor, context.getEarlierScripts());
if (state.getReplSpecific().getShouldGenerateScriptResultValue()) {
FieldInfo resultFieldInfo = context.getResultFieldInfo();
classBuilder.newField(
JvmDeclarationOrigin.NO_ORIGIN,
ACC_PUBLIC | ACC_FINAL,
resultFieldInfo.getFieldName(),
resultFieldInfo.getFieldType().getDescriptor(),
null,
null
);
}
MethodVisitor mv = classBuilder.newMethod(
JvmDeclarationOriginKt.OtherOrigin(scriptDeclaration, scriptDescriptor.getUnsubstitutedPrimaryConstructor()),
ACC_PUBLIC, jvmSignature.getAsmMethod().getName(), jvmSignature.getAsmMethod().getDescriptor(),
null, null);
if (state.getClassBuilderMode().generateBodies) {
mv.visitCode();
InstructionAdapter iv = new InstructionAdapter(mv);
Type classType = typeMapper.mapType(scriptDescriptor);
ClassDescriptor superclass = DescriptorUtilsKt.getSuperClassNotAny(scriptDescriptor);
// TODO: throw if class is not found)
if (superclass == null) {
iv.load(0, classType);
iv.invokespecial("java/lang/Object", "<init>", "()V", false);
}
else {
ConstructorDescriptor ctorDesc = superclass.getUnsubstitutedPrimaryConstructor();
if (ctorDesc == null) throw new RuntimeException("Primary constructor not found for script template " + superclass.toString());
iv.load(0, classType);
int valueParamStart = context.getEarlierScripts().isEmpty() ? 1 : 2; // this + array of earlier scripts if not empty
List<ValueParameterDescriptor> valueParameters = scriptDescriptor.getUnsubstitutedPrimaryConstructor().getValueParameters();
for (ValueParameterDescriptor superclassParam: ctorDesc.getValueParameters()) {
ValueParameterDescriptor valueParam = null;
for (ValueParameterDescriptor vpd: valueParameters) {
if (vpd.getName().equals(superclassParam.getName())) {
valueParam = vpd;
break;
}
}
assert valueParam != null;
iv.load(valueParam.getIndex() + valueParamStart, typeMapper.mapType(valueParam.getType()));
}
CallableMethod ctorMethod = typeMapper.mapToCallableMethod(ctorDesc, false);
String sig = ctorMethod.getAsmMethod().getDescriptor();
iv.invokespecial(
typeMapper.mapSupertype(superclass.getDefaultType(), null).getInternalName(),
"<init>", sig, false);
}
iv.load(0, classType);
FrameMap frameMap = new FrameMap();
frameMap.enterTemp(OBJECT_TYPE);
if (!context.getEarlierScripts().isEmpty()) {
int scriptsParamIndex = frameMap.enterTemp(AsmUtil.getArrayType(OBJECT_TYPE));
int earlierScriptIndex = 0;
for (ScriptDescriptor earlierScript : context.getEarlierScripts()) {
Type earlierClassType = typeMapper.mapClass(earlierScript);
iv.load(0, classType);
iv.load(scriptsParamIndex, earlierClassType);
iv.aconst(earlierScriptIndex++);
iv.aload(OBJECT_TYPE);
iv.checkcast(earlierClassType);
iv.putfield(classType.getInternalName(), context.getScriptFieldName(earlierScript), earlierClassType.getDescriptor());
}
}
ExpressionCodegen codegen = new ExpressionCodegen(mv, frameMap, Type.VOID_TYPE, methodContext, state, this);
generateInitializers(() -> codegen);
iv.areturn(Type.VOID_TYPE);
}
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
private void genFieldsForParameters(@NotNull ClassBuilder classBuilder) {
for (ScriptDescriptor earlierScript : context.getEarlierScripts()) {
Type earlierClassName = typeMapper.mapType(earlierScript);
int access = ACC_PUBLIC | ACC_FINAL;
classBuilder.newField(NO_ORIGIN, access, context.getScriptFieldName(earlierScript), earlierClassName.getDescriptor(), null, null);
}
}
private void genMembers() {
for (KtDeclaration declaration : scriptDeclaration.getDeclarations()) {
if (declaration instanceof KtProperty || declaration instanceof KtNamedFunction || declaration instanceof KtTypeAlias) {
genSimpleMember(declaration);
}
else if (declaration instanceof KtClassOrObject) {
genClassOrObject((KtClassOrObject) declaration);
}
else if (declaration instanceof KtDestructuringDeclaration) {
for (KtDestructuringDeclarationEntry entry : ((KtDestructuringDeclaration) declaration).getEntries()) {
genSimpleMember(entry);
}
}
}
}
}

View File

@@ -0,0 +1,217 @@
/*
* 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.codegen
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.codegen.context.CodegenContext
import org.jetbrains.kotlin.codegen.context.MethodContext
import org.jetbrains.kotlin.codegen.context.ScriptContext
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.*
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.resolve.jvm.diagnostics.*
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin.Companion.NO_ORIGIN
import org.jetbrains.org.objectweb.asm.Opcodes.*
class ScriptCodegen private constructor(
private val scriptDeclaration: KtScript,
state: GenerationState,
private val scriptContext: ScriptContext,
builder: ClassBuilder
) : MemberCodegen<KtScript>(state, null, scriptContext, scriptDeclaration, builder) {
private val scriptDescriptor = scriptContext.scriptDescriptor
private val classAsmType = typeMapper.mapClass(scriptContext.contextDescriptor)
override fun generateDeclaration() {
v.defineClass(
scriptDeclaration,
state.classFileVersion,
ACC_PUBLIC or ACC_SUPER,
classAsmType.internalName,
null,
typeMapper.mapSupertype(scriptDescriptor.getSuperClassOrAny().defaultType, null).internalName,
mapSupertypesNames(typeMapper, scriptDescriptor.getSuperInterfaces(), null)
)
}
override fun generateBody() {
genMembers()
genFieldsForParameters(v)
genConstructor(scriptDescriptor, v, scriptContext.intoFunction(scriptDescriptor.unsubstitutedPrimaryConstructor))
}
override fun generateSyntheticPartsBeforeBody() {
generatePropertyMetadataArrayFieldIfNeeded(classAsmType)
}
override fun generateSyntheticPartsAfterBody() {}
override fun generateKotlinMetadataAnnotation() {
generateKotlinClassMetadataAnnotation(scriptDescriptor, true)
}
private fun genConstructor(
scriptDescriptor: ScriptDescriptor,
classBuilder: ClassBuilder,
methodContext: MethodContext
) {
val jvmSignature = typeMapper.mapScriptSignature(scriptDescriptor, scriptContext.earlierScripts)
if (state.replSpecific.shouldGenerateScriptResultValue) {
val resultFieldInfo = scriptContext.resultFieldInfo
classBuilder.newField(
JvmDeclarationOrigin.NO_ORIGIN,
ACC_PUBLIC or ACC_FINAL,
resultFieldInfo.fieldName,
resultFieldInfo.fieldType.descriptor,
null, null)
}
val mv = classBuilder.newMethod(
OtherOrigin(scriptDeclaration, scriptDescriptor.unsubstitutedPrimaryConstructor),
ACC_PUBLIC, jvmSignature.asmMethod.name, jvmSignature.asmMethod.descriptor, null, null)
if (state.classBuilderMode.generateBodies) {
mv.visitCode()
val iv = InstructionAdapter(mv)
val classType = typeMapper.mapType(scriptDescriptor)
val superclass = scriptDescriptor.getSuperClassNotAny()
// TODO: throw if class is not found)
if (superclass == null) {
iv.load(0, classType)
iv.invokespecial("java/lang/Object", "<init>", "()V", false)
}
else {
val ctorDesc = superclass.unsubstitutedPrimaryConstructor
?: throw RuntimeException("Primary constructor not found for script template " + superclass.toString())
iv.load(0, classType)
val valueParamStart = if (scriptContext.earlierScripts.isEmpty()) 1 else 2 // this + array of earlier scripts if not empty
val valueParameters = scriptDescriptor.unsubstitutedPrimaryConstructor.valueParameters
for (superclassParam in ctorDesc.valueParameters) {
val valueParam = valueParameters.first { it.name == superclassParam.name }
iv.load(valueParam!!.index + valueParamStart, typeMapper.mapType(valueParam.type))
}
val ctorMethod = typeMapper.mapToCallableMethod(ctorDesc, false)
val sig = ctorMethod.getAsmMethod().descriptor
iv.invokespecial(
typeMapper.mapSupertype(superclass.defaultType, null).internalName,
"<init>", sig, false)
}
iv.load(0, classType)
val frameMap = FrameMap()
frameMap.enterTemp(OBJECT_TYPE)
if (!scriptContext.earlierScripts.isEmpty()) {
val scriptsParamIndex = frameMap.enterTemp(AsmUtil.getArrayType(OBJECT_TYPE))
var earlierScriptIndex = 0
for (earlierScript in scriptContext.earlierScripts) {
val earlierClassType = typeMapper.mapClass(earlierScript)
iv.load(0, classType)
iv.load(scriptsParamIndex, earlierClassType)
iv.aconst(earlierScriptIndex++)
iv.aload(OBJECT_TYPE)
iv.checkcast(earlierClassType)
iv.putfield(classType.internalName, scriptContext.getScriptFieldName(earlierScript), earlierClassType.descriptor)
}
}
val codegen = ExpressionCodegen(mv, frameMap, Type.VOID_TYPE, methodContext, state, this)
generateInitializers { codegen }
iv.areturn(Type.VOID_TYPE)
}
mv.visitMaxs(-1, -1)
mv.visitEnd()
}
private fun genFieldsForParameters(classBuilder: ClassBuilder) {
for (earlierScript in scriptContext.earlierScripts) {
val earlierClassName = typeMapper.mapType(earlierScript)
val access = ACC_PUBLIC or ACC_FINAL
classBuilder.newField(NO_ORIGIN, access, scriptContext.getScriptFieldName(earlierScript), earlierClassName.descriptor, null, null)
}
}
private fun genMembers() {
for (declaration in scriptDeclaration.declarations) {
if (declaration is KtProperty || declaration is KtNamedFunction || declaration is KtTypeAlias) {
genSimpleMember(declaration)
}
else if (declaration is KtClassOrObject) {
genClassOrObject(declaration)
}
else if (declaration is KtDestructuringDeclaration) {
for (entry in declaration.entries) {
genSimpleMember(entry)
}
}
}
}
companion object {
@JvmStatic
fun createScriptCodegen(
declaration: KtScript,
state: GenerationState,
parentContext: CodegenContext<*>
): MemberCodegen<KtScript> {
val bindingContext = state.bindingContext
val scriptDescriptor = bindingContext.get<PsiElement, ScriptDescriptor>(BindingContext.SCRIPT, declaration)!!
if (scriptDescriptor.isReplSnippet) {
return ScriptCodegenForRepl.createScriptCodegen(declaration, state, parentContext)
}
val classType = state.typeMapper.mapType(scriptDescriptor)
val builder = state.factory.newVisitor(
OtherOrigin(declaration, scriptDescriptor), classType, declaration.containingFile)
val earlierScripts = state.replSpecific.earlierScriptsForReplInterpreter
val scriptContext = parentContext.intoScript(
scriptDescriptor,
earlierScripts ?: emptyList(),
scriptDescriptor,
state.typeMapper
)
return ScriptCodegen(declaration, state, scriptContext, builder)
}
}
}

View File

@@ -0,0 +1,165 @@
/*
* 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.codegen
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.codegen.context.CodegenContext
import org.jetbrains.kotlin.codegen.context.MethodContext
import org.jetbrains.kotlin.codegen.context.ScriptContext
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.*
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.resolve.jvm.diagnostics.*
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.org.objectweb.asm.Opcodes.*
class ScriptCodegenForRepl private constructor(
private val scriptDeclaration: KtScript,
state: GenerationState,
private val scriptContext: ScriptContext,
builder: ClassBuilder
) : MemberCodegen<KtScript>(state, null, scriptContext, scriptDeclaration, builder) {
private val scriptDescriptor = scriptContext.scriptDescriptor
private val classAsmType = typeMapper.mapClass(scriptContext.contextDescriptor)
override fun generateDeclaration() {
// Do not allow superclasses for REPL line scripts
assert(scriptDescriptor.getSuperClassNotAny() == null)
v.defineClass(
scriptDeclaration,
state.classFileVersion,
ACC_PUBLIC or ACC_SUPER,
classAsmType.internalName,
null,
"java/lang/Object",
mapSupertypesNames(typeMapper, scriptDescriptor.getSuperInterfaces(), null)
)
}
override fun generateBody() {
genMembers()
genDefaultConstructor(v)
genRunMethod(v, scriptContext.intoFunction(scriptDescriptor.unsubstitutedPrimaryConstructor))
genResultFieldIfNeeded(v)
}
override fun generateSyntheticPartsBeforeBody() {
generatePropertyMetadataArrayFieldIfNeeded(classAsmType)
}
override fun generateSyntheticPartsAfterBody() {}
override fun generateKotlinMetadataAnnotation() {
generateKotlinClassMetadataAnnotation(scriptDescriptor, true)
}
private fun genResultFieldIfNeeded(classBuilder: ClassBuilder) {
if (state.replSpecific.shouldGenerateScriptResultValue) {
val resultFieldInfo = scriptContext.resultFieldInfo
classBuilder.newField(
JvmDeclarationOrigin.NO_ORIGIN,
ACC_PUBLIC or ACC_FINAL or ACC_STATIC,
resultFieldInfo.fieldName,
resultFieldInfo.fieldType.descriptor,
null, null)
}
}
private fun genDefaultConstructor(classBuilder: ClassBuilder) {
val mv = classBuilder.newMethod(OtherOrigin(scriptDeclaration), ACC_PUBLIC, "<init>", "()V", null, null)
if (state.classBuilderMode.generateBodies) {
mv.visitCode()
with (InstructionAdapter(mv)) {
load(0, typeMapper.mapType(scriptDescriptor))
invokespecial("java/lang/Object", "<init>", "()V", false)
areturn(Type.VOID_TYPE)
}
}
mv.visitMaxs(-1, -1)
mv.visitEnd()
}
private fun genRunMethod(classBuilder: ClassBuilder, methodContext: MethodContext) {
val mv = classBuilder.newMethod(OtherOrigin(scriptDeclaration), ACC_PUBLIC or ACC_STATIC, RUN_METHOD_NAME, "()V", null, null)
if (state.classBuilderMode.generateBodies) {
mv.visitCode()
with (InstructionAdapter(mv)) {
val codegen = ExpressionCodegen(mv, FrameMap(), Type.VOID_TYPE, methodContext, state, this@ScriptCodegenForRepl)
generateInitializers { codegen }
areturn(Type.VOID_TYPE)
}
}
mv.visitMaxs(-1, -1)
mv.visitEnd()
}
private fun genMembers() {
for (declaration in scriptDeclaration.declarations) {
if (declaration is KtProperty || declaration is KtNamedFunction || declaration is KtTypeAlias) {
genSimpleMember(declaration)
}
else if (declaration is KtClassOrObject) {
genClassOrObject(declaration)
}
else if (declaration is KtDestructuringDeclaration) {
for (entry in declaration.entries) {
genSimpleMember(entry)
}
}
}
}
companion object {
val RUN_METHOD_NAME = "run"
@JvmStatic
fun createScriptCodegen(
declaration: KtScript,
state: GenerationState,
parentContext: CodegenContext<*>
): MemberCodegen<KtScript> {
val bindingContext = state.bindingContext
val scriptDescriptor = bindingContext.get<PsiElement, ScriptDescriptor>(BindingContext.SCRIPT, declaration)!!
val classType = state.typeMapper.mapType(scriptDescriptor)
val builder = state.factory.newVisitor(
OtherOrigin(declaration, scriptDescriptor), classType, declaration.containingFile)
val earlierScripts = state.replSpecific.earlierScriptsForReplInterpreter
val scriptContext = parentContext.intoScript(
scriptDescriptor,
earlierScripts ?: emptyList(),
scriptDescriptor,
state.typeMapper
)
return ScriptCodegenForRepl(declaration, state, scriptContext, builder)
}
}
}

View File

@@ -69,9 +69,15 @@ public class ScriptContext extends ClassContext {
public FieldInfo getResultFieldInfo() {
assert getState().getReplSpecific().getShouldGenerateScriptResultValue() : "Should not be called unless 'scriptResultFieldName' is set";
GenerationState state = getState();
String scriptResultFieldName = state.getReplSpecific().getScriptResultFieldName();
assert scriptResultFieldName != null;
return FieldInfo.createForHiddenField(state.getTypeMapper().mapClass(scriptDescriptor), AsmTypes.OBJECT_TYPE, scriptResultFieldName);
return FieldInfo.createForHiddenField(
state.getTypeMapper().mapClass(scriptDescriptor),
AsmTypes.OBJECT_TYPE,
scriptResultFieldName,
scriptDescriptor.isReplSnippet());
}
@NotNull

View File

@@ -23,6 +23,11 @@ dependencies {
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
compileOnly(intellijDep()) { includeIntellijCoreJarDependencies(project) }
runtime(projectDist(":kotlin-reflect"))
// Used in REPL code insight
compileOnly(project(":idea:idea-core"))
testCompile(project(":compiler:backend"))
testCompile(project(":compiler:cli"))
testCompile(project(":compiler:tests-common"))

View File

@@ -17,24 +17,18 @@
package org.jetbrains.kotlin.cli.common.repl
import java.io.File
import java.net.URLClassLoader
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
open class GenericReplEvaluatorState(baseClasspath: Iterable<File>, baseClassloader: ClassLoader?, override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock())
: IReplStageState<EvalClassWithInstanceAndLoader>
{
open class GenericReplEvaluatorState(
val classLoader: ReplClassLoader,
final override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) : IReplStageState<EvalClassWithInstanceAndLoader> {
override val history: IReplStageHistory<EvalClassWithInstanceAndLoader> = BasicReplStageHistory(lock)
override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
val topClassLoader: ReplClassLoader = makeReplClassLoader(baseClassloader, baseClasspath)
val currentClasspath: List<File> get() = lock.read {
history.peek()?.item?.classLoader?.listAllUrlsAsFiles()
?: topClassLoader.listAllUrlsAsFiles()
history.peek()?.item?.classLoader?.listAllUrlsAsFiles() ?: classLoader.listAllUrlsAsFiles()
}
}
internal fun makeReplClassLoader(baseClassloader: ClassLoader?, baseClasspath: Iterable<File>) =
ReplClassLoader(URLClassLoader(baseClasspath.map { it.toURI().toURL() }.toTypedArray(), baseClassloader))
}

View File

@@ -21,16 +21,23 @@ import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
class GenericReplCompilingEvaluator(val compiler: ReplCompiler,
baseClasspath: Iterable<File>,
baseClasspath: List<File>,
baseClassloader: ClassLoader? = Thread.currentThread().contextClassLoader,
private val fallbackScriptArgs: ScriptArgsWithTypes? = null,
repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT
) : ReplFullEvaluator {
private val evaluator = GenericReplEvaluator(baseClasspath, baseClassloader, fallbackScriptArgs, repeatingMode)
override val classLoader: ReplClassLoader
get() = evaluator.classLoader
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = AggregatedReplStageState(compiler.createState(lock), evaluator.createState(lock), lock)
override fun compileAndEval(state: IReplStageState<*>, codeLine: ReplCodeLine, scriptArgs: ScriptArgsWithTypes?, invokeWrapper: InvokeWrapper?): ReplEvalResult {
if (codeLine.code.trim().isEmpty()) {
return ReplEvalResult.UnitResult()
}
return state.lock.write {
val aggregatedState = state.asState(AggregatedReplStageState::class.java)
val compiled = compiler.compile(state, codeLine)

View File

@@ -23,20 +23,21 @@ import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
open class GenericReplEvaluator(
val baseClasspath: Iterable<File>,
val baseClassloader: ClassLoader? = Thread.currentThread().contextClassLoader,
baseClasspath: List<File>,
baseClassloader: ClassLoader? = Thread.currentThread().contextClassLoader,
protected val fallbackScriptArgs: ScriptArgsWithTypes? = null,
protected val repeatingMode: ReplRepeatingMode = ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT
) : ReplEvaluator {
override val classLoader = ReplClassLoader(baseClasspath, baseClassloader)
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = GenericReplEvaluatorState(baseClasspath, baseClassloader, lock)
override fun createState(lock: ReentrantReadWriteLock) = GenericReplEvaluatorState(classLoader, lock)
override fun eval(state: IReplStageState<*>,
compileResult: ReplCompileResult.CompiledClasses,
scriptArgs: ScriptArgsWithTypes?,
invokeWrapper: InvokeWrapper?): ReplEvalResult {
state.lock.write {
val evalState = state.asState(GenericReplEvaluatorState::class.java)
val evalState = state.asState<GenericReplEvaluatorState>()
val historyActor = when (repeatingMode) {
ReplRepeatingMode.NONE -> HistoryActionsForNoRepeat(evalState)
ReplRepeatingMode.REPEAT_ONLY_MOST_RECENT -> {
@@ -64,49 +65,46 @@ open class GenericReplEvaluator(
return@eval ReplEvalResult.HistoryMismatch(firstMismatch.first?.id?.no ?: firstMismatch.second?.no ?: -1 /* means error? */)
}
val (classLoader, scriptClass) = try {
val scriptClass = try {
historyActor.processClasses(compileResult)
}
catch (e: Exception) {
return@eval ReplEvalResult.Error.Runtime(e.message ?: "unknown", e)
}
val currentScriptArgs = scriptArgs ?: fallbackScriptArgs
val useScriptArgs = currentScriptArgs?.scriptArgs
val useScriptArgsTypes = currentScriptArgs?.scriptArgsTypes?.map { it.java }
historyActor.addPlaceholder(
compileResult.lineId,
EvalClassWithInstanceAndLoader(scriptClass.kotlin, null, evalState.classLoader, invokeWrapper))
val hasHistory = historyActor.effectiveHistory.isNotEmpty()
val constructorParams: Array<Class<*>> = (if (hasHistory) arrayOf<Class<*>>(Array<Any>::class.java) else emptyArray<Class<*>>()) +
(useScriptArgs?.mapIndexed { i, it -> useScriptArgsTypes?.getOrNull(i) ?: it?.javaClass ?: Any::class.java } ?: emptyList())
val constructorArgs: Array<out Any?> = if (hasHistory) arrayOf(historyActor.effectiveHistory.map { it.instance }.takeIf { it.isNotEmpty() }?.toTypedArray(),
*(useScriptArgs.orEmpty()))
else useScriptArgs.orEmpty()
// TODO: try/catch ?
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
historyActor.addPlaceholder(compileResult.lineId, EvalClassWithInstanceAndLoader(scriptClass.kotlin, null, classLoader, invokeWrapper))
fun makeErrorMessage(e: Throwable) = renderReplStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}.run")
val scriptInstance =
try {
if (invokeWrapper != null) invokeWrapper.invoke { scriptInstanceConstructor.newInstance(*constructorArgs) }
else scriptInstanceConstructor.newInstance(*constructorArgs)
val scriptInstance = if (invokeWrapper != null) {
invokeWrapper.invoke { scriptClass.newInstance() }
}
else {
scriptClass.newInstance()
}
scriptInstance.javaClass.getDeclaredMethod("run").invoke(null)
scriptInstance
}
catch (e: InvocationTargetException) {
// ignore everything in the stack trace until this constructor call
return@eval ReplEvalResult.Error.Runtime(renderReplStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}.<init>"), e.targetException as? Exception)
return@eval ReplEvalResult.Error.Runtime(makeErrorMessage(e), e.targetException as? Exception)
}
catch (e: Throwable) {
// ignore everything in the stack trace until this constructor call
return@eval ReplEvalResult.Error.Runtime(renderReplStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}.<init>"), e as? Exception)
return@eval ReplEvalResult.Error.Runtime(makeErrorMessage(e), e as? Exception)
}
finally {
historyActor.removePlaceholder(compileResult.lineId)
}
historyActor.addFinal(compileResult.lineId, EvalClassWithInstanceAndLoader(scriptClass.kotlin, scriptInstance, classLoader, invokeWrapper))
historyActor.addFinal(
compileResult.lineId,
EvalClassWithInstanceAndLoader(scriptClass.kotlin, scriptInstance, evalState.classLoader, invokeWrapper))
val resultField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true }
val resultValue: Any? = resultField.get(scriptInstance)
@@ -123,43 +121,39 @@ open class GenericReplEvaluator(
}
private open class HistoryActionsForNoRepeat(val state: GenericReplEvaluatorState) {
open val effectiveHistory: List<EvalClassWithInstanceAndLoader>
get() = state.history.map { it.item }
open val effectiveHistory: List<EvalClassWithInstanceAndLoader> get() = state.history.map { it.item }
open fun firstMismatch(other: Sequence<ILineId>): Pair<ReplHistoryRecord<EvalClassWithInstanceAndLoader>?, ILineId?>? {
return state.history.firstMismatch(other)
}
open fun firstMismatch(other: Sequence<ILineId>): Pair<ReplHistoryRecord<EvalClassWithInstanceAndLoader>?, ILineId?>? = state.history.firstMismatch(other)
open fun addPlaceholder(lineId: ILineId, value: EvalClassWithInstanceAndLoader) { state.history.push(lineId, value) }
open fun addPlaceholder(lineId: ILineId, value: EvalClassWithInstanceAndLoader) {
state.history.push(lineId, value)
}
open fun removePlaceholder(lineId: ILineId): Boolean = state.history.verifiedPop(lineId) != null
open fun addFinal(lineId: ILineId, value: EvalClassWithInstanceAndLoader) { state.history.push(lineId, value) }
open fun addFinal(lineId: ILineId, value: EvalClassWithInstanceAndLoader) {
state.history.push(lineId, value)
}
open fun processClasses(compileResult: ReplCompileResult.CompiledClasses): Pair<ClassLoader, Class<out Any>> = prependClassLoaderWithNewClasses(effectiveHistory, compileResult)
open fun processClasses(compileResult: ReplCompileResult.CompiledClasses): Class<out Any> {
class ClassToLoad(val name: JvmClassName, val bytes: ByteArray)
private fun prependClassLoaderWithNewClasses(effectiveHistory: List<EvalClassWithInstanceAndLoader>,
compileResult: ReplCompileResult.CompiledClasses
): Pair<ClassLoader, Class<out Any>> {
var mainLineClassName: String? = null
val classLoader = makeReplClassLoader(effectiveHistory.lastOrNull()?.classLoader ?: state.topClassLoader, compileResult.classpathAddendum)
fun classNameFromPath(path: String) = JvmClassName.byInternalName(path.removeSuffix(".class"))
fun compiledClassesNames() = compileResult.classes.map { classNameFromPath(it.path).internalName.replace('/', '.') }
val expectedClassName = compileResult.mainClassName
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 classesToLoad = compileResult.classes
.filter { it.path.endsWith(".class") }
.map { ClassToLoad(JvmClassName.byInternalName(it.path.removeSuffix(".class")), it.bytes) }
val scriptClass = try {
classLoader.loadClass(mainLineClassName!!)
val mainClassName = compileResult.mainClassName
if (!classesToLoad.any { it.name.internalName == mainClassName }) {
val compiledClassNames = classesToLoad.joinToString { it.name.internalName }
throw IllegalStateException("Error loading class $mainClassName: known classes: $compiledClassNames")
}
catch (t: Throwable) {
throw Exception("Error loading class $mainLineClassName: known classes: ${compiledClassesNames()}", t)
}
return Pair(classLoader, scriptClass)
classesToLoad.forEach { state.classLoader.addClass(it.name, it.bytes) }
return Class.forName(mainClassName, true, state.classLoader)
}
}
@@ -181,8 +175,7 @@ private open class HistoryActionsForRepeatRecentOnly(state: GenericReplEvaluator
state.history.push(lineId, value)
}
override fun processClasses(compileResult: ReplCompileResult.CompiledClasses): Pair<ClassLoader, Class<out Any>> =
currentLast.item.classLoader to currentLast.item.klass.java
override fun processClasses(compileResult: ReplCompileResult.CompiledClasses) = currentLast.item.klass.java
}
private open class HistoryActionsForRepeatAny(state: GenericReplEvaluatorState, val matchingLine: ReplHistoryRecord<EvalClassWithInstanceAndLoader>): HistoryActionsForNoRepeat(state) {
@@ -206,6 +199,5 @@ private open class HistoryActionsForRepeatAny(state: GenericReplEvaluatorState,
}
}
override fun processClasses(compileResult: ReplCompileResult.CompiledClasses): Pair<ClassLoader, Class<out Any>> =
matchingLine.item.classLoader to matchingLine.item.klass.java
override fun processClasses(compileResult: ReplCompileResult.CompiledClasses) = matchingLine.item.klass.java
}

View File

@@ -32,7 +32,7 @@ interface KotlinJsr223JvmInvocableScriptEngine : Invocable {
val state: IReplStageState<*> // The Invokable interface do not allow Context/Bindings substitution, so state is supplied via property
private fun prioritizedHistory(receiverClass: KClass<*>?, receiverInstance: Any?): List<EvalClassWithInstanceAndLoader> {
val evalState = state.asState(GenericReplEvaluatorState::class.java)
val evalState = state.asState<GenericReplEvaluatorState>()
return evalState.history.map { it.item }.filter { it.instance != null }.reversed().ensureNotEmpty("no script ").let { history ->
if (receiverInstance != null) {
val receiverKlass = receiverClass ?: receiverInstance::class

View File

@@ -157,7 +157,9 @@ sealed class ReplEvalResult : Serializable {
}
}
interface ReplEvaluator : ReplEvalAction, CreateReplStageStateAction
interface ReplEvaluator : ReplEvalAction, CreateReplStageStateAction {
val classLoader: ReplClassLoader
}
// --- compileAdnEval

View File

@@ -1,61 +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 com.google.common.collect.Maps;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
import org.jetbrains.org.objectweb.asm.ClassReader;
import org.jetbrains.org.objectweb.asm.util.TraceClassVisitor;
import java.io.PrintWriter;
import java.util.Map;
public class ReplClassLoader extends ClassLoader {
private final Map<JvmClassName, byte[]> classes = Maps.newLinkedHashMap();
public ReplClassLoader(@NotNull ClassLoader parent) {
super(parent);
}
@NotNull
@Override
protected Class<?> findClass(@NotNull String name) throws ClassNotFoundException {
byte[] classBytes = classes.get(JvmClassName.byFqNameWithoutInnerClasses(name));
if (classBytes != null) {
return defineClass(name, classBytes, 0, classBytes.length);
}
else {
return super.findClass(name);
}
}
public void addClass(@NotNull JvmClassName className, @NotNull byte[] bytes) {
byte[] oldBytes = classes.put(className, bytes);
if (oldBytes != null) {
throw new IllegalStateException("Rewrite at key " + className);
}
}
public void dumpClasses(@NotNull PrintWriter writer) {
for (byte[] classBytes : classes.values()) {
new ClassReader(classBytes).accept(new TraceClassVisitor(writer), 0);
}
}
}

View File

@@ -0,0 +1,110 @@
/*
* 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 org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.util.TraceClassVisitor
import java.io.ByteArrayInputStream
import java.io.File
import java.io.PrintWriter
import java.net.URL
import java.net.URLClassLoader
import java.net.URLConnection
import java.net.URLStreamHandler
import java.util.*
class ReplClassLoader(
files: List<File>,
parent: ClassLoader?
) : URLClassLoader(files.map { it.toURI().toURL() }.toTypedArray(), parent) {
private val classes = LinkedHashMap<JvmClassName, ByteArray>()
private val loadedClasses = HashMap<JvmClassName, Class<*>>()
fun addJar(file: File) {
super.addURL(file.toURI().toURL())
}
override fun loadClass(name: String, resolve: Boolean): Class<*> {
val className = JvmClassName.byFqNameWithoutInnerClasses(name)
val classBytes = classes[className]
if (classBytes != null) {
return loadedClasses.getOrPut(className) {
defineClass(name, classBytes, 0, classBytes.size)
}
}
return super.loadClass(name, resolve)
}
override fun findClass(name: String): Class<*> {
val className = JvmClassName.byFqNameWithoutInnerClasses(name)
val classBytes = classes[className]
if (classBytes != null) {
return loadedClasses.getOrPut(className) {
defineClass(name, classBytes, 0, classBytes.size)
}
}
return super.findClass(name)
}
override fun findResource(name: String): URL {
val jvmName = JvmClassName.byFqNameWithoutInnerClasses(name)
classes[jvmName]?.let { return wrapToUrl(jvmName.internalName + ".class", it) }
return super.findResource(name)
}
override fun findResources(name: String): Enumeration<URL> {
val result = mutableListOf<URL>()
// Add our custom resource
val jvmName = JvmClassName.byFqNameWithoutInnerClasses(name)
classes[jvmName]?.let { wrapToUrl(jvmName.internalName + ".class", it) }?.let { result += it }
// Add other resources
super.findResources(name).asSequence().forEach { result += it }
return Collections.enumeration(result)
}
private fun wrapToUrl(path: String, bytes: ByteArray): URL {
return URL("repl", null, 0, path, object : URLStreamHandler() {
override fun openConnection(u: URL) = object : URLConnection(u) {
override fun connect() {}
override fun getInputStream() = ByteArrayInputStream(bytes)
}
})
}
fun addClass(className: JvmClassName, bytes: ByteArray) {
classes.put(className, bytes)?.let {
throw IllegalStateException("Rewrite at key " + className)
}
}
fun dumpClasses(writer: PrintWriter) {
for (classBytes in classes.values) {
ClassReader(classBytes).accept(TraceClassVisitor(writer), 0)
}
}
}

View File

@@ -20,16 +20,14 @@ import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
interface ILineId : Comparable<ILineId> {
val no: Int
val generation: Int
}
data class ReplHistoryRecord<out T> (val id: ILineId, val item: T)
data class ReplHistoryRecord<out T>(val id: ILineId, val item: T)
interface IReplStageHistory<T> : List<ReplHistoryRecord<T>> {
fun peek(): ReplHistoryRecord<T>? = lock.read { lastOrNull() }
fun push(id: ILineId, item: T)
@@ -37,8 +35,12 @@ interface IReplStageHistory<T> : List<ReplHistoryRecord<T>> {
fun pop(): ReplHistoryRecord<T>?
fun verifiedPop(id: ILineId): ReplHistoryRecord<T>? = lock.write {
if (lastOrNull()?.id == id) pop()
else null
if (lastOrNull()?.id == id) {
pop()
}
else {
null
}
}
fun reset(): Iterable<ILineId>
@@ -55,26 +57,52 @@ interface IReplStageState<T> {
val currentGeneration: Int
fun getNextLineNo(): Int = history.peek()?.id?.no?.let { it + 1 } ?: REPL_CODE_LINE_FIRST_NO // TODO: it should be more robust downstream (e.g. use atomic)
fun getNextLineNo(): Int {
// TODO: it should be more robust downstream (e.g. use atomic)
return history.peek()?.id?.no?.let { it + 1 } ?: REPL_CODE_LINE_FIRST_NO
}
fun <StateT : IReplStageState<*>> asState(target: Class<out StateT>): StateT =
if (target.isAssignableFrom(this::class.java)) this as StateT
else throw IllegalArgumentException("$this is not an expected instance of IReplStageState")
fun <StateT : IReplStageState<*>> asState(target: Class<out StateT>): StateT {
return if (target.isAssignableFrom(this::class.java)) {
@Suppress("UNCHECKED_CAST")
this as StateT
}
else {
throw IllegalArgumentException("$this is not an expected instance of IReplStageState")
}
}
}
inline fun <reified StateT : IReplStageState<*>> IReplStageState<*>.asState(): StateT {
return asState(StateT::class.java)
}
fun <T> IReplStageHistory<T>.firstMismatch(other: Sequence<ILineId>): Pair<ReplHistoryRecord<T>?, ILineId?>? =
lock.read {
iterator().asSequence().zip(other.asSequence()).firstOrNull { it.first.id != it.second }?.let { it.first to it.second }
fun <T> IReplStageHistory<T>.firstMismatch(other: Sequence<ILineId>): Pair<ReplHistoryRecord<T>?, ILineId?>? = lock.read {
iterator().asSequence()
.zip(other.asSequence())
.firstOrNull { it.first.id != it.second }
?.let { it.first to it.second }
}
fun<T> IReplStageHistory<T>.firstMismatchFiltered(other: Sequence<ILineId>, predicate: (ReplHistoryRecord<T>) -> Boolean): Pair<ReplHistoryRecord<T>?, ILineId?>? =
lock.read {
iterator().asSequence().filter(predicate).zip(other.asSequence()).firstOrNull { it.first.id != it.second }?.let { it.first to it.second }
fun<T> IReplStageHistory<T>.firstMismatchFiltered(
other: Sequence<ILineId>,
predicate: (ReplHistoryRecord<T>) -> Boolean
): Pair<ReplHistoryRecord<T>?, ILineId?>? = lock.read {
iterator().asSequence()
.filter(predicate)
.zip(other.asSequence())
.firstOrNull { it.first.id != it.second }
?.let { it.first to it.second }
}
fun<T> IReplStageHistory<T>.firstMismatchWhile(other: Sequence<ILineId>, predicate: (ReplHistoryRecord<T>) -> Boolean): Pair<ReplHistoryRecord<T>?, ILineId?>? =
fun<T> IReplStageHistory<T>.firstMismatchWhile(
other: Sequence<ILineId>,
predicate: (ReplHistoryRecord<T>) -> Boolean
): Pair<ReplHistoryRecord<T>?, ILineId?>? =
lock.read {
iterator().asSequence().takeWhile(predicate).zip(other.asSequence()).firstOrNull { it.first.id != it.second }?.let { it.first to it.second }
}
iterator().asSequence()
.takeWhile(predicate)
.zip(other.asSequence())
.firstOrNull { it.first.id != it.second }
?.let { it.first to it.second }
}

View File

@@ -39,7 +39,7 @@ import org.jetbrains.kotlin.cli.jvm.config.addJavaSourceRoot
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem
import org.jetbrains.kotlin.cli.jvm.plugins.PluginCliParser
import org.jetbrains.kotlin.cli.jvm.repl.ReplFromTerminal
import org.jetbrains.kotlin.cli.jvm.repl.KotlinRepl
import org.jetbrains.kotlin.codegen.CompilationException
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
@@ -103,7 +103,7 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
messageCollector.report(ERROR, "Specify script source path to evaluate")
return COMPILATION_ERROR
}
ReplFromTerminal.run(rootDisposable, configuration)
KotlinRepl.run(rootDisposable, configuration)
return ExitCode.OK
}
}

View File

@@ -1,63 +0,0 @@
/*
* Copyright 2010-2015 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.jvm.repl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.descriptors.ScriptDescriptor;
public class EarlierLine {
@NotNull
private final String code;
@NotNull
private final ScriptDescriptor scriptDescriptor;
@NotNull
private final Class<?> scriptClass;
@NotNull
private final Object scriptInstance;
public EarlierLine(
@NotNull String code,
@NotNull ScriptDescriptor scriptDescriptor,
@NotNull Class<?> scriptClass,
@NotNull Object scriptInstance
) {
this.code = code;
this.scriptDescriptor = scriptDescriptor;
this.scriptClass = scriptClass;
this.scriptInstance = scriptInstance;
}
@NotNull
public String getCode() {
return code;
}
@NotNull
public ScriptDescriptor getScriptDescriptor() {
return scriptDescriptor;
}
@NotNull
public Class<?> getScriptClass() {
return scriptClass;
}
@NotNull
public Object getScriptInstance() {
return scriptInstance;
}
}

View File

@@ -62,11 +62,14 @@ abstract class GenericReplCheckerState: IReplStageState<ScriptDescriptor> {
var lastLineState: LineState? = null // for transferring state to the compiler in most typical case
}
class GenericReplCompilerState(environment: KotlinCoreEnvironment, override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()) : IReplStageState<ScriptDescriptor>, GenericReplCheckerState() {
class GenericReplCompilerState(
environment: KotlinCoreEnvironment,
override val lock: ReentrantReadWriteLock
) : IReplStageState<ScriptDescriptor>, GenericReplCheckerState() {
override val history = ReplCompilerStageHistory(this)
override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
override val currentGeneration: Int
get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
val analyzerEngine = ReplCodeAnalyzer(environment)

View File

@@ -29,7 +29,7 @@ import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplTerminalDiagnosticMessageHolder
import org.jetbrains.kotlin.cli.jvm.repl.messages.ConsoleDiagnosticMessageHolder
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.JvmTarget
@@ -43,8 +43,8 @@ const val KOTLIN_REPL_JVM_TARGET_PROPERTY = "kotlin.repl.jvm.target"
open class GenericReplChecker(
disposable: Disposable,
val scriptDefinition: KotlinScriptDefinition,
val compilerConfiguration: CompilerConfiguration,
private val scriptDefinition: KotlinScriptDefinition,
private val compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector
) : ReplCheckAction {
@@ -65,11 +65,11 @@ open class GenericReplChecker(
private val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
internal fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder()
private fun createDiagnosticHolder() = ConsoleDiagnosticMessageHolder()
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult {
state.lock.write {
val checkerState = state.asState(GenericReplCheckerState::class.java)
val checkerState = state.asState<GenericReplCheckerState>()
val scriptFileName = makeScriptBaseName(codeLine)
val virtualFile =
LightVirtualFile("$scriptFileName${KotlinParserDefinition.STD_SCRIPT_EXT}", KotlinLanguage.INSTANCE, StringUtil.convertLineSeparators(codeLine.code)).apply {
@@ -88,7 +88,7 @@ open class GenericReplChecker(
return when {
syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof -> ReplCheckResult.Incomplete()
syntaxErrorReport.isHasErrors -> ReplCheckResult.Error(errorHolder.renderedDiagnostics)
syntaxErrorReport.isHasErrors -> ReplCheckResult.Error(errorHolder.renderMessage())
else -> ReplCheckResult.Ok()
}
}

View File

@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.cli.jvm.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.repl.*
@@ -35,15 +36,20 @@ import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.ScriptDependenciesProvider
import org.jetbrains.kotlin.utils.repl.IS_REPL_SNIPPET
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.util.TraceClassVisitor
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.PrintWriter
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
// WARNING: not thread safe, assuming external synchronization
open class GenericReplCompiler(disposable: Disposable,
protected val scriptDefinition: KotlinScriptDefinition,
protected val compilerConfiguration: CompilerConfiguration,
scriptDefinition: KotlinScriptDefinition,
private val compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector
) : ReplCompiler {
@@ -56,9 +62,16 @@ open class GenericReplCompiler(disposable: Disposable,
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = checker.check(state, codeLine)
fun extractSnippetExpression(file: PsiFile): KtExpression? {
return file.getChildOfType<KtScript>()
?.getChildOfType<KtBlockExpression>()
?.getChildOfType<KtScriptInitializer>()
?.getChildOfType<KtExpression>()
}
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult {
state.lock.write {
val compilerState = state.asState(GenericReplCompilerState::class.java)
val compilerState = state.asState<GenericReplCompilerState>()
val (psiFile, errorHolder) = run {
if (compilerState.lastLineState == null || compilerState.lastLineState!!.codeLine != codeLine) {
@@ -79,10 +92,14 @@ open class GenericReplCompiler(disposable: Disposable,
classpathAddendum = newDependencies?.let { checker.environment.updateClasspath(it.classpath.map(::JvmClasspathRoot)) }
}
val ktScript = psiFile.getChildOfType<KtScript>()?.also { ktScript ->
ktScript.putUserData(IS_REPL_SNIPPET, true)
}
val analysisResult = compilerState.analyzerEngine.analyzeReplLine(psiFile, codeLine)
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
val scriptDescriptor = when (analysisResult) {
is ReplCodeAnalyzer.ReplLineAnalysisResult.WithErrors -> return ReplCompileResult.Error(errorHolder.renderedDiagnostics)
is ReplCodeAnalyzer.ReplLineAnalysisResult.WithErrors -> return ReplCompileResult.Error(errorHolder.renderMessage())
is ReplCodeAnalyzer.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
else -> error("Unexpected result ${analysisResult::class.java}")
}
@@ -107,10 +124,7 @@ open class GenericReplCompiler(disposable: Disposable,
val generatedClassname = makeScriptBaseName(codeLine)
compilerState.history.push(LineId(codeLine), scriptDescriptor)
val expression = psiFile.getChildOfType<KtScript>()?.
getChildOfType<KtBlockExpression>()?.
getChildOfType<KtScriptInitializer>()?.
getChildOfType<KtExpression>()
val expression = extractSnippetExpression(psiFile)
val type = expression?.let {
compilerState.analyzerEngine.trace.bindingContext.getType(it)

View File

@@ -0,0 +1,162 @@
/*
* 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.jvm.repl
import com.google.common.hash.Hashing
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtPsiFactory
import java.io.File
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.util.jar.JarFile
class KotlinLibraryNameCache(val project: Project) {
private companion object {
val cacheDirectory = File(
System.getProperty("user.home")
?: error("Can't find home directory"), ".kotlin/repl/libraryCache"
).apply { mkdirs() }
}
val ktFactory = KtPsiFactory(project)
private val librariesWithSources = mutableMapOf<File, Boolean>()
private val libraryCache = mutableMapOf<File, LibraryNames>()
fun getEntryName(library: File, packageName: String, declarationName: String): String? {
processAndCacheIfNeeded(library)
val libraryNames = libraryCache[library] ?: return null
return (libraryNames[packageName] ?: return null)[declarationName]
}
fun hasSources(library: File): Boolean {
processAndCacheIfNeeded(library)
return librariesWithSources[library] ?: false
}
private fun processAndCacheIfNeeded(library: File) {
val cacheFile = File(cacheDirectory, getFileIdentifier(library))
if (!cacheFile.exists()) {
processAndCache(library)
} else if (library !in libraryCache) {
loadLibraryFromCache(library, cacheFile)
}
}
private fun loadLibraryFromCache(library: File, cacheFile: File) {
val (sourceFilesFound, libraryNames) = cacheFile.inputStream().use {
ObjectInputStream(it).run {
@Suppress("UNCHECKED_CAST")
Pair(readBoolean(), readObject() as? LibraryNames)
}
}
libraryCache[library] = libraryNames ?: emptyMap()
librariesWithSources[library] = sourceFilesFound
}
private fun processAndCache(library: File): LibraryNames {
if (!library.isFile) {
return emptyMap()
}
try {
val namesForFile: MutableLibraryNames = mutableMapOf()
val sourceFilesFound = processLibrary(library, namesForFile)
libraryCache[library] = namesForFile
librariesWithSources[library] = sourceFilesFound
File(cacheDirectory, getFileIdentifier(library)).outputStream().use { os ->
ObjectOutputStream(os).use { oos ->
oos.writeBoolean(sourceFilesFound)
oos.writeObject(namesForFile)
}
}
return namesForFile
} catch (e: Throwable) {
throw RuntimeException("Can not process $library", e)
}
}
private fun getFileIdentifier(file: File): String {
return sha1(file.absolutePath + file.length() + file.lastModified())
}
private fun processLibrary(file: File, consumer: MutableLibraryNames): Boolean {
var sourceFilesFound = false
JarFile(file).use { jar ->
for (entry in jar.entries()) {
val extension = entry.name.substringAfterLast('.', missingDelimiterValue = "")
if (!sourceFilesFound && (extension == "kt") || extension == "java") {
sourceFilesFound = true
}
fun readEntryContents() = jar.getInputStream(entry).reader().readText()
if (extension == "kt") {
val (packageName, declarationNames) = scanKotlinSource(readEntryContents())
if (declarationNames.isEmpty()) {
continue
}
val packageData = consumer.getOrPut(packageName, { mutableMapOf() })
declarationNames.forEach { packageData[it] = entry.name }
}
}
}
return sourceFilesFound
}
private fun scanKotlinSource(source: String): FileNames {
val ktFile = ktFactory.createFile(source)
val declarationNames = hashSetOf<String>()
fun processDeclaration(prefix: String, declaration: KtDeclaration) {
if (declaration.docComment == null) {
return
}
declarationNames += prefix + declaration.name
if (declaration is KtClassOrObject) {
val newPrefix = prefix + declaration.name + "."
declaration.declarations.forEach { processDeclaration(newPrefix, it) }
}
}
ktFile.declarations.forEach { processDeclaration("", it) }
return FileNames(ktFile.packageFqName.asString(), declarationNames)
}
}
private typealias LibraryNames = Map<String, Map<String, String>>
private typealias MutableLibraryNames = MutableMap<String, MutableMap<String, String>>
private data class FileNames(val packageName: String, val declarationNames: Set<String>)
private fun sha1(text: String): String {
return Hashing.sha1().hashBytes(text.toByteArray()).toString()
}

View File

@@ -0,0 +1,98 @@
/*
* 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.jvm.repl
import com.intellij.openapi.Disposable
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.GroupingMessageCollector
import org.jetbrains.kotlin.cli.jvm.repl.configuration.ConsoleReplConfiguration
import org.jetbrains.kotlin.cli.jvm.repl.configuration.ReplConfiguration
import org.jetbrains.kotlin.cli.jvm.repl.configuration.IdeReplConfiguration
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplProcessor
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.KotlinCompilerVersion
import java.util.concurrent.Callable
import java.util.concurrent.Executors
import java.util.concurrent.Future
class KotlinRepl(
disposable: Disposable,
compilerConfiguration: CompilerConfiguration,
private val replConfiguration: ReplConfiguration
) {
private val replInitializer: Future<ReplInterpreter> = Executors.newSingleThreadExecutor().submit(Callable {
ReplInterpreter(disposable, compilerConfiguration, replConfiguration)
})
private val replInterpreter: ReplInterpreter
get() = replInitializer.get()
val writer get() = replConfiguration.writer
private val messageCollector = compilerConfiguration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
private fun doRun() {
try {
with(writer) {
printlnWelcomeMessage(
"Welcome to Kotlin version ${KotlinCompilerVersion.VERSION} " +
"(JRE ${System.getProperty("java.runtime.version")})"
)
printlnWelcomeMessage("Type :help for help, :quit for quit")
}
// Display compiler messages related to configuration and CLI arguments, quit if there are errors
val hasErrors = messageCollector.hasErrors()
(messageCollector as? GroupingMessageCollector)?.flush()
if (hasErrors) return
val processor = ReplProcessor(replInterpreter, replConfiguration)
replConfiguration.reader.runLoop(processor)
} catch (e: Exception) {
replConfiguration.exceptionReporter.report(e)
throw e
} finally {
try {
replConfiguration.reader.flushHistory()
} catch (e: Exception) {
replConfiguration.exceptionReporter.report(e)
throw e
}
}
}
enum class WhatNextAfterOneLine {
READ_LINE,
INCOMPLETE,
QUIT
}
companion object {
fun run(disposable: Disposable, configuration: CompilerConfiguration) {
val replIdeMode = System.getProperty("kotlin.repl.ideMode") == "true"
val replConfiguration = if (replIdeMode) IdeReplConfiguration() else ConsoleReplConfiguration()
return try {
KotlinRepl(disposable, configuration, replConfiguration).doRun()
} catch (e: Exception) {
replConfiguration.exceptionReporter.report(e)
throw e
}
}
}
}

View File

@@ -0,0 +1,165 @@
/*
* 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.jvm.repl
import com.intellij.lang.Language
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
import java.io.File
import java.util.jar.JarFile
class LibraryDocInfoProvider(val project: Project) {
private companion object {
private val LINE_SEPARATOR = System.getProperty("line.separator")
private val FILTERED_JAVA_TAGS = listOf("see", "author", "since")
}
private val kotlinLibraryCache = KotlinLibraryNameCache(project)
private val javaFileFactory = PsiFileFactory.getInstance(project)
fun getDoc(language: Language, libraries: List<File>, packageName: String, declarationName: String): String? {
assert(language == KotlinLanguage.INSTANCE || language == JavaLanguage.INSTANCE)
for (library in libraries) {
// Checks also for .java source files
if (!kotlinLibraryCache.hasSources(library)) {
continue
}
if (language == KotlinLanguage.INSTANCE) {
getKotlinDoc(library, packageName, declarationName)?.let { return it }
}
if (language == JavaLanguage.INSTANCE) {
getJavaDoc(library, packageName, declarationName)?.let { return it }
}
}
return null
}
private fun getKotlinDoc(library: File, packageName: String, declarationName: String): String? {
assert(declarationName.isNotEmpty())
val entryName = kotlinLibraryCache.getEntryName(library, packageName, declarationName) ?: return null
JarFile(library).use { jar ->
assert(entryName.toLowerCase().endsWith(".kt"))
val entry = jar.getJarEntry(entryName) ?: return null
val ktFile = kotlinLibraryCache.ktFactory.createFile(jar.getInputStream(entry).reader().readText())
fun findDeclaration(current: KtElement, path: List<String>): KtDeclaration? {
if (path.isEmpty() && current is KtNamedDeclaration) {
return current
}
if (current is KtDeclarationContainer) {
val targetName = path.first()
for (declaration in current.declarations) {
if (declaration is KtNamedDeclaration && declaration.name == targetName) {
return findDeclaration(declaration, path.drop(1))
}
}
}
return null
}
val kdoc = findDeclaration(ktFile, declarationName.split('.'))?.docComment ?: return null
return kdoc.getChildrenOfType<KDocSection>().firstOrNull()?.text?.let { prettifyKdoc(it) }
}
}
private fun getJavaDoc(library: File, packageName: String, declarationName: String): String? {
JarFile(library).use { jar ->
val segments = declarationName.split('.')
// If the file has more than one top-level class, only the public one will be found
val entryName = packageName.replace('.', '/') + (if (packageName.isEmpty()) "" else "/") + segments.first() + ".java"
val entry = jar.getJarEntry(entryName) ?: return null
val fileText = jar.getInputStream(entry).reader().readText()
val javaFile = javaFileFactory.createFileFromText(entry.name, JavaLanguage.INSTANCE, fileText) as? PsiJavaFile ?: return null
val topLevelClass = javaFile.classes.firstOrNull { it.name == segments.first() } ?: return null
fun findDeclaration(element: PsiElement, segments: List<String>): PsiJavaDocumentedElement? {
if (segments.isEmpty() && element is PsiJavaDocumentedElement) {
return element
}
assert(segments.isNotEmpty())
val targetName = segments.first()
if (element is PsiClass) {
val innerClass = element.innerClasses.firstOrNull { it.name == targetName }
if (innerClass != null) {
return findDeclaration(innerClass, segments.drop(1))
}
if (segments.size > 1) {
return null
}
val field = element.fields.firstOrNull { it.name == targetName }
if (field != null) {
return field
}
val method = element.methods.firstOrNull { it.name == targetName }
if (method != null) {
return method
}
return null
}
return null
}
val javadoc = findDeclaration(topLevelClass, segments.drop(1))?.docComment ?: return null
return prettifyJavaDoc(javadoc.text)
}
}
private fun prettifyKdoc(rawString: String): String {
val withoutLeadingStars = rawString.trimMargin("*")
return withoutLeadingStars
.lines()
.joinToString(LINE_SEPARATOR) { it.trim() }
.replace(Regex("\n\n\n+"), "\n\n")
}
private fun prettifyJavaDoc(rawString: String): String {
val withoutCommentSymbols = rawString.trim().substringAfter("/**").substringBeforeLast("*/").trim()
val withoutHtmlTags = withoutCommentSymbols.replace(Regex("<[^A-Z>]*>"), "")
val withoutLeadingStars = withoutHtmlTags.trimMargin("*")
return withoutLeadingStars.lines()
.map { it.trim() }
.filter { line -> FILTERED_JAVA_TAGS.none { line.startsWith("@$it ") } }
.joinToString(LINE_SEPARATOR)
.replace(Regex(" +"), " ")
.replace(Regex("\n\n\n+"), "\n\n")
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.jvm.repl
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import java.io.File
class LibrarySourcesSearcher(private val compilerConfiguration: CompilerConfiguration) {
fun findAdditionalSourceJars(classpath: List<File>): List<File> {
return listOfNotNull(findKotlinStdlibSources(classpath), findJdkSources())
}
private fun findJdkSources(): File? {
var jdkHome = compilerConfiguration[JVMConfigurationKeys.JDK_HOME] ?: return null
if (jdkHome.name == "jre") {
jdkHome = jdkHome.parentFile ?: return null
}
return File(jdkHome, "src.zip").takeIf { it.exists() } ?: return null
}
private fun findKotlinStdlibSources(classpath: List<File>): File? {
fun File.getClassifier(): String {
val nameWithoutExtension = this.nameWithoutExtension
return when {
nameWithoutExtension.endsWith("-sources") -> "sources"
nameWithoutExtension.endsWith("-javadoc") -> "javadoc"
else -> "" // kotlin-stdlib does not have other classifiers for now
}
}
var stdlibSourcesJar: File? = null
val classpathJars = classpath.filter { it.extension.toLowerCase() == "jar" }
val sourceJars = classpathJars.filter { it.getClassifier() == "sources" }
val classesJars = classpathJars.filter { it.getClassifier() == "" }
classesJars.find { it.name.startsWith("kotlin-stdlib") }?.let { kotlinStdlibJar ->
if (sourceJars.any { it.name.startsWith("kotlin-stdlib-") }) {
// There is already sources jar in classpath
return@let
}
val probablySourcesJar = File(kotlinStdlibJar.parentFile, kotlinStdlibJar.nameWithoutExtension + "-sources.jar")
if (probablySourcesJar.exists()) {
stdlibSourcesJar = probablySourcesJar
}
else {
// We are probably inside the .gradle dir
val versionDir = kotlinStdlibJar.parentFile?.parentFile
val artifactDir = versionDir?.parentFile
val groupDir = artifactDir?.parentFile
// .gradle/caches/modules-2/files-2.1/groupName/artifactName/versionName/separateDirsForArtifacts
val dotGradleDir = groupDir?.parentFile?.parentFile?.parentFile?.parentFile
if (dotGradleDir != null
&& dotGradleDir.name == ".gradle"
&& groupDir.name == "org.jetbrains.kotlin"
&& artifactDir.name == "kotlin-stdlib"
) {
versionDir.listFiles { f: File -> f.isDirectory }
.flatMap { (it.listFiles() ?: emptyArray()).asList() }
.firstOrNull { it.isFile && it.extension.toLowerCase() == "jar" && it.getClassifier() == "sources" }
?.let { stdlibSourcesJar = it }
}
}
}
return stdlibSourcesJar
}
}

View File

@@ -18,6 +18,7 @@
package org.jetbrains.kotlin.cli.jvm.repl
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.cli.common.repl.CompiledReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ILineId
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
@@ -25,37 +26,39 @@ import org.jetbrains.kotlin.cli.common.repl.ReplHistory
import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingTraceContext
import org.jetbrains.kotlin.resolve.LazyTopDownAnalyzer
import org.jetbrains.kotlin.resolve.TopDownAnalysisContext
import org.jetbrains.kotlin.resolve.TopDownAnalysisMode
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.lazy.*
import org.jetbrains.kotlin.resolve.lazy.data.KtClassLikeInfo
import org.jetbrains.kotlin.resolve.lazy.declarations.*
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyScriptDescriptor
import org.jetbrains.kotlin.resolve.scopes.ImportingScope
import org.jetbrains.kotlin.resolve.scopes.*
import org.jetbrains.kotlin.resolve.scopes.utils.parentsWithSelf
import org.jetbrains.kotlin.resolve.scopes.utils.replaceImportingScopes
import org.jetbrains.kotlin.script.ScriptPriorities
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
private val topDownAnalysisContext: TopDownAnalysisContext
private val topDownAnalyzer: LazyTopDownAnalyzer
private val resolveSession: ResolveSession
private val scriptDeclarationFactory: ScriptMutableDeclarationProviderFactory
private val replState = ResettableAnalyzerState()
val qualifiedExpressionResolver: QualifiedExpressionResolver
val project: Project
val module: ModuleDescriptorImpl
val componentProvider: ComponentProvider
val trace: BindingTraceContext = CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace()
init {
@@ -71,9 +74,13 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
{ _, _ -> ScriptMutableDeclarationProviderFactory() }
)
this.project = environment.project
this.componentProvider = container
this.module = container.get<ModuleDescriptorImpl>()
this.scriptDeclarationFactory = container.get<ScriptMutableDeclarationProviderFactory>()
this.resolveSession = container.get<ResolveSession>()
this.qualifiedExpressionResolver = container.get<QualifiedExpressionResolver>()
this.topDownAnalysisContext = TopDownAnalysisContext(
TopDownAnalysisMode.TopLevelDeclarations, DataFlowInfoFactory.EMPTY, resolveSession.declarationScopeProvider
)
@@ -91,6 +98,12 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
}
}
fun lastSuccessfulLine() = replState.lastSuccessfulLine()
fun setDelegateFactory(linePsi: KtFile) {
scriptDeclarationFactory.setDelegateFactory(FileBasedDeclarationProviderFactory(resolveSession.storageManager, listOf(linePsi)))
}
fun resetToLine(lineId: ILineId): List<ReplCodeLine> = replState.resetToLine(lineId)
fun reset(): List<ReplCodeLine> = replState.reset()
@@ -105,7 +118,7 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
}
private fun doAnalyze(linePsi: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult {
scriptDeclarationFactory.setDelegateFactory(FileBasedDeclarationProviderFactory(resolveSession.storageManager, listOf(linePsi)))
setDelegateFactory(linePsi)
replState.submitLine(linePsi, codeLine)
val context = topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi))
@@ -173,6 +186,8 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
private val successfulLines = ReplHistory<LineInfo.SuccessfulLine>()
private val submittedLines = hashMapOf<KtFile, LineInfo>()
fun lastSuccessfulLine() = successfulLines.lastValue()?.lineDescriptor
fun resetToLine(lineId: ILineId): List<ReplCodeLine> {
val removed = successfulLines.resetToLine(lineId.no)
removed.forEach { submittedLines.remove(it.second.linePsi) }
@@ -217,12 +232,23 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
}
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 previousLineDescriptor = lineInfo.parentLine?.lineDescriptor ?: return null
val lexicalScopeAfterLastLine = previousLineDescriptor.scopeForInitializerResolution
val lastLineImports = lexicalScopeAfterLastLine.parentsWithSelf.firstIsInstance<ImportingScope>()
val scopesForThisLine = fileScopeFactory.createScopesForFile(lineInfo.linePsi, lastLineImports)
val combinedLexicalScopes = lexicalScopeAfterLastLine.replaceImportingScopes(scopesForThisLine.importingScope)
return FileScopes(combinedLexicalScopes, scopesForThisLine.importingScope, scopesForThisLine.importResolver)
// Create scope that wraps previous line lexical scope and adds imports from this line
val newScope = LexicalChainedScope(
lexicalScopeAfterLastLine.replaceImportingScopes(scopesForThisLine.importingScope),
previousLineDescriptor,
false,
null,
LexicalScopeKind.CODE_BLOCK,
listOf(previousLineDescriptor.unsubstitutedMemberScope))
return FileScopes(newScope, scopesForThisLine.importingScope, scopesForThisLine.importResolver)
}
}
}

View File

@@ -1,112 +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.jvm.repl
import org.jetbrains.kotlin.cli.jvm.repl.messages.*
import org.jetbrains.kotlin.cli.jvm.repl.reader.ConsoleReplCommandReader
import org.jetbrains.kotlin.cli.jvm.repl.reader.IdeReplCommandReader
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommandReader
import java.io.PrintWriter
import java.io.StringWriter
interface ReplConfiguration {
val writer: ReplWriter
val errorLogger: ReplErrorLogger
val commandReader: ReplCommandReader
val allowIncompleteLines: Boolean
fun createDiagnosticHolder(): DiagnosticMessageHolder
fun onUserCodeExecuting(isExecuting: Boolean)
}
class ReplForIdeConfiguration : ReplConfiguration {
override val allowIncompleteLines: Boolean
get() = false
override fun onUserCodeExecuting(isExecuting: Boolean) {
sinWrapper.isReplScriptExecuting = isExecuting
}
override fun createDiagnosticHolder() = ReplIdeDiagnosticMessageHolder()
override val writer: ReplWriter
override val errorLogger: ReplErrorLogger
override val commandReader: ReplCommandReader
val sinWrapper: ReplSystemInWrapper
init {
// wrapper for `out` is required to escape every input in [ideMode];
// if [ideMode == false] then just redirects all input to [System.out]
// if user calls [System.setOut(...)] then undefined behaviour
val soutWrapper = ReplSystemOutWrapperForIde(System.out)
System.setOut(soutWrapper)
// wrapper for `in` is required to give user possibility of calling
// [readLine] from ide-console repl
sinWrapper = ReplSystemInWrapper(System.`in`, soutWrapper)
System.setIn(sinWrapper)
writer = soutWrapper
errorLogger = IdeReplErrorLogger(writer)
commandReader = IdeReplCommandReader()
}
}
class ConsoleReplConfiguration : ReplConfiguration {
override val allowIncompleteLines: Boolean
get() = true
override fun onUserCodeExecuting(isExecuting: Boolean) {
// do nothing
}
override fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder()
override val writer: ReplWriter
override val errorLogger: ReplErrorLogger
override val commandReader: ReplCommandReader
init {
writer = ReplConsoleWriter()
errorLogger = object : ReplErrorLogger {
override fun logException(e: Throwable): Nothing {
throw e
}
}
commandReader = ConsoleReplCommandReader()
}
}
interface ReplErrorLogger {
fun logException(e: Throwable): Nothing
}
class IdeReplErrorLogger(private val replWriter: ReplWriter) : ReplErrorLogger {
override fun logException(e: Throwable): Nothing {
val errorStringWriter = StringWriter()
val errorPrintWriter = PrintWriter(errorStringWriter)
e.printStackTrace(errorPrintWriter)
val writerString = errorStringWriter.toString()
val internalErrorText = if (writerString.isEmpty()) "Unknown error" else writerString
replWriter.sendInternalErrorReport(internalErrorText)
throw e
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.jvm.repl
import org.jetbrains.kotlin.cli.jvm.repl.writer.ReplWriter
import java.io.PrintWriter
import java.io.StringWriter
interface ReplExceptionReporter {
fun report(e: Throwable)
companion object DoNothing : ReplExceptionReporter {
override fun report(e: Throwable) {}
}
}
class IdeReplExceptionReporter(private val replWriter: ReplWriter) : ReplExceptionReporter {
override fun report(e: Throwable) {
val stringWriter = StringWriter()
val printWriter = PrintWriter(stringWriter)
e.printStackTrace(printWriter)
val writerString = stringWriter.toString()
val internalErrorText = if (writerString.isEmpty()) "Unknown error" else writerString
replWriter.sendInternalErrorReport(internalErrorText)
}
}

View File

@@ -1,170 +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.jvm.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.GroupingMessageCollector
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult
import org.jetbrains.kotlin.cli.jvm.repl.messages.unescapeLineBreaks
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.KotlinCompilerVersion
import java.io.File
import java.io.PrintWriter
import java.util.*
import java.util.concurrent.Callable
import java.util.concurrent.Executors
import java.util.concurrent.Future
class ReplFromTerminal(
disposable: Disposable,
compilerConfiguration: CompilerConfiguration,
replConfiguration: ReplConfiguration
) : ReplConfiguration by replConfiguration {
private val replInitializer: Future<ReplInterpreter> = Executors.newSingleThreadExecutor().submit(Callable {
ReplInterpreter(disposable, compilerConfiguration, replConfiguration)
})
private val replInterpreter: ReplInterpreter
get() = replInitializer.get()
private val messageCollector: MessageCollector = compilerConfiguration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
private fun doRun() {
try {
writer.printlnWelcomeMessage("Welcome to Kotlin version ${KotlinCompilerVersion.VERSION} " +
"(JRE ${System.getProperty("java.runtime.version")})")
writer.printlnWelcomeMessage("Type :help for help, :quit for quit")
// Display compiler messages related to configuration and CLI arguments, quit if there are errors
val hasErrors = messageCollector.hasErrors()
(messageCollector as? GroupingMessageCollector)?.flush()
if (hasErrors) return
var next = WhatNextAfterOneLine.READ_LINE
while (true) {
next = one(next)
if (next == WhatNextAfterOneLine.QUIT) {
break
}
}
}
catch (e: Exception) {
errorLogger.logException(e)
}
finally {
try {
commandReader.flushHistory()
}
catch (e: Exception) {
errorLogger.logException(e)
}
}
}
enum class WhatNextAfterOneLine {
READ_LINE,
INCOMPLETE,
QUIT
}
private fun one(next: WhatNextAfterOneLine): WhatNextAfterOneLine {
var line = commandReader.readLine(next) ?: return WhatNextAfterOneLine.QUIT
line = unescapeLineBreaks(line)
if (line.startsWith(":") && (line.length == 1 || line.get(1) != ':')) {
val notQuit = oneCommand(line.substring(1))
return if (notQuit) WhatNextAfterOneLine.READ_LINE else WhatNextAfterOneLine.QUIT
}
val lineResult = eval(line)
return if (lineResult is ReplEvalResult.Incomplete) {
WhatNextAfterOneLine.INCOMPLETE
}
else {
WhatNextAfterOneLine.READ_LINE
}
}
private fun eval(line: String): ReplEvalResult {
val evalResult = replInterpreter.eval(line)
when (evalResult) {
is ReplEvalResult.ValueResult, is ReplEvalResult.UnitResult -> {
writer.notifyCommandSuccess()
if (evalResult is ReplEvalResult.ValueResult) {
writer.outputCommandResult(evalResult.value.toString())
}
}
is ReplEvalResult.Error.Runtime -> writer.outputRuntimeError(evalResult.message)
is ReplEvalResult.Error.CompileTime -> writer.outputRuntimeError(evalResult.message)
is ReplEvalResult.Incomplete -> writer.notifyIncomplete()
}
return evalResult
}
@Throws(Exception::class)
private fun oneCommand(command: String): Boolean {
val split = splitCommand(command)
if (split.size >= 1 && command == "help") {
writer.printlnHelpMessage("Available commands:\n" +
":help show this help\n" +
":quit exit the interpreter\n" +
":dump bytecode dump classes to terminal\n" +
":load <file> load script from specified file")
return true
}
else if (split.size >= 2 && split[0] == "dump" && split[1] == "bytecode") {
replInterpreter.dumpClasses(PrintWriter(System.out))
return true
}
else if (split.size >= 1 && split[0] == "quit") {
return false
}
else if (split.size >= 2 && split[0] == "load") {
val fileName = split[1]
val scriptText = FileUtil.loadFile(File(fileName))
eval(scriptText)
return true
}
else {
writer.printlnHelpMessage("Unknown command\n" + "Type :help for help")
return true
}
}
companion object {
private fun splitCommand(command: String): List<String> {
return Arrays.asList(*command.split(" ".toRegex()).dropLastWhile(String::isEmpty).toTypedArray())
}
fun run(disposable: Disposable, configuration: CompilerConfiguration) {
val replIdeMode = System.getProperty("kotlin.repl.ideMode") == "true"
val replConfiguration = if (replIdeMode) ReplForIdeConfiguration() else ConsoleReplConfiguration()
return try {
ReplFromTerminal(disposable, configuration, replConfiguration).doRun()
}
catch (e: Exception) {
replConfiguration.errorLogger.logException(e)
}
}
}
}

View File

@@ -16,7 +16,10 @@
package org.jetbrains.kotlin.cli.jvm.repl
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.Disposable
import com.intellij.psi.PsiCompiledElement
import org.jetbrains.kotlin.backend.jvm.descriptors.JvmDescriptorWithExtraFlags
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
@@ -24,19 +27,35 @@ import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.cli.jvm.config.JvmModulePathRoot
import org.jetbrains.kotlin.cli.jvm.repl.codeinsight.ReplCodeInsightFacadeImpl
import org.jetbrains.kotlin.cli.jvm.repl.configuration.ReplConfiguration
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.ImportingScope
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.utils.collectDescriptorsFiltered
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import java.io.File
import java.io.PrintWriter
import java.net.URLClassLoader
import java.util.concurrent.atomic.AtomicInteger
class ReplInterpreter(
disposable: Disposable,
private val configuration: CompilerConfiguration,
private val replConfiguration: ReplConfiguration
): ReplConfiguration by replConfiguration {
) {
private val lineNumber = AtomicInteger()
private val previousIncompleteLines = arrayListOf<String>()
@@ -48,7 +67,11 @@ class ReplInterpreter(
else -> null
}
}
private val classLoader = ReplClassLoader(URLClassLoader(classpathRoots.map { it.toURI().toURL() }.toTypedArray(), null))
private val classpathRootsWithSources by lazy {
// This is lazy because compiler configuration is not yet set up propertly when the class is initiated
classpathRoots.let { it + LibrarySourcesSearcher(configuration).findAdditionalSourceJars(it) }
}
private val messageCollector = object : MessageCollector {
private var hasErrors = false
@@ -76,57 +99,173 @@ class ReplInterpreter(
}
// TODO: add script definition with project-based resolving for IDEA repl
private val scriptCompiler: ReplCompiler by lazy {
private val scriptCompiler by lazy {
GenericReplCompiler(disposable, REPL_LINE_AS_SCRIPT_DEFINITION, configuration, messageCollector)
}
private val scriptEvaluator: ReplFullEvaluator by lazy {
GenericReplCompilingEvaluator(scriptCompiler, classpathRoots, classLoader, null, ReplRepeatingMode.REPEAT_ANY_PREVIOUS)
GenericReplCompilingEvaluator(scriptCompiler, classpathRoots, null, null, ReplRepeatingMode.REPEAT_ANY_PREVIOUS)
}
private val docProvider by lazy {
LibraryDocInfoProvider(evalState.asState<GenericReplCompilerState>().analyzerEngine.project)
}
private val evalState by lazy { scriptEvaluator.createState() }
fun eval(line: String): ReplEvalResult {
val fullText = (previousIncompleteLines + line).joinToString(separator = "\n")
val fullText = concatWithPreviousLines(line)
try {
val evalRes = scriptEvaluator.compileAndEval(evalState, ReplCodeLine(lineNumber.getAndIncrement(), 0, fullText), null, object : InvokeWrapper {
override fun <T> invoke(body: () -> T): T = executeUserCode { body() }
override fun <T> invoke(body: () -> T): T = replConfiguration.executionInterceptor.execute(body)
})
when {
evalRes !is ReplEvalResult.Incomplete -> previousIncompleteLines.clear()
allowIncompleteLines -> previousIncompleteLines.add(line)
replConfiguration.allowIncompleteLines -> previousIncompleteLines.add(line)
else -> return ReplEvalResult.Error.CompileTime("incomplete code")
}
return evalRes
}
catch (e: Throwable) {
val writer = PrintWriter(System.err)
classLoader.dumpClasses(writer)
scriptEvaluator.classLoader.dumpClasses(writer)
writer.flush()
throw e
}
}
private fun <T> executeUserCode(body: () -> T): T {
try {
onUserCodeExecuting(true)
return body()
}
finally {
onUserCodeExecuting(false)
fun complete(line: String): Collection<DeclarationDescriptor> {
val compilerState = evalState.asState<GenericReplCompilerState>()
val codeFragment = KtPsiFactory(compilerState.analyzerEngine.project).createExpressionCodeFragment(line, null)
compilerState.analyzerEngine.setDelegateFactory(codeFragment.containingKtFile)
val targetIdentifier =
codeFragment.findElementAt(codeFragment.textLength - 1)
?.getNonStrictParentOfType(KtSimpleNameExpression::class.java)
?: return emptyList()
val codeInsightFacade = ReplCodeInsightFacadeImpl.create(compilerState.analyzerEngine)
return codeInsightFacade.complete(targetIdentifier, "o")
}
fun doc(rawFqName: String): String? {
val compilerState = evalState.asState<GenericReplCompilerState>()
val bindingContext = compilerState.analyzerEngine.trace.bindingContext
val moduleDescriptor = compilerState.analyzerEngine.module
val qualifiedExpressionResolver = compilerState.analyzerEngine.qualifiedExpressionResolver
val codeFragment = KtPsiFactory(compilerState.analyzerEngine.project).createExpressionCodeFragment(rawFqName, null)
compilerState.analyzerEngine.setDelegateFactory(codeFragment.containingKtFile)
val targetExpression = codeFragment.findElementAt(codeFragment.textLength - 1)?.let {
it.getStrictParentOfType<KtQualifiedExpression>()
?: it.getNonStrictParentOfType(KtSimpleNameExpression::class.java)
} ?: return null
val scope = compilerState.analyzerEngine.lastSuccessfulLine()?.scopeForInitializerResolution
?: LexicalScope.Base(ImportingScope.Empty, moduleDescriptor)
val descriptors = when (targetExpression) {
is KtDotQualifiedExpression -> {
val (containingDescriptor, memberName) = qualifiedExpressionResolver
.resolveClassOrPackageInQualifiedExpression(targetExpression, scope, bindingContext)
if (containingDescriptor == null) {
return null
}
when (memberName) {
null -> listOf(containingDescriptor)
else -> {
val scopes = when (containingDescriptor) {
is PackageViewDescriptor -> listOf(containingDescriptor.memberScope)
is PackageFragmentDescriptor -> listOf(containingDescriptor.getMemberScope())
is ClassDescriptor -> listOf(containingDescriptor.unsubstitutedMemberScope, containingDescriptor.staticScope)
is TypeAliasDescriptor -> listOfNotNull(containingDescriptor.classDescriptor?.unsubstitutedMemberScope)
else -> null
} ?: return null
scopes.flatMap { it.getContributedDescriptors(CALLABLES_AND_CLASSIFIERS) { it == memberName } }
.filter { it.name == memberName }
}
}
}
is KtSimpleNameExpression -> {
val a = scope.collectDescriptorsFiltered(
CALLABLES_AND_CLASSIFIERS,
{ it.asString() == targetExpression.getReferencedName() })
a
}
else -> return null
}.takeIf { it.isNotEmpty() } ?: return null
return descriptors.joinToString(LINE_SEPARATOR + LINE_SEPARATOR) { descriptor ->
val fqName = descriptor.fqNameSafe
val documentation = findDocumentation(classpathRootsWithSources, descriptor)
fqName.asString() + LINE_SEPARATOR + documentation.lines().joinToString(LINE_SEPARATOR) { " " + it }
}
}
private fun findDocumentation(libraries: List<File>, descriptor: DeclarationDescriptor): String {
if (descriptor is DeclarationDescriptorWithSource) {
val psi = descriptor.source.getPsi()
// REPL declaration
if (psi != null && psi !is PsiCompiledElement) {
return psi.getChildrenOfType<KDoc>()
.flatMap { it.getChildrenOfType<KDocSection>().asList() }
.joinToString(LINE_SEPARATOR + LINE_SEPARATOR) { it.text.trim() }
}
// Library declaration
val (packageName, declarationName) = splitPackageDeclarationNames(descriptor)
val language = when (descriptor) {
is JavaClassDescriptor, is JvmDescriptorWithExtraFlags, is JavaCallableMemberDescriptor -> JavaLanguage.INSTANCE
else -> KotlinLanguage.INSTANCE
}
docProvider.getDoc(language, libraries, packageName, declarationName)?.let { return it }
}
return "No documentation found."
}
private fun splitPackageDeclarationNames(descriptor: DeclarationDescriptor): Pair<String, String> {
fun getPackageName(descriptor: DeclarationDescriptor): FqName = when (descriptor) {
is PackageFragmentDescriptor -> descriptor.fqName
is PackageViewDescriptor -> descriptor.fqName
else -> getPackageName(descriptor.containingDeclaration ?: error("$descriptor is outside any package"))
}
val fqName = descriptor.fqNameSafe.asString()
val packageName = getPackageName(descriptor).asString()
if (packageName.isEmpty()) {
return Pair("", fqName)
}
assert(fqName.startsWith(packageName + "."))
return Pair(packageName, fqName.drop(packageName.length + 1))
}
private fun concatWithPreviousLines(line: String) = (previousIncompleteLines + line).joinToString(separator = "\n")
fun dumpClasses(out: PrintWriter) {
classLoader.dumpClasses(out)
scriptEvaluator.classLoader.dumpClasses(out)
}
companion object {
private val REPL_LINE_AS_SCRIPT_DEFINITION = object : KotlinScriptDefinition(Any::class) {
override val name = "Kotlin REPL"
}
private val LINE_SEPARATOR = System.getProperty("line.separator")
private val CALLABLES_AND_CLASSIFIERS = DescriptorKindFilter(
DescriptorKindFilter.CALLABLES_MASK or DescriptorKindFilter.CLASSIFIERS_MASK)
}
}

View File

@@ -0,0 +1,155 @@
/*
* 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.jvm.repl.codeinsight
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.cli.common.repl.ReplClassLoader
import org.jetbrains.kotlin.cli.jvm.repl.ReplCodeAnalyzer
import org.jetbrains.kotlin.container.getService
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper
import org.jetbrains.kotlin.idea.resolve.ResolutionFacadeBase
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import java.io.File
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Modifier
import java.lang.reflect.Proxy.getProxyClass
interface ReplCodeInsightFacade {
fun complete(expression: KtSimpleNameExpression, identifierPart: String): Collection<DeclarationDescriptor>
}
class ReplCodeInsightFacadeImpl(replCodeAnalyzer: ReplCodeAnalyzer) : ReplCodeInsightFacade {
companion object {
private val CALLABLES_AND_CLASSIFIERS = DescriptorKindFilter(
DescriptorKindFilter.CALLABLES_MASK or DescriptorKindFilter.CLASSIFIERS_MASK)
fun create(replCodeAnalyzer: ReplCodeAnalyzer): ReplCodeInsightFacade {
val kotlinCodeInsightFile = getKotlinCodeInsightJar()
val classLoaderWithCodeInsight = ReplClassLoader(listOf(kotlinCodeInsightFile), replCodeAnalyzer.javaClass.classLoader)
fun addClass(className: String) {
val bytes = ReplCodeInsightFacadeImpl::class.java.classLoader
.getResource(className.replace('.', '/') + ".class").readBytes()
classLoaderWithCodeInsight.addClass(JvmClassName.byFqNameWithoutInnerClasses(className), bytes)
}
addClass(ReplCodeInsightFacadeImpl::class.java.name)
addClass("org.jetbrains.kotlin.cli.jvm.repl.codeinsight.ResolutionFacadeForRepl")
val actualImplementation = Class.forName(ReplCodeInsightFacadeImpl::class.java.name, true, classLoaderWithCodeInsight)
.declaredConstructors.single().newInstance(replCodeAnalyzer)
val proxyClass = getProxyClass(ReplCodeInsightFacadeImpl::class.java.classLoader, ReplCodeInsightFacade::class.java)
val proxy = proxyClass.constructors.single().newInstance(InvocationHandler { _, method, args ->
val isStatic = Modifier.isStatic(method.modifiers)
fun parameterTypesMatches(first: Array<Class<*>>, second: Array<Class<*>>): Boolean {
if (first.size != second.size) return false
return first.indices.none { first[it] != second[it] }
}
actualImplementation.javaClass.methods
.single { it.name == method.name && parameterTypesMatches(it.parameterTypes, method.parameterTypes) }
.invoke(if (isStatic) null else actualImplementation, *(args ?: emptyArray()))
})
return proxy as ReplCodeInsightFacade
}
private fun getKotlinCodeInsightJar(): File {
System.getProperty("kotlin.code.insight.jar")?.let { return File(it) }
val kotlinCompilerJarFile = getClasspathEntry(ReplCodeAnalyzer::class.java) ?: error("Can't find Kotlin compiler")
val kotlinCodeInsightFile = File(kotlinCompilerJarFile.parentFile, "kotlin-code-insight.jar")
if (!kotlinCodeInsightFile.exists()) {
error("Can't find kotlin-code-insight.jar")
}
return kotlinCodeInsightFile
}
private fun getClasspathEntry(clazz: Class<*>): File? {
val classFileName = clazz.name.replace('.', '/') + ".class"
val resource = clazz.classLoader.getResource(classFileName) ?: return null
return File(
when (resource.protocol?.toLowerCase()) {
"file" -> resource.path
?.takeIf { it.endsWith(classFileName) }
?.removeSuffix(classFileName)
"jar" -> resource.path
.takeIf { it.startsWith("file:", ignoreCase = true) }
?.drop("file:".length)
?.substringBefore("!/")
else -> null
}
)
}
}
private val referenceVariantsHelper = ReferenceVariantsHelper(
replCodeAnalyzer.trace.bindingContext,
ResolutionFacadeForRepl(replCodeAnalyzer),
replCodeAnalyzer.module)
override fun complete(expression: KtSimpleNameExpression, identifierPart: String): Collection<DeclarationDescriptor> {
// TODO do not filter out
return referenceVariantsHelper.getReferenceVariants(expression, CALLABLES_AND_CLASSIFIERS, NameFilter(identifierPart))
}
class NameFilter(val prefix: String) : (Name) -> Boolean {
override fun invoke(name: Name): Boolean {
return !name.isSpecial && applicableNameFor(prefix, name.identifier)
}
private fun applicableNameFor(prefix: String, completion: String): Boolean {
return completion.startsWith(prefix) || completion.toLowerCase().startsWith(prefix)
}
}
}
class ResolutionFacadeForRepl(private val replCodeAnalyzer: ReplCodeAnalyzer) : ResolutionFacadeBase {
override val project: Project
get() = replCodeAnalyzer.project
override val moduleDescriptor: ModuleDescriptor
get() = replCodeAnalyzer.module
override fun <T : Any> getFrontendService(serviceClass: Class<T>): T {
return replCodeAnalyzer.componentProvider.getService(serviceClass)
}
override fun <T : Any> getFrontendService(element: PsiElement, serviceClass: Class<T>): T {
throw NotImplementedError()
}
override fun <T : Any> tryGetFrontendService(element: PsiElement, serviceClass: Class<T>): T? {
throw NotImplementedError()
}
override fun <T : Any> getFrontendService(moduleDescriptor: ModuleDescriptor, serviceClass: Class<T>): T {
throw NotImplementedError()
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. 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.cli.jvm.repl.command
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommand
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommand.*
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommandContext
import java.io.PrintWriter
object ClassesReplCommand : ReplCommand {
override val name = "classes"
override val args = emptyList<String>()
override val help = "dump loaded classes"
override val constraints = ArgumentConstraints.Empty
override fun run(context: ReplCommandContext, rawArgs: String): Boolean {
context.interpreter.dumpClasses(PrintWriter(System.out))
return true
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. 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.cli.jvm.repl.command
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommand
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommand.*
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommandContext
object DocReplCommand : ReplCommand {
override val name = "doc"
override val args = listOf("symbol")
override val help = "display documentation for the given symbol"
override val constraints = ArgumentConstraints.Raw
override fun run(context: ReplCommandContext, rawArgs: String): Boolean {
val docComment = context.interpreter.doc(rawArgs)
context.configuration.writer.printlnHelpMessage(docComment ?: "No documentation found.")
return true
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. 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.cli.jvm.repl.command
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommand
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommand.*
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommandContext
object HelpReplCommand : ReplCommand {
override val name = "help"
override val args = emptyList<String>()
override val help = "display help message"
override val constraints = ArgumentConstraints.Empty
override fun run(context: ReplCommandContext, rawArgs: String): Boolean {
val commands = ReplCommand.COMMANDS.map { (_, command) ->
buildString {
append(command.name)
if (command.args.isNotEmpty()) {
append(' ')
command.args.joinTo(this) { "<$it>" }
}
} to command.help
}
val maxCommandLength = (commands.map { it.first.length }.max() ?: 0) + 4
val renderedCommands = commands
.joinToString("\n") { ":" + it.first + " ".repeat(maxCommandLength - it.first.length) + it.second }
context.configuration.writer.printlnHelpMessage("Available commands:\n" + renderedCommands)
return true
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. 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.cli.jvm.repl.command
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommand
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommand.*
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommandContext
import java.io.File
import java.io.IOException
object LoadReplCommand : ReplCommand {
override val name = "load"
override val args = listOf("file")
override val help = "evaluate a script file"
override val constraints = ArgumentConstraints.Raw
override fun run(context: ReplCommandContext, rawArgs: String): Boolean {
fun printError(text: String) = context.configuration.writer.outputCompileError(text)
val file = File(rawArgs).takeIf { it.isFile } ?: run {
printError("File not exists: ${File(rawArgs).absolutePath}")
return true
}
try {
val scriptText = FileUtil.loadFile(file)
context.interpreter.eval(scriptText)
} catch (e: IOException) {
printError("Can not load script: ${e.message}")
}
return true
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. 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.cli.jvm.repl.command
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommand
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommand.*
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommandContext
object QuitReplCommand : ReplCommand {
override val name = "quit"
override val args = emptyList<String>()
override val help = "quit REPL"
override val constraints = ArgumentConstraints.Empty
override fun run(context: ReplCommandContext, rawArgs: String): Boolean {
if (rawArgs.isNotEmpty()) {
context.configuration.writer.outputCompileError("Expected zero arguments, got $rawArgs")
return true
}
return false
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.jvm.repl.configuration
import org.jetbrains.kotlin.cli.jvm.repl.ReplExceptionReporter
import org.jetbrains.kotlin.cli.jvm.repl.messages.ConsoleDiagnosticMessageHolder
import org.jetbrains.kotlin.cli.jvm.repl.reader.ConsoleReplReader
import org.jetbrains.kotlin.cli.jvm.repl.writer.ConsoleReplWriter
class ConsoleReplConfiguration : ReplConfiguration {
override val writer = ConsoleReplWriter()
override val exceptionReporter
get() = ReplExceptionReporter
override val reader = ConsoleReplReader()
override val allowIncompleteLines: Boolean
get() = true
override val executionInterceptor
get() = SnippetExecutionInterceptor
override fun createDiagnosticHolder() = ConsoleDiagnosticMessageHolder()
}

View File

@@ -0,0 +1,65 @@
/*
* 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.jvm.repl.configuration
import org.jetbrains.kotlin.cli.jvm.repl.IdeReplExceptionReporter
import org.jetbrains.kotlin.cli.jvm.repl.ReplExceptionReporter
import org.jetbrains.kotlin.cli.jvm.repl.messages.IdeDiagnosticMessageHolder
import org.jetbrains.kotlin.cli.jvm.repl.reader.*
import org.jetbrains.kotlin.cli.jvm.repl.writer.IdeSystemOutWrapperReplWriter
import org.jetbrains.kotlin.cli.jvm.repl.writer.ReplWriter
class IdeReplConfiguration : ReplConfiguration {
override val allowIncompleteLines: Boolean
get() = false
override val executionInterceptor: SnippetExecutionInterceptor = object : SnippetExecutionInterceptor {
override fun <T> execute(block: () -> T): T {
try {
sinWrapper.isReplScriptExecuting = true
return block()
} finally {
sinWrapper.isReplScriptExecuting = false
}
}
}
override fun createDiagnosticHolder() = IdeDiagnosticMessageHolder()
override val writer: ReplWriter
override val exceptionReporter: ReplExceptionReporter
override val reader: ReplReader
val sinWrapper: ReplSystemInWrapper
init {
// wrapper for `out` is required to escape every input in [ideMode];
// if [ideMode == false] then just redirects all input to [System.out]
// if user calls [System.setOut(...)] then undefined behaviour
val soutWrapper = IdeSystemOutWrapperReplWriter(System.out)
System.setOut(soutWrapper)
// wrapper for `in` is required to give user possibility of calling
// [readLine] from ide-console repl
sinWrapper = ReplSystemInWrapper(System.`in`, soutWrapper)
System.setIn(sinWrapper)
writer = soutWrapper
exceptionReporter = IdeReplExceptionReporter(writer)
reader = IdeReplReader()
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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.jvm.repl.configuration
import org.jetbrains.kotlin.cli.jvm.repl.ReplExceptionReporter
import org.jetbrains.kotlin.cli.jvm.repl.messages.*
import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplReader
import org.jetbrains.kotlin.cli.jvm.repl.writer.ReplWriter
interface ReplConfiguration {
val reader: ReplReader
val writer: ReplWriter
val exceptionReporter: ReplExceptionReporter
val allowIncompleteLines: Boolean
val executionInterceptor: SnippetExecutionInterceptor
fun createDiagnosticHolder(): DiagnosticMessageHolder
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 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.
@@ -14,11 +14,12 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.repl.reader
package org.jetbrains.kotlin.cli.jvm.repl.configuration
import org.jetbrains.kotlin.cli.jvm.repl.ReplFromTerminal
interface SnippetExecutionInterceptor {
fun <T> execute(block: () -> T): T
interface ReplCommandReader {
fun readLine(next: ReplFromTerminal.WhatNextAfterOneLine): String?
fun flushHistory()
companion object Plain : SnippetExecutionInterceptor {
override fun <T> execute(block: () -> T) = block()
}
}

View File

@@ -22,20 +22,16 @@ import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.nio.ByteBuffer
class ReplTerminalDiagnosticMessageHolder : MessageCollectorBasedReporter, DiagnosticMessageHolder {
class ConsoleDiagnosticMessageHolder : MessageCollectorBasedReporter, DiagnosticMessageHolder {
private val outputStream = ByteArrayOutputStream()
override val messageCollector: GroupingMessageCollector = GroupingMessageCollector(
PrintingMessageCollector(PrintStream(outputStream), MessageRenderer.WITHOUT_PATHS, false),
false
)
false)
override val renderedDiagnostics: String
get() {
messageCollector.flush()
val bytes = outputStream.toByteArray()
return Charsets.UTF_8.decode(ByteBuffer.wrap(bytes)).toString()
}
override fun renderMessage(): String {
messageCollector.flush()
return outputStream.toString("UTF-8")
}
}

View File

@@ -19,5 +19,5 @@ package org.jetbrains.kotlin.cli.jvm.repl.messages
import org.jetbrains.kotlin.cli.common.messages.DiagnosticMessageReporter
interface DiagnosticMessageHolder : DiagnosticMessageReporter {
val renderedDiagnostics: String
fun renderMessage(): String
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2010-2015 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.jvm.repl.messages
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.w3c.dom.ls.DOMImplementationLS
import javax.xml.parsers.DocumentBuilderFactory
class IdeDiagnosticMessageHolder : DiagnosticMessageHolder {
private val diagnostics = arrayListOf<Pair<Diagnostic, String>>()
override fun report(diagnostic: Diagnostic, file: PsiFile, render: String) {
diagnostics.add(Pair(diagnostic, render))
}
override fun renderMessage(): String {
val docFactory = DocumentBuilderFactory.newInstance()
val docBuilder = docFactory.newDocumentBuilder()
val errorReport = docBuilder.newDocument()
val rootElement = errorReport.createElement("report")
errorReport.appendChild(rootElement)
for ((diagnostic, message) in diagnostics) {
val errorRange = DiagnosticUtils.firstRange(diagnostic.textRanges)
val reportEntry = errorReport.createElement("reportEntry")
reportEntry.setAttribute("severity", diagnostic.severity.toString())
reportEntry.setAttribute("rangeStart", errorRange.startOffset.toString())
reportEntry.setAttribute("rangeEnd", errorRange.endOffset.toString())
reportEntry.appendChild(errorReport.createTextNode(StringUtil.escapeXml(message)))
rootElement.appendChild(reportEntry)
}
val domImplementation = errorReport.implementation as DOMImplementationLS
val lsSerializer = domImplementation.createLSSerializer()
return lsSerializer.writeToString(errorReport)
}
}

View File

@@ -1,58 +0,0 @@
/*
* Copyright 2010-2015 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.jvm.repl.messages
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.w3c.dom.ls.DOMImplementationLS
import javax.xml.parsers.DocumentBuilderFactory
class ReplIdeDiagnosticMessageHolder : DiagnosticMessageHolder {
private val diagnostics = arrayListOf<Pair<Diagnostic, String>>()
override fun report(diagnostic: Diagnostic, file: PsiFile, render: String) {
diagnostics.add(Pair(diagnostic, render))
}
override val renderedDiagnostics: String
get() {
val docFactory = DocumentBuilderFactory.newInstance()
val docBuilder = docFactory.newDocumentBuilder()
val errorReport = docBuilder.newDocument()
val rootElement = errorReport.createElement("report")
errorReport.appendChild(rootElement)
for ((diagnostic, message) in diagnostics) {
val errorRange = DiagnosticUtils.firstRange(diagnostic.textRanges)
val reportEntry = errorReport.createElement("reportEntry")
reportEntry.setAttribute("severity", diagnostic.severity.toString())
reportEntry.setAttribute("rangeStart", errorRange.startOffset.toString())
reportEntry.setAttribute("rangeEnd", errorRange.endOffset.toString())
reportEntry.appendChild(errorReport.createTextNode(StringUtil.escapeXml(message)))
rootElement.appendChild(reportEntry)
}
val domImplementation = errorReport.implementation as DOMImplementationLS
val lsSerializer = domImplementation.createLSSerializer()
return lsSerializer.writeToString(errorReport)
}
}

View File

@@ -17,24 +17,9 @@
package org.jetbrains.kotlin.cli.jvm.repl.messages
import com.intellij.openapi.util.text.StringUtil
import org.w3c.dom.Element
import org.xml.sax.InputSource
import java.io.ByteArrayInputStream
import javax.xml.parsers.DocumentBuilderFactory
// using '#' to avoid collisions with xml escaping
val SOURCE_CHARS: Array<String> = arrayOf("\n", "#")
val XML_REPLACEMENTS: Array<String> = arrayOf("#n", "#diez")
fun parseXml(inputMessage: String): String {
fun strToSource(s: String) = InputSource(ByteArrayInputStream(s.toByteArray()))
val docFactory = DocumentBuilderFactory.newInstance()
val docBuilder = docFactory.newDocumentBuilder()
val input = docBuilder.parse(strToSource(inputMessage))
val root = input.firstChild as Element
return root.textContent
}
internal val SOURCE_CHARS: Array<String> = arrayOf("\n", "#")
internal val XML_REPLACEMENTS: Array<String> = arrayOf("#n", "#diez")
fun unescapeLineBreaks(s: String) = StringUtil.replace(s, XML_REPLACEMENTS, SOURCE_CHARS)

View File

@@ -1,61 +0,0 @@
/*
* Copyright 2010-2015 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.jvm.repl.reader
import org.jetbrains.kotlin.cli.jvm.repl.ReplFromTerminal
import org.jline.reader.EndOfFileException
import org.jline.reader.LineReader
import org.jline.reader.LineReaderBuilder
import org.jline.reader.UserInterruptException
import org.jline.terminal.TerminalBuilder
import java.io.File
import java.util.logging.Level
import java.util.logging.Logger
class ConsoleReplCommandReader : ReplCommandReader {
private val lineReader = LineReaderBuilder.builder()
.appName("kotlin")
.terminal(TerminalBuilder.terminal())
.variable(LineReader.HISTORY_FILE, File(File(System.getProperty("user.home")), ".kotlinc_history").absolutePath)
.build()
.apply {
setOpt(LineReader.Option.DISABLE_EVENT_EXPANSION)
}
override fun readLine(next: ReplFromTerminal.WhatNextAfterOneLine): String? {
val prompt = if (next == ReplFromTerminal.WhatNextAfterOneLine.INCOMPLETE) "... " else ">>> "
try {
return lineReader.readLine(prompt)
}
catch (e: UserInterruptException) {
println("<interrupted>")
System.out.flush()
return ""
}
catch (e: EndOfFileException) {
return null
}
}
override fun flushHistory() = lineReader.history.save()
private companion object {
init {
Logger.getLogger("org.jline").level = Level.OFF;
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2010-2015 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.jvm.repl.reader
import org.jetbrains.kotlin.cli.jvm.repl.KotlinRepl
import org.jline.reader.*
import org.jline.terminal.TerminalBuilder
import java.io.File
import java.util.logging.Level
import java.util.logging.Logger
class ConsoleReplReader : ReplReader {
private var processor: ReplProcessor? = null
private val lineReader = LineReaderBuilder.builder()
.appName("kotlin")
.terminal(TerminalBuilder.terminal())
.variable(LineReader.HISTORY_FILE, File(File(System.getProperty("user.home")), ".kotlinc_history").absolutePath)
.completer(::completer)
.build()
.apply {
Logger.getLogger("org.jline").level = Level.OFF
setOpt(LineReader.Option.DISABLE_EVENT_EXPANSION)
}
override fun runLoop(processor: ReplProcessor) {
try {
this.processor = processor
super.runLoop(processor)
} finally {
this.processor = null
}
}
private fun completer(lineReader: LineReader, parsedLine: ParsedLine, candidates: MutableList<Candidate>) {
val processor = this.processor ?: error("ReplProcessor is not initialized")
candidates.addAll(processor.complete(parsedLine.line(), parsedLine.cursor()).map { Candidate(it) })
}
override fun readLine(next: KotlinRepl.WhatNextAfterOneLine): String? {
val prompt = if (next == KotlinRepl.WhatNextAfterOneLine.INCOMPLETE) "... " else ">>> "
return try {
lineReader.readLine(prompt)
} catch (e: UserInterruptException) {
println("<interrupted>")
System.out.flush()
""
} catch (e: EndOfFileException) {
null
}
}
override fun flushHistory() = lineReader.history.save()
}

View File

@@ -16,9 +16,9 @@
package org.jetbrains.kotlin.cli.jvm.repl.reader
import org.jetbrains.kotlin.cli.jvm.repl.ReplFromTerminal
import org.jetbrains.kotlin.cli.jvm.repl.KotlinRepl
class IdeReplCommandReader : ReplCommandReader {
override fun readLine(next: ReplFromTerminal.WhatNextAfterOneLine) = readLine()
class IdeReplReader : ReplReader {
override fun readLine(next: KotlinRepl.WhatNextAfterOneLine) = readLine()
override fun flushHistory() = Unit
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. 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.cli.jvm.repl.reader
import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult
import org.jetbrains.kotlin.cli.jvm.repl.ReplInterpreter
import org.jetbrains.kotlin.cli.jvm.repl.command.*
import org.jetbrains.kotlin.cli.jvm.repl.configuration.ReplConfiguration
class ReplProcessor(
interpreter: ReplInterpreter,
configuration: ReplConfiguration
) {
private val context = ReplCommandContext(interpreter, configuration)
fun processCommand(commandName: String, rawArgs: String): Boolean {
val command = ReplCommand.COMMANDS[commandName] ?: run {
context.configuration.writer.printlnHelpMessage("Unknown command\n" + "Type :help for help")
return true
}
return command.run(context, rawArgs)
}
fun evaluate(text: String): ReplEvalResult {
val evalResult = context.interpreter.eval(text)
val writer = context.configuration.writer
when (evalResult) {
is ReplEvalResult.ValueResult, is ReplEvalResult.UnitResult -> {
writer.notifyCommandSuccess()
if (evalResult is ReplEvalResult.ValueResult) {
writer.outputCommandResult(evalResult.value.toString())
}
}
is ReplEvalResult.Error.Runtime -> writer.outputRuntimeError(evalResult.message)
is ReplEvalResult.Error.CompileTime -> writer.outputRuntimeError(evalResult.message)
is ReplEvalResult.Incomplete -> writer.notifyIncomplete()
}
return evalResult
}
fun complete(line: String, position: Int): List<String> {
return context.interpreter.complete(line.substring(0, position)).map { it.name.asString() }
}
}
interface ReplCommand {
val name: String
val args: List<String>
val help: String
val constraints: ArgumentConstraints
/** Returns `true` if REPL should continue processing the next line. */
fun run(context: ReplCommandContext, rawArgs: String): Boolean
sealed class ArgumentConstraints {
object Empty : ArgumentConstraints()
object Raw : ArgumentConstraints()
}
companion object {
val COMMANDS: Map<String, ReplCommand> = listOf(
ClassesReplCommand,
DocReplCommand,
LoadReplCommand,
HelpReplCommand,
QuitReplCommand
).map { it.name to it }.toMap()
}
}
class ReplCommandContext(val interpreter: ReplInterpreter, val configuration: ReplConfiguration)

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2010-2015 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.jvm.repl.reader
import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult
import org.jetbrains.kotlin.cli.jvm.repl.KotlinRepl
import org.jetbrains.kotlin.cli.jvm.repl.messages.unescapeLineBreaks
interface ReplReader {
fun readLine(next: KotlinRepl.WhatNextAfterOneLine): String?
fun flushHistory()
tailrec fun runLoop(processor: ReplProcessor) {
val nextLine = KotlinRepl.WhatNextAfterOneLine.READ_LINE
if (processLine(processor, nextLine) != KotlinRepl.WhatNextAfterOneLine.QUIT) {
runLoop(processor)
}
}
private fun processLine(
processor: ReplProcessor,
next: KotlinRepl.WhatNextAfterOneLine
): KotlinRepl.WhatNextAfterOneLine {
val rawLine = readLine(next) ?: return KotlinRepl.WhatNextAfterOneLine.QUIT
val line = unescapeLineBreaks(rawLine)
if (isCommand(line)) {
val commandName = line.substringBefore(' ')
val rawArgs = line.substringAfter(' ', missingDelimiterValue = "").trim()
return if (processor.processCommand(commandName.drop(1), rawArgs)) {
KotlinRepl.WhatNextAfterOneLine.READ_LINE
} else {
KotlinRepl.WhatNextAfterOneLine.QUIT
}
}
val lineResult = processor.evaluate(line)
return if (lineResult is ReplEvalResult.Incomplete) {
KotlinRepl.WhatNextAfterOneLine.INCOMPLETE
} else {
KotlinRepl.WhatNextAfterOneLine.READ_LINE
}
}
private fun isCommand(line: String) = line.startsWith(":") && (line.length == 1 || line[1] != ':')
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 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.
@@ -14,10 +14,17 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.repl.messages
package org.jetbrains.kotlin.cli.jvm.repl.reader
import org.jetbrains.kotlin.cli.jvm.repl.messages.unescapeLineBreaks
import org.jetbrains.kotlin.cli.jvm.repl.writer.END_LINE
import org.jetbrains.kotlin.cli.jvm.repl.writer.ReplWriter
import org.w3c.dom.Element
import org.xml.sax.InputSource
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import javax.xml.parsers.DocumentBuilderFactory
class ReplSystemInWrapper(
private val stdin: InputStream,
@@ -87,4 +94,15 @@ class ReplSystemInWrapper(
isLastByteProcessed = true
}
}
}
private fun parseXml(inputMessage: String): String {
fun strToSource(s: String) = InputSource(ByteArrayInputStream(s.toByteArray()))
val docFactory = DocumentBuilderFactory.newInstance()
val docBuilder = docFactory.newDocumentBuilder()
val input = docBuilder.parse(strToSource(inputMessage))
val root = input.firstChild as Element
return root.textContent
}

View File

@@ -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.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.repl.messages
package org.jetbrains.kotlin.cli.jvm.repl.writer
class ReplConsoleWriter : ReplWriter {
class ConsoleReplWriter : ReplWriter {
override fun printlnWelcomeMessage(x: String) = println(x)
override fun printlnHelpMessage(x: String) = println(x)
override fun outputCompileError(x: String) = println(x)

View File

@@ -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.
@@ -14,30 +14,20 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.repl.messages
package org.jetbrains.kotlin.cli.jvm.repl.writer
import com.intellij.openapi.util.text.StringUtil
import com.intellij.util.LineSeparator
import org.jetbrains.kotlin.cli.jvm.repl.messages.SOURCE_CHARS
import org.jetbrains.kotlin.cli.jvm.repl.messages.XML_REPLACEMENTS
import org.jetbrains.kotlin.utils.repl.ReplEscapeType
import java.io.PrintStream
import org.jetbrains.kotlin.utils.repl.ReplEscapeType.*
val END_LINE: String = LineSeparator.getSystemLineSeparator().separatorString
val XML_PREAMBLE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
class ReplSystemOutWrapperForIde(standardOut: PrintStream) : PrintStream(standardOut, true), ReplWriter {
private enum class EscapeType {
INITIAL_PROMPT,
HELP_PROMPT,
USER_OUTPUT,
REPL_RESULT,
READLINE_START,
READLINE_END,
REPL_INCOMPLETE,
COMPILE_ERROR,
RUNTIME_ERROR,
INTERNAL_ERROR,
SUCCESS
}
internal val END_LINE: String = LineSeparator.getSystemLineSeparator().separatorString
internal val XML_PREAMBLE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
class IdeSystemOutWrapperReplWriter(standardOut: PrintStream) : PrintStream(standardOut, true), ReplWriter {
override fun print(x: Boolean) = printWithEscaping(x.toString())
override fun print(x: Char) = printWithEscaping(x.toString())
override fun print(x: Int) = printWithEscaping(x.toString())
@@ -47,25 +37,27 @@ class ReplSystemOutWrapperForIde(standardOut: PrintStream) : PrintStream(standar
override fun print(x: String) = printWithEscaping(x)
override fun print(x: Any?) = printWithEscaping(x.toString())
private fun printlnWithEscaping(text: String, escapeType: EscapeType = EscapeType.USER_OUTPUT) = printWithEscaping("$text\n", escapeType)
private fun printWithEscaping(text: String, escapeType: EscapeType = EscapeType.USER_OUTPUT) {
super.print("${xmlEscape(text, escapeType)}$END_LINE")
private fun printlnWithEscaping(text: String, escapeType: ReplEscapeType = USER_OUTPUT) {
printWithEscaping("$text\n", escapeType)
}
private fun xmlEscape(s: String, escapeType: EscapeType): String {
private fun printWithEscaping(text: String, escapeType: ReplEscapeType = USER_OUTPUT) {
super.print("${xmlEscape(text, escapeType)}${END_LINE}")
}
private fun xmlEscape(s: String, escapeType: ReplEscapeType): String {
val singleLine = StringUtil.replace(s, SOURCE_CHARS, XML_REPLACEMENTS)
return "$XML_PREAMBLE<output type=\"$escapeType\">${StringUtil.escapeXml(singleLine)}</output>"
return "${XML_PREAMBLE}<output type=\"$escapeType\">${StringUtil.escapeXml(singleLine)}</output>"
}
override fun printlnWelcomeMessage(x: String) = printlnWithEscaping(x, EscapeType.INITIAL_PROMPT)
override fun printlnHelpMessage(x: String) = printlnWithEscaping(x, EscapeType.HELP_PROMPT)
override fun outputCommandResult(x: String) = printlnWithEscaping(x, EscapeType.REPL_RESULT)
override fun notifyReadLineStart() = printlnWithEscaping("", EscapeType.READLINE_START)
override fun notifyReadLineEnd() = printlnWithEscaping("", EscapeType.READLINE_END)
override fun notifyCommandSuccess() = printlnWithEscaping("", EscapeType.SUCCESS)
override fun notifyIncomplete() = printlnWithEscaping("", EscapeType.REPL_INCOMPLETE)
override fun outputCompileError(x: String) = printlnWithEscaping(x, EscapeType.COMPILE_ERROR)
override fun outputRuntimeError(x: String) = printlnWithEscaping(x, EscapeType.RUNTIME_ERROR)
override fun sendInternalErrorReport(x: String) = printlnWithEscaping(x, EscapeType.INTERNAL_ERROR)
override fun printlnWelcomeMessage(x: String) = printlnWithEscaping(x, INITIAL_PROMPT)
override fun printlnHelpMessage(x: String) = printlnWithEscaping(x, HELP_PROMPT)
override fun outputCommandResult(x: String) = printlnWithEscaping(x, REPL_RESULT)
override fun notifyReadLineStart() = printlnWithEscaping("", READLINE_START)
override fun notifyReadLineEnd() = printlnWithEscaping("", READLINE_END)
override fun notifyCommandSuccess() = printlnWithEscaping("", SUCCESS)
override fun notifyIncomplete() = printlnWithEscaping("", REPL_INCOMPLETE)
override fun outputCompileError(x: String) = printlnWithEscaping(x, COMPILE_ERROR)
override fun outputRuntimeError(x: String) = printlnWithEscaping(x, RUNTIME_ERROR)
override fun sendInternalErrorReport(x: String) = printlnWithEscaping(x, INTERNAL_ERROR)
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.repl.messages
package org.jetbrains.kotlin.cli.jvm.repl.writer
interface ReplWriter {
fun printlnWelcomeMessage(x: String)

View File

@@ -64,8 +64,8 @@ open class KotlinRemoteReplCompilerClient(
RemoteReplCompilerState(compileService.replCreateState(sessionId).get(), lock)
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult =
compileService.replCheck(sessionId, state.asState(RemoteReplCompilerState::class.java).replStateFacade.getId(), codeLine).get()
compileService.replCheck(sessionId, state.asState<RemoteReplCompilerState>().replStateFacade.getId(), codeLine).get()
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult =
compileService.replCompile(sessionId, state.asState(RemoteReplCompilerState::class.java).replStateFacade.getId(), codeLine).get()
compileService.replCompile(sessionId, state.asState<RemoteReplCompilerState>().replStateFacade.getId(), codeLine).get()
}

View File

@@ -122,7 +122,7 @@ open class KotlinJvmReplService(
fun createRemoteState(port: Int = portForServers): RemoteReplStateFacadeServer = statesLock.write {
val id = getValidId(stateIdCounter) { id -> states.none { it.key.getId() == id} }
val stateFacade = RemoteReplStateFacadeServer(id, createState().asState(GenericReplCompilerState::class.java), port)
val stateFacade = RemoteReplStateFacadeServer(id, createState().asState(), port)
states.put(stateFacade, true)
stateFacade
}

View File

@@ -16,7 +16,9 @@
package org.jetbrains.kotlin.resolve.annotations
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
@@ -29,6 +31,13 @@ val JVM_STATIC_ANNOTATION_FQ_NAME = FqName("kotlin.jvm.JvmStatic")
val JVM_FIELD_ANNOTATION_FQ_NAME = FqName("kotlin.jvm.JvmField")
fun DeclarationDescriptor.hasJvmStaticAnnotation(): Boolean {
if (this is CallableDescriptor) {
val containingDeclaration = this.containingDeclaration
if (containingDeclaration is ScriptDescriptor && containingDeclaration.isReplSnippet) {
return true
}
}
return annotations.findAnnotation(JVM_STATIC_ANNOTATION_FQ_NAME) != null
}

View File

@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.utils.repl.IS_REPL_SNIPPET
class KtScriptInfo(
val script: KtScript
@@ -32,7 +33,10 @@ class KtScriptInfo(
override fun getCorrespondingClassOrObject() = null
override fun getTypeParameterList() = null
override fun getPrimaryConstructorParameters() = listOf<KtParameter>()
override fun getClassKind() = ClassKind.CLASS
override fun getClassKind() = if (isReplSnippet) ClassKind.OBJECT else ClassKind.CLASS
override fun getDeclarations() = script.declarations
override fun getDanglingAnnotations() = listOf<KtAnnotationEntry>()
val isReplSnippet: Boolean
get() = script.getUserData(IS_REPL_SNIPPET) == true
}

View File

@@ -100,7 +100,13 @@ class ClassResolutionScopesSupport(
if (withCompanionObject) {
staticScopes.addIfNotNull(classDescriptor.companionObjectDescriptor?.unsubstitutedInnerClassesScope)
implicitReceiver = classDescriptor.companionObjectDescriptor?.thisAsReceiverParameter
if (classDescriptor is LazyScriptDescriptor && classDescriptor.isReplSnippet) {
implicitReceiver = null
}
else {
implicitReceiver = classDescriptor.companionObjectDescriptor?.thisAsReceiverParameter
}
parentForNewScope = classDescriptor.companionObjectDescriptor?.let {
it.getAllSuperclassesWithoutAny().asReversed().fold(parent) { scope, currentClass ->

View File

@@ -50,6 +50,8 @@ class LazyScriptDescriptor(
resolveSession.trace.record(BindingContext.SCRIPT, scriptInfo.script, this)
}
override fun isReplSnippet() = scriptInfo.isReplSnippet
private val sourceElement = scriptInfo.script.toSourceElement()
override fun getSource() = sourceElement

View File

@@ -20,16 +20,26 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.utils.Printer
class LexicalScopeImpl @JvmOverloads constructor(
parent: HierarchicalScope,
override val ownerDescriptor: DeclarationDescriptor,
override val isOwnerDescriptorAccessibleByLabel: Boolean,
override val implicitReceiver: ReceiverParameterDescriptor?,
override val kind: LexicalScopeKind,
redeclarationChecker: LocalRedeclarationChecker = LocalRedeclarationChecker.DO_NOTHING,
initialize: LexicalScopeImpl.InitializeHandler.() -> Unit = {}
) : LexicalScope, LexicalScopeStorage(parent, redeclarationChecker) {
parent: HierarchicalScope,
override val ownerDescriptor: DeclarationDescriptor,
override val isOwnerDescriptorAccessibleByLabel: Boolean,
implicitReceiver: ReceiverParameterDescriptor?,
override val kind: LexicalScopeKind,
redeclarationChecker: LocalRedeclarationChecker = LocalRedeclarationChecker.DO_NOTHING,
initialize: LexicalScopeImpl.InitializeHandler.() -> Unit = {}
): LexicalScope, LexicalScopeStorage(parent, redeclarationChecker) {
override val implicitReceiver: ReceiverParameterDescriptor?
init {
if (ownerDescriptor is ScriptDescriptor && ownerDescriptor.isReplSnippet) {
// Do not allow 'this' in scripts
this.implicitReceiver = null
}
else {
this.implicitReceiver = implicitReceiver
}
InitializeHandler().initialize()
}

View File

@@ -7,6 +7,6 @@ error: unresolved reference: a
a
^
>>> val s: String = s
error: variable 's' must be initialized
error: unresolved reference: s
val s: String = s
^

View File

@@ -18,7 +18,7 @@ package org.jetbrains.kotlin.repl
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult
import org.jetbrains.kotlin.cli.jvm.repl.ConsoleReplConfiguration
import org.jetbrains.kotlin.cli.jvm.repl.configuration.ConsoleReplConfiguration
import org.jetbrains.kotlin.cli.jvm.repl.ReplInterpreter
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils

View File

@@ -0,0 +1,46 @@
/*
* 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.utils.repl
import com.intellij.openapi.util.Key
val IS_REPL_SNIPPET = Key<Boolean>("IS_REPL_SNIPPET")
enum class ReplEscapeType {
INITIAL_PROMPT,
HELP_PROMPT,
USER_OUTPUT,
REPL_RESULT,
READLINE_START,
READLINE_END,
REPL_INCOMPLETE,
COMPILE_ERROR,
RUNTIME_ERROR,
INTERNAL_ERROR,
SUCCESS;
companion object {
fun valueOfOrNull(string: String): ReplEscapeType? {
return try {
valueOf(string)
}
catch (e: IllegalArgumentException) {
null
}
}
}
}

View File

@@ -21,6 +21,8 @@ import org.jetbrains.annotations.NotNull;
public interface ScriptDescriptor extends ClassDescriptor {
int getPriority();
boolean isReplSnippet();
@NotNull
@Override
ClassConstructorDescriptor getUnsubstitutedPrimaryConstructor();

View File

@@ -16,3 +16,4 @@ sourceSets {
"test" {}
}
dist(targetName = "kotlin-code-insight.jar")

View File

@@ -19,7 +19,7 @@ package org.jetbrains.kotlin.idea.codeInsight
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.resolve.ResolutionFacadeBase
import org.jetbrains.kotlin.idea.resolve.frontendService
import org.jetbrains.kotlin.idea.util.*
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
@@ -49,9 +49,9 @@ import java.util.*
class ReferenceVariantsHelper(
private val bindingContext: BindingContext,
private val resolutionFacade: ResolutionFacade,
private val resolutionFacade: ResolutionFacadeBase,
private val moduleDescriptor: ModuleDescriptor,
private val visibilityFilter: (DeclarationDescriptor) -> Boolean,
private val visibilityFilter: (DeclarationDescriptor) -> Boolean = { _ -> true },
private val notProperties: Set<FqNameUnsafe> = setOf()
) {
fun getReferenceVariants(
@@ -422,7 +422,7 @@ class ReferenceVariantsHelper(
}
private fun MemberScope.collectStaticMembers(
resolutionFacade: ResolutionFacade,
resolutionFacade: ResolutionFacadeBase,
kindFilter: DescriptorKindFilter,
nameFilter: (Name) -> Boolean
): Collection<DeclarationDescriptor> {
@@ -430,7 +430,7 @@ private fun MemberScope.collectStaticMembers(
}
fun ResolutionScope.collectSyntheticStaticMembersAndConstructors(
resolutionFacade: ResolutionFacade,
resolutionFacade: ResolutionFacadeBase,
kindFilter: DescriptorKindFilter,
nameFilter: (Name) -> Boolean
): List<FunctionDescriptor> {

View File

@@ -26,9 +26,22 @@ import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
interface ResolutionFacade {
interface ResolutionFacadeBase {
val project: Project
val moduleDescriptor: ModuleDescriptor
// get service for the module this resolution was created for
fun <T : Any> getFrontendService(serviceClass: Class<T>): T
// get service for the module defined by PsiElement/ModuleDescriptor passed as parameter
fun <T : Any> getFrontendService(element: PsiElement, serviceClass: Class<T>): T
fun <T : Any> tryGetFrontendService(element: PsiElement, serviceClass: Class<T>): T?
fun <T : Any> getFrontendService(moduleDescriptor: ModuleDescriptor, serviceClass: Class<T>): T
}
interface ResolutionFacade : ResolutionFacadeBase {
fun analyze(element: KtElement, bodyResolveMode: BodyResolveMode = BodyResolveMode.FULL): BindingContext
fun analyze(elements: Collection<KtElement>, bodyResolveMode: BodyResolveMode): BindingContext
@@ -36,22 +49,10 @@ interface ResolutionFacade {
fun resolveToDescriptor(declaration: KtDeclaration, bodyResolveMode: BodyResolveMode = BodyResolveMode.FULL): DeclarationDescriptor
val moduleDescriptor: ModuleDescriptor
// get service for the module this resolution was created for
fun <T : Any> getFrontendService(serviceClass: Class<T>): T
fun <T : Any> getIdeService(serviceClass: Class<T>): T
// get service for the module defined by PsiElement/ModuleDescriptor passed as parameter
fun <T : Any> getFrontendService(element: PsiElement, serviceClass: Class<T>): T
fun <T : Any> tryGetFrontendService(element: PsiElement, serviceClass: Class<T>): T?
fun <T : Any> getFrontendService(moduleDescriptor: ModuleDescriptor, serviceClass: Class<T>): T
}
inline fun <reified T : Any> ResolutionFacade.frontendService(): T
inline fun <reified T : Any> ResolutionFacadeBase.frontendService(): T
= this.getFrontendService(T::class.java)
inline fun <reified T : Any> ResolutionFacade.ideService(): T

View File

@@ -19,7 +19,7 @@ package org.jetbrains.kotlin.idea.util
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.resolve.ResolutionFacadeBase
import org.jetbrains.kotlin.idea.resolve.frontendService
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
@@ -219,7 +219,7 @@ fun CallTypeAndReceiver<*, *>.receiverTypes(
bindingContext: BindingContext,
contextElement: PsiElement,
moduleDescriptor: ModuleDescriptor,
resolutionFacade: ResolutionFacade,
resolutionFacade: ResolutionFacadeBase,
stableSmartCastsOnly: Boolean
): Collection<KotlinType>? {
return receiverTypesWithIndex(bindingContext, contextElement, moduleDescriptor, resolutionFacade, stableSmartCastsOnly)?.map { it.type }
@@ -229,7 +229,7 @@ fun CallTypeAndReceiver<*, *>.receiverTypesWithIndex(
bindingContext: BindingContext,
contextElement: PsiElement,
moduleDescriptor: ModuleDescriptor,
resolutionFacade: ResolutionFacade,
resolutionFacade: ResolutionFacadeBase,
stableSmartCastsOnly: Boolean,
withImplicitReceiversWhenExplicitPresent: Boolean = false
): Collection<ReceiverType>? {

View File

@@ -17,10 +17,9 @@
package org.jetbrains.kotlin.idea.util
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.resolve.ResolutionFacadeBase
import org.jetbrains.kotlin.idea.resolve.frontendService
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
@@ -40,14 +39,14 @@ import java.util.*
class ShadowedDeclarationsFilter(
private val bindingContext: BindingContext,
private val resolutionFacade: ResolutionFacade,
private val resolutionFacade: ResolutionFacadeBase,
private val context: PsiElement,
private val explicitReceiverValue: ReceiverValue?
) {
companion object {
fun create(
bindingContext: BindingContext,
resolutionFacade: ResolutionFacade,
resolutionFacade: ResolutionFacadeBase,
context: PsiElement,
callTypeAndReceiver: CallTypeAndReceiver<*, *>
): ShadowedDeclarationsFilter? {

View File

@@ -23,7 +23,7 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.resolve.ResolutionFacadeBase
import org.jetbrains.kotlin.idea.resolve.frontendService
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.Name
@@ -80,7 +80,7 @@ fun PsiElement.getResolutionScope(bindingContext: BindingContext): LexicalScope?
return null
}
fun PsiElement.getResolutionScope(bindingContext: BindingContext, resolutionFacade: ResolutionFacade/*TODO: get rid of this parameter*/): LexicalScope {
fun PsiElement.getResolutionScope(bindingContext: BindingContext, resolutionFacade: ResolutionFacadeBase/*TODO: get rid of this parameter*/): LexicalScope {
return getResolutionScope(bindingContext) ?:
when (containingFile) {
is KtFile -> resolutionFacade.getFileResolutionScope(containingFile as KtFile)
@@ -94,6 +94,6 @@ fun KtElement.getResolutionScope(): LexicalScope {
return getResolutionScope(context, resolutionFacade)
}
fun ResolutionFacade.getFileResolutionScope(file: KtFile): LexicalScope {
fun ResolutionFacadeBase.getFileResolutionScope(file: KtFile): LexicalScope {
return frontendService<FileScopeProvider>().getFileResolutionScope(file)
}

View File

@@ -24,11 +24,13 @@ import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.annotations.NotNull
import org.jetbrains.kotlin.console.actions.logError
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.utils.repl.ReplEscapeType
import org.w3c.dom.Element
import org.xml.sax.InputSource
import java.io.ByteArrayInputStream
import java.nio.charset.Charset
import javax.xml.parsers.DocumentBuilderFactory
import org.jetbrains.kotlin.utils.repl.ReplEscapeType.*
val XML_REPLACEMENTS: Array<String> = arrayOf("#n", "#diez")
val SOURCE_CHARS: Array<String> = arrayOf("\n", "#")
@@ -76,25 +78,25 @@ class ReplOutputHandler(
}
val root = output.firstChild as Element
val outputType = root.getAttribute("type")
val outputType = ReplEscapeType.valueOfOrNull(root.getAttribute("type"))
val content = StringUtil.replace(root.textContent, XML_REPLACEMENTS, SOURCE_CHARS)
when (outputType) {
"INITIAL_PROMPT" -> buildWarningIfNeededBeforeInit(content)
"HELP_PROMPT" -> outputProcessor.printHelp(content)
"USER_OUTPUT" -> outputProcessor.printUserOutput(content)
"REPL_RESULT" -> outputProcessor.printResultWithGutterIcon(content)
"READLINE_START" -> runner.isReadLineMode = true
"READLINE_END" -> runner.isReadLineMode = false
"REPL_INCOMPLETE",
"COMPILE_ERROR" -> outputProcessor.highlightCompilerErrors(createCompilerMessages(content))
"RUNTIME_ERROR" -> outputProcessor.printRuntimeError("${content.trim()}\n")
"INTERNAL_ERROR" -> outputProcessor.printInternalErrorMessage(content)
"SUCCESS" -> runner.commandHistory.lastUnprocessedEntry()?.entryText?.let { runner.successfulLine(it) }
else -> logError(ReplOutputHandler::class.java, "Unexpected output type:\n$outputType")
INITIAL_PROMPT -> buildWarningIfNeededBeforeInit(content)
HELP_PROMPT -> outputProcessor.printHelp(content)
USER_OUTPUT -> outputProcessor.printUserOutput(content)
REPL_RESULT -> outputProcessor.printResultWithGutterIcon(content)
READLINE_START -> runner.isReadLineMode = true
READLINE_END -> runner.isReadLineMode = false
REPL_INCOMPLETE,
COMPILE_ERROR -> outputProcessor.highlightCompilerErrors(createCompilerMessages(content))
RUNTIME_ERROR -> outputProcessor.printRuntimeError("${content.trim()}\n")
INTERNAL_ERROR -> outputProcessor.printInternalErrorMessage(content)
SUCCESS -> runner.commandHistory.lastUnprocessedEntry()?.entryText?.let { runner.successfulLine(it) }
null -> logError(ReplOutputHandler::class.java, "Unexpected output type:\n$outputType")
}
if (outputType in setOf("SUCCESS", "COMPILE_ERROR", "INTERNAL_ERROR", "RUNTIME_ERROR", "READLINE_END")) {
if (outputType in setOf(SUCCESS, COMPILE_ERROR, INTERNAL_ERROR, RUNTIME_ERROR, READLINE_END)) {
runner.commandHistory.entryProcessed()
}
}