do not exit repl if something went wrong

This commit is contained in:
Stepan Koltsov
2012-06-14 18:30:49 +04:00
parent e0c389b239
commit 970c2ae1f9
11 changed files with 325 additions and 57 deletions
@@ -18,8 +18,8 @@ package org.jetbrains.jet.cli.common.messages;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiRecursiveElementWalkingVisitor;
import jet.Function0;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -75,8 +75,8 @@ public final class AnalyzerWithCompilerReport {
};
}
private void reportDiagnostic(@NotNull Diagnostic diagnostic) {
if (!diagnostic.isValid()) return;
private static boolean reportDiagnostic(@NotNull Diagnostic diagnostic, @NotNull MessageCollector messageCollector) {
if (!diagnostic.isValid()) return false;
DiagnosticUtils.LineAndColumn lineAndColumn = DiagnosticUtils.getLineAndColumn(diagnostic);
VirtualFile virtualFile = diagnostic.getPsiFile().getVirtualFile();
String path = virtualFile == null ? null : virtualFile.getPath();
@@ -87,8 +87,9 @@ public final class AnalyzerWithCompilerReport {
else {
render = DefaultErrorMessages.RENDERER.render(diagnostic);
}
messageCollectorWrapper.report(convertSeverity(diagnostic.getSeverity()), render,
CompilerMessageLocation.create(path, lineAndColumn.getLine(), lineAndColumn.getColumn()));
messageCollector.report(convertSeverity(diagnostic.getSeverity()), render,
CompilerMessageLocation.create(path, lineAndColumn.getLine(), lineAndColumn.getColumn()));
return diagnostic.getSeverity() == Severity.ERROR;
}
private void reportIncompleteHierarchies() {
@@ -104,27 +105,40 @@ public final class AnalyzerWithCompilerReport {
}
}
private void reportDiagnostics() {
assert analyzeExhaust != null;
for (Diagnostic diagnostic : analyzeExhaust.getBindingContext().getDiagnostics()) {
reportDiagnostic(diagnostic);
public static boolean reportDiagnostics(@NotNull BindingContext bindingContext, @NotNull MessageCollector messageCollector) {
boolean hasErrors = false;
for (Diagnostic diagnostic : bindingContext.getDiagnostics()) {
hasErrors |= reportDiagnostic(diagnostic, messageCollector);
}
return hasErrors;
}
private void reportSyntaxErrors(@NotNull Collection<JetFile> files) {
for (JetFile file : files) {
file.accept(new AnalyzingUtils.PsiErrorElementVisitor() {
@Override
public void visitErrorElement(PsiErrorElement element) {
String description = element.getErrorDescription();
String message = StringUtil.isEmpty(description) ? "Syntax error" : description;
Diagnostic diagnostic = new SyntaxErrorDiagnostic(element, Severity.ERROR, message);
reportDiagnostic(diagnostic);
}
});
for (PsiElement file : files) {
reportSyntaxErrors(file, AnalyzerWithCompilerReport.this.messageCollectorWrapper);
}
}
public static boolean reportSyntaxErrors(@NotNull PsiElement file, @NotNull final MessageCollector messageCollector) {
class ErrorReportingVisitor extends AnalyzingUtils.PsiErrorElementVisitor {
boolean hasErrors = false;
@Override
public void visitErrorElement(PsiErrorElement element) {
String description = element.getErrorDescription();
String message = StringUtil.isEmpty(description) ? "Syntax error" : description;
Diagnostic diagnostic = new SyntaxErrorDiagnostic(element, Severity.ERROR, message);
reportDiagnostic(diagnostic, messageCollector);
hasErrors = true;
}
}
ErrorReportingVisitor visitor = new ErrorReportingVisitor();
file.accept(visitor);
return visitor.hasErrors;
}
@Nullable
public AnalyzeExhaust getAnalyzeExhaust() {
return analyzeExhaust;
@@ -137,7 +151,7 @@ public final class AnalyzerWithCompilerReport {
public void analyzeAndReport(@NotNull Function0<AnalyzeExhaust> analyzer, @NotNull Collection<JetFile> files) {
reportSyntaxErrors(files);
analyzeExhaust = analyzer.invoke();
reportDiagnostics();
reportDiagnostics(analyzeExhaust.getBindingContext(), messageCollectorWrapper);
reportIncompleteHierarchies();
}
@@ -19,12 +19,7 @@ package org.jetbrains.jet.cli.common.messages;
import org.jetbrains.annotations.NotNull;
public interface MessageCollector {
MessageCollector PLAIN_TEXT_TO_SYSTEM_ERR = new MessageCollector() {
@Override
public void report(@NotNull CompilerMessageSeverity severity, @NotNull String message, @NotNull CompilerMessageLocation location) {
System.err.println(MessageRenderer.PLAIN.render(severity, message, location));
}
};
MessageCollector PLAIN_TEXT_TO_SYSTEM_ERR = new MessageCollectorPlainTextToStream(System.err);
void report(@NotNull CompilerMessageSeverity severity, @NotNull String message, @NotNull CompilerMessageLocation location);
}
@@ -0,0 +1,39 @@
/*
* Copyright 2010-2012 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.jet.cli.common.messages;
import org.jetbrains.annotations.NotNull;
import java.io.PrintStream;
/**
* @author Stepan Koltsov
*/
public class MessageCollectorPlainTextToStream implements MessageCollector {
@NotNull
private final PrintStream stream;
public MessageCollectorPlainTextToStream(@NotNull PrintStream stream) {
this.stream = stream;
}
@Override
public void report(@NotNull CompilerMessageSeverity severity, @NotNull String message, @NotNull CompilerMessageLocation location) {
stream.println(MessageRenderer.PLAIN.render(severity, message, location));
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2012 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.jet.cli.common.messages;
import org.jetbrains.annotations.NotNull;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
/**
* @author Stepan Koltsov
*/
public class MessageCollectorToString implements MessageCollector {
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
private final MessageCollector actualCollector = new MessageCollectorPlainTextToStream(new PrintStream(outputStream));
@Override
public void report(@NotNull CompilerMessageSeverity severity, @NotNull String message, @NotNull CompilerMessageLocation location) {
actualCollector.report(severity, message, location);
}
private static Charset UTF8 = Charset.forName("utf-8");
@NotNull
public String getString() {
return UTF8.decode(ByteBuffer.wrap(outputStream.toByteArray())).toString();
}
}
@@ -117,7 +117,10 @@ public class ReplFromTerminal {
}
ReplInterpreter.LineResult lineResult = getReplInterpreter().eval(line);
if (!lineResult.isUnit()) {
if (!lineResult.isSuccessful()) {
System.out.print(lineResult.getErrorText());
}
else if (!lineResult.isUnit()) {
System.out.println(lineResult.getValue());
}
return true;
@@ -17,6 +17,7 @@
package org.jetbrains.jet.cli.jvm.repl;
import com.google.common.base.Predicates;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.project.Project;
@@ -29,6 +30,9 @@ import com.intellij.testFramework.LightVirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.analyzer.AnalyzeExhaust;
import org.jetbrains.jet.cli.common.messages.AnalyzerWithCompilerReport;
import org.jetbrains.jet.cli.common.messages.MessageCollector;
import org.jetbrains.jet.cli.common.messages.MessageCollectorToString;
import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment;
import org.jetbrains.jet.codegen.ClassBuilderFactories;
import org.jetbrains.jet.codegen.CompilationErrorHandler;
@@ -40,7 +44,6 @@ import org.jetbrains.jet.lang.descriptors.NamespaceLikeBuilderDummy;
import org.jetbrains.jet.lang.descriptors.ScriptDescriptor;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter;
import org.jetbrains.jet.lang.resolve.AnalyzingUtils;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.BindingTraceContext;
import org.jetbrains.jet.lang.resolve.ScriptHeaderResolver;
@@ -60,7 +63,6 @@ import org.jetbrains.jet.utils.ExceptionUtils;
import org.jetbrains.jet.utils.Progress;
import java.io.File;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -122,19 +124,52 @@ public class ReplInterpreter {
public static class LineResult {
private final Object value;
private final boolean unit;
private final String errorText;
public LineResult(Object value, boolean unit) {
private LineResult(Object value, boolean unit, String errorText) {
this.value = value;
this.unit = unit;
this.errorText = errorText;
}
public boolean isSuccessful() {
return errorText == null;
}
private void checkSuccessful() {
if (!isSuccessful()) {
throw new IllegalStateException("it is error");
}
}
public Object getValue() {
checkSuccessful();
return value;
}
public boolean isUnit() {
checkSuccessful();
return unit;
}
@NotNull
public String getErrorText() {
return errorText;
}
public static LineResult successful(Object value, boolean unit) {
return new LineResult(value, unit, null);
}
public static LineResult error(@NotNull String errorText) {
if (errorText.isEmpty()) {
errorText = "<unknown error>";
}
else if (!errorText.endsWith("\n")) {
errorText = errorText + "\n";
}
return new LineResult(null, false, errorText);
}
}
@NotNull
@@ -147,13 +182,21 @@ public class ReplInterpreter {
virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET);
JetFile psiFile = (JetFile) ((PsiFileFactoryImpl) PsiFileFactory.getInstance(jetCoreEnvironment.getProject())).trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false);
AnalyzingUtils.checkForSyntacticErrors(psiFile);
MessageCollectorToString errorCollector = new MessageCollectorToString();
boolean hasErrors = AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorCollector);
if (hasErrors) {
return LineResult.error(errorCollector.getString());
}
injector.getTopDownAnalyzer().prepareForTheNextReplLine();
psiFile.getScript().putUserData(ScriptHeaderResolver.PRIORITY_KEY, lineNumber);
ScriptDescriptor scriptDescriptor = doAnalyze(psiFile);
ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorCollector);
if (scriptDescriptor == null) {
return LineResult.error(errorCollector.getString());
}
Progress backendProgress = new Progress() {
@Override
@@ -188,14 +231,19 @@ public class ReplInterpreter {
}
Constructor<?> scriptInstanceConstructor = scriptClass.getConstructor(constructorParams);
Object scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs);
Object scriptInstance;
try {
scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs);
} catch (Throwable e) {
return LineResult.error(Throwables.getStackTraceAsString(e));
}
Field rvField = scriptClass.getDeclaredField("rv");
rvField.setAccessible(true);
Object rv = rvField.get(scriptInstance);
earlierLines.add(new EarlierLine(line, scriptDescriptor, scriptClass, scriptInstance, scriptClassName));
return new LineResult(rv, scriptDescriptor.getReturnType().equals(JetStandardClasses.getUnitType()));
return LineResult.successful(rv, scriptDescriptor.getReturnType().equals(JetStandardClasses.getUnitType()));
} catch (Throwable e) {
PrintWriter writer = new PrintWriter(System.err);
classLoader.dumpClasses(writer);
@@ -204,7 +252,8 @@ public class ReplInterpreter {
}
}
private ScriptDescriptor doAnalyze(@NotNull JetFile psiFile) {
@Nullable
private ScriptDescriptor doAnalyze(@NotNull JetFile psiFile, @NotNull MessageCollector messageCollector) {
final WritableScope scope = new WritableScopeImpl(
JetScope.EMPTY, module,
new TraceBasedRedeclarationHandler(trace), "Root scope in analyzeNamespace");
@@ -231,8 +280,10 @@ public class ReplInterpreter {
// namespaces added to module explicitly in
injector.getTopDownAnalyzer().doProcess(scope, new NamespaceLikeBuilderDummy(), Collections.singletonList(psiFile));
// TODO: print, not throw
AnalyzingUtils.throwExceptionOnErrors(trace.getBindingContext());
boolean hasErrors = AnalyzerWithCompilerReport.reportDiagnostics(trace.getBindingContext(), messageCollector);
if (hasErrors) {
return null;
}
ScriptDescriptor scriptDescriptor = injector.getTopDownAnalysisContext().getScripts().get(psiFile.getScript());
lastLineScope = trace.get(BindingContext.SCRIPT_SCOPE, scriptDescriptor);
@@ -0,0 +1,6 @@
>>> fun foo() = 765
null
>>> foo(1)
ERROR: /line2.ktscript: (1, 5) Too many arguments for internal final fun foo() : jet.Int defined in <script>
>>> foo()
765
@@ -0,0 +1,6 @@
>>> throw Exception("hi there")
stack trace
>>> fun foo() = 2
null
>>> foo()
2
@@ -58,10 +58,17 @@ public class ReplInterpreterTest {
ReplSessionTestFile file = ReplSessionTestFile.load(new File("compiler/testData/repl/" + relativePath));
for (Pair<String, String> t : file.getLines()) {
String code = t.first;
String expected = t.second;
String expected = t.second.replaceFirst("\r?\n$", "");
Object actual = repl.eval(code).getValue();
String actualString = actual != null ? actual.toString() : "null";
ReplInterpreter.LineResult lineResult = repl.eval(code);
Object actual;
if (lineResult.isSuccessful()) {
actual = lineResult.getValue();
}
else {
actual = lineResult.getErrorText();
}
String actualString = (actual != null ? actual.toString() : "null").replaceFirst("\r?\n$", "");
Assert.assertEquals("after evaluation of: " + code, expected, actualString);
}
@@ -113,4 +120,22 @@ public class ReplInterpreterTest {
}
@Test
public void syntaxErrors() {
testFile("syntaxErrors.repl");
}
@Test
public void analyzeErrors() {
// TODO
//testFile("analyzeErrors.repl");
}
@Test
public void evaluationErrors() {
// TODO
//testFile("evaluationErrors.repl");
}
}
@@ -17,7 +17,9 @@
package org.jetbrains.jet.repl;
import com.google.common.collect.Lists;
import com.google.common.io.CharStreams;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
@@ -51,7 +53,8 @@ public class ReplSessionTestFile {
FileInputStream inputStream = new FileInputStream(file);
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
return load(reader);
List<String> lines = CharStreams.readLines(reader);
return load(new SimpleLinesParser(lines));
} finally {
inputStream.close();
}
@@ -60,28 +63,22 @@ public class ReplSessionTestFile {
}
}
private static ReplSessionTestFile load(@NotNull BufferedReader reader) throws IOException {
private static ReplSessionTestFile load(@NotNull SimpleLinesParser parser) throws IOException {
List<Pair<String, String>> list = Lists.newArrayList();
while (true) {
String odd = reader.readLine();
if (odd == null) {
return new ReplSessionTestFile(list);
}
Pattern pattern = Pattern.compile(">>>( |$)(.*)");
Matcher matcher = pattern.matcher(odd);
if (!matcher.matches()) {
throw new IllegalStateException("odd lines must start with >>>");
}
while (!parser.lookingAtEof()) {
Pattern startPattern = Pattern.compile(">>>( |$)(.*)");
Matcher matcher = parser.next(startPattern);
String code = matcher.group(2);
String even = reader.readLine();
if (even == null) {
throw new IllegalStateException("expecting even");
StringBuilder value = new StringBuilder();
while (!parser.lookingAtEof() && parser.lookingAt(startPattern) == null) {
value.append(parser.next()).append("\n");
}
list.add(Pair.create(code, even));
list.add(Pair.create(code, value.toString()));
}
return new ReplSessionTestFile(list);
}
}
@@ -0,0 +1,87 @@
/*
* Copyright 2010-2012 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.jet.repl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Stepan Koltsov
*
* @see org.jetbrains.jet.lang.types.ref.SimpleParser
*/
class SimpleLinesParser {
@NotNull
private final List<String> lines;
private int position;
SimpleLinesParser(@NotNull List<String> lines) {
this.lines = lines;
}
public boolean lookingAtEof() {
return position == lines.size();
}
public void checkNotEof() {
if (lookingAtEof()) {
throw new IllegalStateException("unexpected EOF");
}
}
@NotNull
public String lookahead() {
checkNotEof();
return lines.get(position);
}
@Nullable
public Matcher lookingAt(@NotNull Pattern pattern) {
if (lookingAtEof()) {
return null;
}
Matcher matcher = pattern.matcher(lookahead());
if (matcher.matches()) {
return matcher;
}
else {
return null;
}
}
@NotNull
public Matcher next(@NotNull Pattern pattern) {
Matcher r = lookingAt(pattern);
if (r == null) {
throw new IllegalStateException("line " + position + " must match " + pattern);
}
++position;
return r;
}
@NotNull
public String next() {
String r = lookahead();
++position;
return r;
}
}