From cd1211fcb30bfef04beea9418c56f45b049a98e9 Mon Sep 17 00:00:00 2001 From: Stepan Koltsov Date: Thu, 28 Jun 2012 19:38:44 +0400 Subject: [PATCH] repl: multiline --- .../messages/AnalyzerWithCompilerReport.java | 26 +++++++++- .../jet/cli/jvm/repl/ReplFromTerminal.java | 47 +++++++++++++----- .../jet/cli/jvm/repl/ReplInterpreter.java | 49 +++++++++++++++---- compiler/testData/repl/multiline.repl | 6 +++ compiler/testData/repl/multiline3.repl | 10 ++++ .../jet/repl/ReplInterpreterTest.java | 14 +++++- 6 files changed, 128 insertions(+), 24 deletions(-) create mode 100644 compiler/testData/repl/multiline.repl create mode 100644 compiler/testData/repl/multiline3.repl diff --git a/compiler/cli/src/org/jetbrains/jet/cli/common/messages/AnalyzerWithCompilerReport.java b/compiler/cli/src/org/jetbrains/jet/cli/common/messages/AnalyzerWithCompilerReport.java index beeb50dfa91..f6ad1cf84b3 100644 --- a/compiler/cli/src/org/jetbrains/jet/cli/common/messages/AnalyzerWithCompilerReport.java +++ b/compiler/cli/src/org/jetbrains/jet/cli/common/messages/AnalyzerWithCompilerReport.java @@ -123,13 +123,35 @@ public final class AnalyzerWithCompilerReport { } } - public static boolean reportSyntaxErrors(@NotNull PsiElement file, @NotNull final MessageCollector messageCollector) { + public static class SyntaxErrorReport { + private final boolean hasErrors; + private final boolean onlyErrorAtEof; + + public SyntaxErrorReport(boolean hasErrors, boolean onlyErrorAtEof) { + this.hasErrors = hasErrors; + this.onlyErrorAtEof = onlyErrorAtEof; + } + + public boolean isHasErrors() { + return hasErrors; + } + + public boolean isOnlyErrorAtEof() { + return onlyErrorAtEof; + } + } + + public static SyntaxErrorReport reportSyntaxErrors(@NotNull final PsiElement file, @NotNull final MessageCollector messageCollector) { class ErrorReportingVisitor extends AnalyzingUtils.PsiErrorElementVisitor { boolean hasErrors = false; + boolean onlyErrorAtEof = false; private void reportDiagnostic(E element, SimpleDiagnosticFactory factory, String message) { MyDiagnostic diagnostic = new MyDiagnostic(element, factory, message); AnalyzerWithCompilerReport.reportDiagnostic(diagnostic, messageCollector); + if (element.getTextRange().getStartOffset() == file.getTextRange().getEndOffset()) { + onlyErrorAtEof = !hasErrors; + } hasErrors = true; } @@ -150,7 +172,7 @@ public final class AnalyzerWithCompilerReport { file.accept(visitor); - return visitor.hasErrors; + return new SyntaxErrorReport(visitor.hasErrors, visitor.onlyErrorAtEof); } @Nullable diff --git a/compiler/cli/src/org/jetbrains/jet/cli/jvm/repl/ReplFromTerminal.java b/compiler/cli/src/org/jetbrains/jet/cli/jvm/repl/ReplFromTerminal.java index f6d2ae3b977..f3079537c94 100644 --- a/compiler/cli/src/org/jetbrains/jet/cli/jvm/repl/ReplFromTerminal.java +++ b/compiler/cli/src/org/jetbrains/jet/cli/jvm/repl/ReplFromTerminal.java @@ -89,9 +89,10 @@ public class ReplFromTerminal { try { System.out.println("Kotlin interactive shell"); System.out.println("Type :help for help, :quit for quit"); + WhatNextAfterOneLine next = WhatNextAfterOneLine.READ_LINE; while (true) { - boolean next = one(); - if (!next) { + next = one(next); + if (next == WhatNextAfterOneLine.QUIT) { break; } } @@ -106,33 +107,55 @@ public class ReplFromTerminal { } } - private boolean one() { + private enum WhatNextAfterOneLine { + READ_LINE, + INCOMPLETE, + QUIT, + } + + @NotNull + private WhatNextAfterOneLine one(@NotNull WhatNextAfterOneLine next) { try { - String line = consoleReader.readLine(">>> "); + String line = consoleReader.readLine(next == WhatNextAfterOneLine.INCOMPLETE ? "... " : ">>> "); if (line == null) { - return false; + return WhatNextAfterOneLine.QUIT; } if (line.startsWith(":")) { - return oneCommand(line.substring(1)); + boolean notQuit = oneCommand(line.substring(1)); + return notQuit ? WhatNextAfterOneLine.READ_LINE : WhatNextAfterOneLine.QUIT; } - eval(line); - return true; + ReplInterpreter.LineResultType lineResultType = eval(line); + if (lineResultType == ReplInterpreter.LineResultType.INCOMPLETE) { + return WhatNextAfterOneLine.INCOMPLETE; + } + else { + return WhatNextAfterOneLine.READ_LINE; + } } catch (Exception e) { throw ExceptionUtils.rethrow(e); } } - private void eval(@NotNull String line) { + @NotNull + private ReplInterpreter.LineResultType eval(@NotNull String line) { ReplInterpreter.LineResult lineResult = getReplInterpreter().eval(line); - if (!lineResult.isSuccessful()) { + if (lineResult.getType() == ReplInterpreter.LineResultType.SUCCESS) { + if (!lineResult.isUnit()) { + System.out.println(lineResult.getValue()); + } + } + else if (lineResult.getType() == ReplInterpreter.LineResultType.INCOMPLETE) { + } + else if (lineResult.getType() == ReplInterpreter.LineResultType.ERROR) { System.out.print(lineResult.getErrorText()); } - else if (!lineResult.isUnit()) { - System.out.println(lineResult.getValue()); + else { + throw new IllegalStateException("unknown line result type: " + lineResult); } + return lineResult.getType(); } private boolean oneCommand(@NotNull String command) throws Exception { diff --git a/compiler/cli/src/org/jetbrains/jet/cli/jvm/repl/ReplInterpreter.java b/compiler/cli/src/org/jetbrains/jet/cli/jvm/repl/ReplInterpreter.java index 26cdc72a325..cbdda083107 100644 --- a/compiler/cli/src/org/jetbrains/jet/cli/jvm/repl/ReplInterpreter.java +++ b/compiler/cli/src/org/jetbrains/jet/cli/jvm/repl/ReplInterpreter.java @@ -79,6 +79,7 @@ public class ReplInterpreter { @Nullable private JetScope lastLineScope; private List earlierLines = Lists.newArrayList(); + private List previousIncompleteLines = Lists.newArrayList(); private final ReplClassLoader classLoader; @NotNull @@ -120,23 +121,34 @@ public class ReplInterpreter { classLoader = new ReplClassLoader(new URLClassLoader(classpath.toArray(new URL[0]))); } + public enum LineResultType { + SUCCESS, + ERROR, + INCOMPLETE, + } + public static class LineResult { + private final Object value; private final boolean unit; private final String errorText; + @NotNull + private final LineResultType type; - private LineResult(Object value, boolean unit, String errorText) { + private LineResult(Object value, boolean unit, String errorText, @NotNull LineResultType type) { this.value = value; this.unit = unit; this.errorText = errorText; + this.type = type; } - public boolean isSuccessful() { - return errorText == null; + @NotNull + public LineResultType getType() { + return type; } private void checkSuccessful() { - if (!isSuccessful()) { + if (!(getType() == LineResultType.SUCCESS)) { throw new IllegalStateException("it is error"); } } @@ -157,7 +169,7 @@ public class ReplInterpreter { } public static LineResult successful(Object value, boolean unit) { - return new LineResult(value, unit, null); + return new LineResult(value, unit, null, LineResultType.SUCCESS); } public static LineResult error(@NotNull String errorText) { @@ -167,7 +179,11 @@ public class ReplInterpreter { else if (!errorText.endsWith("\n")) { errorText = errorText + "\n"; } - return new LineResult(null, false, errorText); + return new LineResult(null, false, errorText, LineResultType.ERROR); + } + + public static LineResult incomplete() { + return new LineResult(null, false, null, LineResultType.INCOMPLETE); } } @@ -177,14 +193,29 @@ public class ReplInterpreter { JvmClassName scriptClassName = JvmClassName.byInternalName("Line" + lineNumber); - LightVirtualFile virtualFile = new LightVirtualFile("line" + lineNumber + ".ktscript", JetLanguage.INSTANCE, line); + StringBuilder fullText = new StringBuilder(); + for (String prevLine : previousIncompleteLines) { + fullText.append(prevLine + "\n"); + } + fullText.append(line); + + LightVirtualFile virtualFile = new LightVirtualFile("line" + lineNumber + ".ktscript", JetLanguage.INSTANCE, fullText.toString()); virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET); JetFile psiFile = (JetFile) ((PsiFileFactoryImpl) PsiFileFactory.getInstance(jetCoreEnvironment.getProject())).trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false); MessageCollectorToString errorCollector = new MessageCollectorToString(); - boolean hasErrors = AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorCollector); - if (hasErrors) { + AnalyzerWithCompilerReport.SyntaxErrorReport syntaxErrorReport = + AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorCollector); + + if (syntaxErrorReport.isOnlyErrorAtEof()) { + previousIncompleteLines.add(line); + return LineResult.incomplete(); + } + + previousIncompleteLines.clear(); + + if (syntaxErrorReport.isHasErrors()) { return LineResult.error(errorCollector.getString()); } diff --git a/compiler/testData/repl/multiline.repl b/compiler/testData/repl/multiline.repl new file mode 100644 index 00000000000..d554926f074 --- /dev/null +++ b/compiler/testData/repl/multiline.repl @@ -0,0 +1,6 @@ +>>> fun clock() = +incomplete +>>> 12 +null +>>> clock() +12 diff --git a/compiler/testData/repl/multiline3.repl b/compiler/testData/repl/multiline3.repl new file mode 100644 index 00000000000..b7ef806d571 --- /dev/null +++ b/compiler/testData/repl/multiline3.repl @@ -0,0 +1,10 @@ +>>> fun get7() = +incomplete +>>> 4 + +incomplete +>>> 2 + +incomplete +>>> 1 +null +>>> get7() +7 diff --git a/compiler/tests/org/jetbrains/jet/repl/ReplInterpreterTest.java b/compiler/tests/org/jetbrains/jet/repl/ReplInterpreterTest.java index daf9441c940..11465c66033 100644 --- a/compiler/tests/org/jetbrains/jet/repl/ReplInterpreterTest.java +++ b/compiler/tests/org/jetbrains/jet/repl/ReplInterpreterTest.java @@ -64,9 +64,12 @@ public class ReplInterpreterTest { ReplInterpreter.LineResult lineResult = repl.eval(code); Object actual; - if (lineResult.isSuccessful()) { + if (lineResult.getType() == ReplInterpreter.LineResultType.SUCCESS) { actual = lineResult.getValue(); } + else if (lineResult.getType() == ReplInterpreter.LineResultType.INCOMPLETE) { + actual = "incomplete"; + } else { actual = lineResult.getErrorText(); } @@ -143,5 +146,14 @@ public class ReplInterpreterTest { testFile("evaluationErrors.repl"); } + @Test + public void multiline() { + testFile("multiline.repl"); + } + + @Test + public void multiline3() { + testFile("multiline3.repl"); + } }