Add kotlin console repl
This commit is contained in:
committed by
Pavel V. Talanov
parent
80cbee83ee
commit
a11f411f7d
+30
@@ -242,6 +242,36 @@ public final class AnalyzerWithCompilerReport {
|
||||
return new SyntaxErrorReport(visitor.hasErrors, visitor.allErrorsAtEof);
|
||||
}
|
||||
|
||||
public static SyntaxErrorReport reportSyntaxErrorsWithDiagnostics(@NotNull final PsiElement file, @NotNull final MessageCollector messageCollector,
|
||||
@NotNull final List<Diagnostic> diagnostics) {
|
||||
class ErrorReportingVisitor extends AnalyzingUtils.PsiErrorElementVisitor {
|
||||
boolean hasErrors = false;
|
||||
boolean allErrorsAtEof = true;
|
||||
|
||||
private <E extends PsiElement> void reportDiagnostic(E element, DiagnosticFactory0<E> factory, String message) {
|
||||
MyDiagnostic<?> diagnostic = new MyDiagnostic<E>(element, factory, message);
|
||||
AnalyzerWithCompilerReport.reportDiagnostic(diagnostic, messageCollector);
|
||||
if (element.getTextRange().getStartOffset() != file.getTextRange().getEndOffset()) {
|
||||
allErrorsAtEof = false;
|
||||
}
|
||||
hasErrors = true;
|
||||
|
||||
diagnostics.add(diagnostic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitErrorElement(PsiErrorElement element) {
|
||||
String description = element.getErrorDescription();
|
||||
reportDiagnostic(element, SYNTAX_ERROR_FACTORY, StringUtil.isEmpty(description) ? "Syntax error" : description);
|
||||
}
|
||||
}
|
||||
ErrorReportingVisitor visitor = new ErrorReportingVisitor();
|
||||
|
||||
file.accept(visitor);
|
||||
|
||||
return new SyntaxErrorReport(visitor.hasErrors, visitor.allErrorsAtEof);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AnalysisResult getAnalysisResult() {
|
||||
return analysisResult;
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.jetbrains.kotlin.cli.jvm.repl;
|
||||
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.testFramework.LightVirtualFile;
|
||||
import jline.console.ConsoleReader;
|
||||
import jline.console.history.FileHistory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -26,6 +28,7 @@ import org.jetbrains.kotlin.config.CompilerConfiguration;
|
||||
import org.jetbrains.kotlin.utils.UtilsPackage;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -36,6 +39,9 @@ public class ReplFromTerminal {
|
||||
private Throwable replInitializationFailed;
|
||||
private final Object waitRepl = new Object();
|
||||
|
||||
private final boolean ideMode;
|
||||
private ReplSystemOutWrapper replWriter;
|
||||
|
||||
private final ConsoleReader consoleReader;
|
||||
|
||||
public ReplFromTerminal(
|
||||
@@ -56,8 +62,24 @@ public class ReplFromTerminal {
|
||||
}
|
||||
}.start();
|
||||
|
||||
String replIdeMode = System.getProperty("repl.ideMode");
|
||||
ideMode = replIdeMode != null && replIdeMode.equals("true");
|
||||
|
||||
try {
|
||||
consoleReader = new ConsoleReader("kotlin", System.in, System.out, null);
|
||||
OutputStream outStream = System.out;
|
||||
if (ideMode) {
|
||||
// consoleReader duplicates his input in his output stream;
|
||||
// to prevent such behaviour we redirect his output to dummy output stream
|
||||
VirtualFile consoleOutputRedirect = new LightVirtualFile("kotlinConsoleReplRedirect");
|
||||
outStream = consoleOutputRedirect.getOutputStream(null);
|
||||
|
||||
// wrapper is required to escape every input;
|
||||
// if user calls [System.setOut(...)] then undefined behaviour
|
||||
replWriter = new ReplSystemOutWrapper(System.out);
|
||||
System.setOut(replWriter);
|
||||
}
|
||||
|
||||
consoleReader = new ConsoleReader("kotlin", System.in, outStream, null);
|
||||
consoleReader.setHistoryEnabled(true);
|
||||
consoleReader.setExpandEvents(false);
|
||||
consoleReader.setHistory(new FileHistory(new File(new File(System.getProperty("user.home")), ".kotlin_history")));
|
||||
@@ -65,6 +87,8 @@ public class ReplFromTerminal {
|
||||
catch (Exception e) {
|
||||
throw UtilsPackage.rethrow(e);
|
||||
}
|
||||
|
||||
getReplInterpreter().setIdeMode(ideMode);
|
||||
}
|
||||
|
||||
private ReplInterpreter getReplInterpreter() {
|
||||
@@ -122,7 +146,9 @@ public class ReplFromTerminal {
|
||||
@NotNull
|
||||
private WhatNextAfterOneLine one(@NotNull WhatNextAfterOneLine next) {
|
||||
try {
|
||||
String line = consoleReader.readLine(next == WhatNextAfterOneLine.INCOMPLETE ? "... " : ">>> ");
|
||||
String prompt = getPrompt(next);
|
||||
String line = consoleReader.readLine(prompt);
|
||||
|
||||
if (line == null) {
|
||||
return WhatNextAfterOneLine.QUIT;
|
||||
}
|
||||
@@ -145,6 +171,15 @@ public class ReplFromTerminal {
|
||||
}
|
||||
}
|
||||
|
||||
private String getPrompt(@NotNull WhatNextAfterOneLine next) {
|
||||
String prompt = null;
|
||||
// consoleReader always print prompt; in [ideMode] we don't allow it
|
||||
if (!ideMode) {
|
||||
prompt = next == WhatNextAfterOneLine.INCOMPLETE ? "... " : ">>> ";
|
||||
}
|
||||
return prompt;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private ReplInterpreter.LineResultType eval(@NotNull String line) {
|
||||
ReplInterpreter.LineResult lineResult = getReplInterpreter().eval(line);
|
||||
@@ -156,7 +191,11 @@ public class ReplFromTerminal {
|
||||
else if (lineResult.getType() == ReplInterpreter.LineResultType.INCOMPLETE) {
|
||||
}
|
||||
else if (lineResult.getType() == ReplInterpreter.LineResultType.ERROR) {
|
||||
System.out.print(lineResult.getErrorText());
|
||||
if (ideMode) {
|
||||
replWriter.printlnWithEscaping(lineResult.getErrorText(), EscapeType.REPORT);
|
||||
} else {
|
||||
System.out.print(lineResult.getErrorText());
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("unknown line result type: " + lineResult);
|
||||
|
||||
@@ -21,6 +21,8 @@ import com.google.common.collect.Lists;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.CharsetToolkit;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFileFactory;
|
||||
@@ -47,6 +49,7 @@ import org.jetbrains.kotlin.context.MutableModuleContext;
|
||||
import org.jetbrains.kotlin.descriptors.ScriptDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider;
|
||||
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl;
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic;
|
||||
import org.jetbrains.kotlin.idea.JetLanguage;
|
||||
import org.jetbrains.kotlin.name.FqName;
|
||||
import org.jetbrains.kotlin.parsing.JetParserDefinition;
|
||||
@@ -65,18 +68,30 @@ import org.jetbrains.kotlin.resolve.scopes.JetScope;
|
||||
import org.jetbrains.kotlin.types.JetType;
|
||||
import org.jetbrains.kotlin.utils.UtilsPackage;
|
||||
import org.jetbrains.org.objectweb.asm.Type;
|
||||
import org.w3c.dom.Attr;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.ls.DOMImplementationLS;
|
||||
import org.w3c.dom.ls.LSSerializer;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import static org.jetbrains.kotlin.cli.jvm.config.ConfigPackage.getJvmClasspathRoots;
|
||||
import static org.jetbrains.kotlin.cli.jvm.config.ConfigPackage.getModuleName;
|
||||
@@ -102,6 +117,8 @@ public class ReplInterpreter {
|
||||
private final ResolveSession resolveSession;
|
||||
private final ScriptMutableDeclarationProviderFactory scriptDeclarationFactory;
|
||||
|
||||
private boolean ideMode;
|
||||
|
||||
public ReplInterpreter(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) {
|
||||
KotlinCoreEnvironment environment =
|
||||
KotlinCoreEnvironment.createForProduction(disposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES);
|
||||
@@ -159,12 +176,21 @@ public class ReplInterpreter {
|
||||
c.getScripts().clear();
|
||||
}
|
||||
|
||||
public void setIdeMode(boolean ideMode) {
|
||||
this.ideMode = ideMode;
|
||||
}
|
||||
|
||||
public enum LineResultType {
|
||||
SUCCESS,
|
||||
ERROR,
|
||||
INCOMPLETE,
|
||||
}
|
||||
|
||||
private enum ErrorType {
|
||||
CODE_ERROR,
|
||||
THROWABLE
|
||||
}
|
||||
|
||||
public static class LineResult {
|
||||
private final Object value;
|
||||
private final boolean unit;
|
||||
@@ -208,16 +234,77 @@ public class ReplInterpreter {
|
||||
return new LineResult(value, unit, null, LineResultType.SUCCESS);
|
||||
}
|
||||
|
||||
public static LineResult error(@NotNull String errorText) {
|
||||
public static LineResult error(@NotNull String errorText, boolean ideMode,
|
||||
@NotNull ErrorType errType, @Nullable Collection<Diagnostic> diagnostics
|
||||
) {
|
||||
if (errorText.isEmpty()) {
|
||||
errorText = "<unknown error>";
|
||||
}
|
||||
else if (!errorText.endsWith("\n")) {
|
||||
|
||||
String[] lines = errorTextToLinesWithPreprocessing(errorText);
|
||||
|
||||
if (ideMode && errType == ErrorType.CODE_ERROR) {
|
||||
assert diagnostics != null : "diagnostics should not be null in not-throwable case";
|
||||
errorText = convertLinesToXML(lines, diagnostics);
|
||||
}
|
||||
else {
|
||||
errorText = StringUtil.join(lines, "\n");
|
||||
}
|
||||
|
||||
if (!errorText.endsWith("\n")) {
|
||||
errorText += "\n";
|
||||
}
|
||||
|
||||
return new LineResult(null, false, errorText, LineResultType.ERROR);
|
||||
}
|
||||
|
||||
private static String[] errorTextToLinesWithPreprocessing(@NotNull String errorText) {
|
||||
// cut "/line_i.kts:row:column" prefix and capitalize "error:" and "warning:"
|
||||
String[] lines = errorText.split("\\n");
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
if (lines[i].contains("error:")) {
|
||||
int pos = lines[i].indexOf("error:");
|
||||
lines[i] = "E" + lines[i].substring(pos + 1);
|
||||
}
|
||||
else if (lines[i].contains("warning:")) {
|
||||
int pos = lines[i].indexOf("warning:");
|
||||
lines[i] = "W" + lines[i].substring(pos + 1);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
private static String convertLinesToXML(@NotNull String[] lines, @NotNull Collection<Diagnostic> diagnostics) {
|
||||
try {
|
||||
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
|
||||
Document errorReport = docBuilder.newDocument();
|
||||
|
||||
Element rootElement = errorReport.createElement("report");
|
||||
errorReport.appendChild(rootElement);
|
||||
|
||||
Iterator<Diagnostic> di = diagnostics.iterator();
|
||||
for (String s : lines) {
|
||||
if (s.startsWith("Error:") || s.startsWith("Warning:")) {
|
||||
TextRange errorRange = di.hasNext() ? di.next().getTextRanges().get(0) : TextRange.EMPTY_RANGE;
|
||||
|
||||
Element reportEntry = errorReport.createElement("reportEntry");
|
||||
reportEntry.setAttribute("rangeStart", String.valueOf(errorRange.getStartOffset()));
|
||||
reportEntry.setAttribute("rangeEnd", String.valueOf(errorRange.getEndOffset()));
|
||||
reportEntry.appendChild(errorReport.createTextNode(StringUtil.escapeXml(s)));
|
||||
|
||||
rootElement.appendChild(reportEntry);
|
||||
}
|
||||
}
|
||||
|
||||
DOMImplementationLS domImplementation = (DOMImplementationLS) errorReport.getImplementation();
|
||||
LSSerializer lsSerializer = domImplementation.createLSSerializer();
|
||||
return lsSerializer.writeToString(errorReport);
|
||||
} catch (ParserConfigurationException e) {
|
||||
throw UtilsPackage.rethrow(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static LineResult incomplete() {
|
||||
return new LineResult(null, false, null, LineResultType.INCOMPLETE);
|
||||
}
|
||||
@@ -242,9 +329,10 @@ public class ReplInterpreter {
|
||||
assert psiFile != null : "Script file not analyzed at line " + lineNumber + ": " + fullText;
|
||||
|
||||
ReplMessageCollectorWrapper errorCollector = new ReplMessageCollectorWrapper();
|
||||
List<Diagnostic> syntaxDiagnostics = new ArrayList<Diagnostic>();
|
||||
|
||||
AnalyzerWithCompilerReport.SyntaxErrorReport syntaxErrorReport =
|
||||
AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorCollector.getMessageCollector());
|
||||
AnalyzerWithCompilerReport.reportSyntaxErrorsWithDiagnostics(psiFile, errorCollector.getMessageCollector(), syntaxDiagnostics);
|
||||
|
||||
if (syntaxErrorReport.isHasErrors() && syntaxErrorReport.isAllErrorsAtEof()) {
|
||||
previousIncompleteLines.add(line);
|
||||
@@ -254,7 +342,7 @@ public class ReplInterpreter {
|
||||
previousIncompleteLines.clear();
|
||||
|
||||
if (syntaxErrorReport.isHasErrors()) {
|
||||
return LineResult.error(errorCollector.getString());
|
||||
return LineResult.error(errorCollector.getString(), ideMode, ErrorType.CODE_ERROR, syntaxDiagnostics);
|
||||
}
|
||||
|
||||
prepareForTheNextReplLine(topDownAnalysisContext);
|
||||
@@ -265,7 +353,7 @@ public class ReplInterpreter {
|
||||
|
||||
ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorCollector);
|
||||
if (scriptDescriptor == null) {
|
||||
return LineResult.error(errorCollector.getString());
|
||||
return LineResult.error(errorCollector.getString(), ideMode, ErrorType.CODE_ERROR, trace.getBindingContext().getDiagnostics().all());
|
||||
}
|
||||
|
||||
List<Pair<ScriptDescriptor, Type>> earlierScripts = Lists.newArrayList();
|
||||
@@ -303,7 +391,7 @@ public class ReplInterpreter {
|
||||
scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
return LineResult.error(renderStackTrace(e.getCause()));
|
||||
return LineResult.error(renderStackTrace(e.getCause()), ideMode, ErrorType.THROWABLE, null);
|
||||
}
|
||||
Field rvField = scriptClass.getDeclaredField("rv");
|
||||
rvField.setAccessible(true);
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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 com.intellij.openapi.util.text.StringUtil
|
||||
import java.io.PrintStream
|
||||
|
||||
enum class EscapeType {
|
||||
ORDINARY, REPORT
|
||||
}
|
||||
|
||||
public class ReplSystemOutWrapper(private val standardOut: PrintStream) : PrintStream(standardOut, true) {
|
||||
private val XML_PREFIX = "<?xml version=\"1.0\" encoding=\"UTF-16\"?>"
|
||||
|
||||
private fun xmlEscape(s: String, escapeType: EscapeType): String {
|
||||
val singleLine = StringUtil.escapeLineBreak(s)
|
||||
return "$XML_PREFIX<output type=\"$escapeType\">${StringUtil.escapeXml(singleLine)}</output>"
|
||||
}
|
||||
|
||||
override fun print(x: Any) = printWithEscaping(x.toString())
|
||||
private fun printWithEscaping(text: String, escapeType: EscapeType = EscapeType.ORDINARY) = super.print(xmlEscape(text, escapeType))
|
||||
|
||||
override fun println(x: String) = printlnWithEscaping(x)
|
||||
override fun println(x: Any) = printlnWithEscaping(x.toString())
|
||||
public fun printlnWithEscaping(text: String, escapeType: EscapeType = EscapeType.ORDINARY): Unit = super.println(xmlEscape(text, escapeType))
|
||||
}
|
||||
Reference in New Issue
Block a user