001/*
002 * Copyright 2010-2013 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.jetbrains.jet.cli.jvm.repl;
018
019import com.google.common.base.Predicates;
020import com.google.common.base.Throwables;
021import com.google.common.collect.Lists;
022import com.intellij.openapi.Disposable;
023import com.intellij.openapi.project.Project;
024import com.intellij.openapi.util.Pair;
025import com.intellij.openapi.vfs.CharsetToolkit;
026import com.intellij.psi.PsiFile;
027import com.intellij.psi.PsiFileFactory;
028import com.intellij.psi.impl.PsiFileFactoryImpl;
029import com.intellij.testFramework.LightVirtualFile;
030import org.jetbrains.annotations.NotNull;
031import org.jetbrains.annotations.Nullable;
032import org.jetbrains.jet.analyzer.AnalyzeExhaust;
033import org.jetbrains.jet.cli.common.messages.AnalyzerWithCompilerReport;
034import org.jetbrains.jet.cli.common.messages.MessageCollector;
035import org.jetbrains.jet.cli.common.messages.MessageCollectorToString;
036import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys;
037import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment;
038import org.jetbrains.jet.codegen.ClassBuilderFactories;
039import org.jetbrains.jet.codegen.CompilationErrorHandler;
040import org.jetbrains.jet.codegen.state.GenerationState;
041import org.jetbrains.jet.config.CompilerConfiguration;
042import org.jetbrains.jet.di.InjectorForTopDownAnalyzerForJvm;
043import org.jetbrains.jet.lang.descriptors.ModuleDescriptorImpl;
044import org.jetbrains.jet.lang.descriptors.ScriptDescriptor;
045import org.jetbrains.jet.lang.descriptors.impl.NamespaceDescriptorImpl;
046import org.jetbrains.jet.lang.descriptors.impl.NamespaceLikeBuilderDummy;
047import org.jetbrains.jet.lang.psi.JetFile;
048import org.jetbrains.jet.lang.resolve.*;
049import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM;
050import org.jetbrains.jet.lang.resolve.java.JvmClassName;
051import org.jetbrains.jet.lang.resolve.name.FqName;
052import org.jetbrains.jet.lang.resolve.scopes.JetScope;
053import org.jetbrains.jet.lang.resolve.scopes.WritableScope;
054import org.jetbrains.jet.lang.resolve.scopes.WritableScopeImpl;
055import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
056import org.jetbrains.jet.plugin.JetLanguage;
057import org.jetbrains.jet.utils.ExceptionUtils;
058
059import java.io.File;
060import java.io.PrintWriter;
061import java.lang.reflect.Constructor;
062import java.lang.reflect.Field;
063import java.net.MalformedURLException;
064import java.net.URL;
065import java.net.URLClassLoader;
066import java.util.Collections;
067import java.util.List;
068
069public class ReplInterpreter {
070
071    private int lineNumber = 0;
072    @Nullable
073    private JetScope lastLineScope;
074    private List<EarlierLine> earlierLines = Lists.newArrayList();
075    private List<String> previousIncompleteLines = Lists.newArrayList();
076    private final ReplClassLoader classLoader;
077
078    @NotNull
079    private final InjectorForTopDownAnalyzerForJvm injector;
080    @NotNull
081    private final JetCoreEnvironment jetCoreEnvironment;
082    @NotNull
083    private final BindingTraceContext trace;
084    @NotNull
085    private final ModuleDescriptorImpl module;
086
087    public ReplInterpreter(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) {
088        jetCoreEnvironment = new JetCoreEnvironment(disposable, configuration);
089        Project project = jetCoreEnvironment.getProject();
090        trace = new BindingTraceContext();
091        module = AnalyzerFacadeForJVM.createJavaModule("<repl>");
092        TopDownAnalysisParameters topDownAnalysisParameters = new TopDownAnalysisParameters(
093                Predicates.<PsiFile>alwaysTrue(),
094                false,
095                true,
096                Collections.<AnalyzerScriptParameter>emptyList());
097        injector = new InjectorForTopDownAnalyzerForJvm(project, topDownAnalysisParameters, trace, module);
098        module.setModuleConfiguration(injector.getJavaBridgeConfiguration());
099
100        List<URL> classpath = Lists.newArrayList();
101
102        for (File file : configuration.getList(JVMConfigurationKeys.CLASSPATH_KEY)) {
103            try {
104                classpath.add(file.toURI().toURL());
105            }
106            catch (MalformedURLException e) {
107                throw ExceptionUtils.rethrow(e);
108            }
109        }
110
111        classLoader = new ReplClassLoader(new URLClassLoader(classpath.toArray(new URL[0])));
112    }
113
114    public enum LineResultType {
115        SUCCESS,
116        ERROR,
117        INCOMPLETE,
118    }
119
120    public static class LineResult {
121
122        private final Object value;
123        private final boolean unit;
124        private final String errorText;
125        @NotNull
126        private final LineResultType type;
127
128        private LineResult(Object value, boolean unit, String errorText, @NotNull LineResultType type) {
129            this.value = value;
130            this.unit = unit;
131            this.errorText = errorText;
132            this.type = type;
133        }
134
135        @NotNull
136        public LineResultType getType() {
137            return type;
138        }
139
140        private void checkSuccessful() {
141            if (!(getType() == LineResultType.SUCCESS)) {
142                throw new IllegalStateException("it is error");
143            }
144        }
145
146        public Object getValue() {
147            checkSuccessful();
148            return value;
149        }
150
151        public boolean isUnit() {
152            checkSuccessful();
153            return unit;
154        }
155
156        @NotNull
157        public String getErrorText() {
158            return errorText;
159        }
160
161        public static LineResult successful(Object value, boolean unit) {
162            return new LineResult(value, unit, null, LineResultType.SUCCESS);
163        }
164
165        public static LineResult error(@NotNull String errorText) {
166            if (errorText.isEmpty()) {
167                errorText = "<unknown error>";
168            }
169            else if (!errorText.endsWith("\n")) {
170                errorText = errorText + "\n";
171            }
172            return new LineResult(null, false, errorText, LineResultType.ERROR);
173        }
174
175        public static LineResult incomplete() {
176            return new LineResult(null, false, null, LineResultType.INCOMPLETE);
177        }
178    }
179
180    @NotNull
181    public LineResult eval(@NotNull String line) {
182        ++lineNumber;
183
184        JvmClassName scriptClassName = JvmClassName.byInternalName("Line" + lineNumber);
185
186        StringBuilder fullText = new StringBuilder();
187        for (String prevLine : previousIncompleteLines) {
188            fullText.append(prevLine + "\n");
189        }
190        fullText.append(line);
191
192        LightVirtualFile virtualFile = new LightVirtualFile("line" + lineNumber + ".ktscript", JetLanguage.INSTANCE, fullText.toString());
193        virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET);
194        JetFile psiFile = (JetFile) ((PsiFileFactoryImpl) PsiFileFactory.getInstance(jetCoreEnvironment.getProject())).trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false);
195
196        MessageCollectorToString errorCollector = new MessageCollectorToString();
197
198        AnalyzerWithCompilerReport.SyntaxErrorReport syntaxErrorReport =
199                AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorCollector);
200
201        if (syntaxErrorReport.isOnlyErrorAtEof()) {
202            previousIncompleteLines.add(line);
203            return LineResult.incomplete();
204        }
205
206        previousIncompleteLines.clear();
207
208        if (syntaxErrorReport.isHasErrors()) {
209            return LineResult.error(errorCollector.getString());
210        }
211
212        injector.getTopDownAnalyzer().prepareForTheNextReplLine();
213        trace.clearDiagnostics();
214
215        psiFile.getScript().putUserData(ScriptHeaderResolver.PRIORITY_KEY, lineNumber);
216
217        ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorCollector);
218        if (scriptDescriptor == null) {
219            return LineResult.error(errorCollector.getString());
220        }
221
222        List<Pair<ScriptDescriptor, JvmClassName>> earierScripts = Lists.newArrayList();
223
224        for (EarlierLine earlierLine : earlierLines) {
225            earierScripts.add(Pair.create(earlierLine.getScriptDescriptor(), earlierLine.getClassName()));
226        }
227
228        BindingContext bindingContext = AnalyzeExhaust.success(trace.getBindingContext(), module).getBindingContext();
229        GenerationState generationState = new GenerationState(psiFile.getProject(), ClassBuilderFactories.binaries(false),
230                                                              bindingContext, Collections.singletonList(psiFile));
231        generationState.getScriptCodegen().compileScript(psiFile.getScript(), scriptClassName, earierScripts,
232                                                         CompilationErrorHandler.THROW_EXCEPTION);
233
234        for (String file : generationState.getFactory().files()) {
235            classLoader.addClass(JvmClassName.byInternalName(file.replaceFirst("\\.class$", "")), generationState.getFactory().asBytes(file));
236        }
237
238        try {
239            Class<?> scriptClass = classLoader.loadClass(scriptClassName.getFqName().asString());
240
241            Class<?>[] constructorParams = new Class<?>[earlierLines.size()];
242            Object[] constructorArgs = new Object[earlierLines.size()];
243
244            for (int i = 0; i < earlierLines.size(); ++i) {
245                constructorParams[i] = earlierLines.get(i).getScriptClass();
246                constructorArgs[i] = earlierLines.get(i).getScriptInstance();
247            }
248
249            Constructor<?> scriptInstanceConstructor = scriptClass.getConstructor(constructorParams);
250            Object scriptInstance;
251            try {
252                scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs);
253            } catch (Throwable e) {
254                return LineResult.error(Throwables.getStackTraceAsString(e));
255            }
256            Field rvField = scriptClass.getDeclaredField("rv");
257            rvField.setAccessible(true);
258            Object rv = rvField.get(scriptInstance);
259
260            earlierLines.add(new EarlierLine(line, scriptDescriptor, scriptClass, scriptInstance, scriptClassName));
261
262            return LineResult.successful(rv, scriptDescriptor.getReturnType().equals(KotlinBuiltIns.getInstance().getUnitType()));
263        } catch (Throwable e) {
264            PrintWriter writer = new PrintWriter(System.err);
265            classLoader.dumpClasses(writer);
266            writer.flush();
267            throw ExceptionUtils.rethrow(e);
268        }
269    }
270
271    @Nullable
272    private ScriptDescriptor doAnalyze(@NotNull JetFile psiFile, @NotNull MessageCollector messageCollector) {
273        WritableScope scope = new WritableScopeImpl(
274                JetScope.EMPTY, module,
275                new TraceBasedRedeclarationHandler(trace), "Root scope in analyzeNamespace");
276
277        scope.changeLockLevel(WritableScope.LockLevel.BOTH);
278
279        NamespaceDescriptorImpl rootNs = injector.getNamespaceFactory().createNamespaceDescriptorPathIfNeeded(FqName.ROOT);
280
281        // map "jet" namespace into KotlinBuiltIns
282        // @see DefaultModuleConfiguraiton#extendNamespaceScope
283        injector.getNamespaceFactory().createNamespaceDescriptorPathIfNeeded(KotlinBuiltIns.getInstance().getBuiltInsPackageFqName());
284
285        // Import a scope that contains all top-level namespaces that come from dependencies
286        // This makes the namespaces visible at all, does not import themselves
287        scope.importScope(rootNs.getMemberScope());
288
289        if (lastLineScope != null) {
290            scope.importScope(lastLineScope);
291        }
292
293        scope.changeLockLevel(WritableScope.LockLevel.READING);
294
295        // dummy builder is used because "root" is module descriptor,
296        // namespaces added to module explicitly in
297        injector.getTopDownAnalyzer().doProcess(scope, new NamespaceLikeBuilderDummy(), Collections.singletonList(psiFile));
298
299        boolean hasErrors = AnalyzerWithCompilerReport.reportDiagnostics(trace.getBindingContext(), messageCollector);
300        if (hasErrors) {
301            return null;
302        }
303
304        ScriptDescriptor scriptDescriptor = injector.getTopDownAnalysisContext().getScripts().get(psiFile.getScript());
305        lastLineScope = trace.get(BindingContext.SCRIPT_SCOPE, scriptDescriptor);
306        if (lastLineScope == null) {
307            throw new IllegalStateException("last line scope is not initialized");
308        }
309
310        return scriptDescriptor;
311    }
312
313    public void dumpClasses(@NotNull PrintWriter out) {
314        classLoader.dumpClasses(out);
315    }
316}