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    
017    package org.jetbrains.jet.cli.jvm.repl;
018    
019    import com.google.common.base.Predicates;
020    import com.google.common.base.Throwables;
021    import com.google.common.collect.Lists;
022    import com.intellij.openapi.Disposable;
023    import com.intellij.openapi.project.Project;
024    import com.intellij.openapi.util.Pair;
025    import com.intellij.openapi.vfs.CharsetToolkit;
026    import com.intellij.psi.PsiFile;
027    import com.intellij.psi.PsiFileFactory;
028    import com.intellij.psi.impl.PsiFileFactoryImpl;
029    import com.intellij.testFramework.LightVirtualFile;
030    import org.jetbrains.annotations.NotNull;
031    import org.jetbrains.annotations.Nullable;
032    import org.jetbrains.asm4.Type;
033    import org.jetbrains.jet.OutputFile;
034    import org.jetbrains.jet.analyzer.AnalyzeExhaust;
035    import org.jetbrains.jet.cli.common.messages.AnalyzerWithCompilerReport;
036    import org.jetbrains.jet.cli.common.messages.MessageCollector;
037    import org.jetbrains.jet.cli.common.messages.MessageCollectorToString;
038    import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys;
039    import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment;
040    import org.jetbrains.jet.codegen.ClassBuilderFactories;
041    import org.jetbrains.jet.codegen.CompilationErrorHandler;
042    import org.jetbrains.jet.codegen.KotlinCodegenFacade;
043    import org.jetbrains.jet.codegen.state.GenerationState;
044    import org.jetbrains.jet.config.CompilerConfiguration;
045    import org.jetbrains.jet.di.InjectorForTopDownAnalyzerForJvm;
046    import org.jetbrains.jet.lang.descriptors.ModuleDescriptorImpl;
047    import org.jetbrains.jet.lang.descriptors.ScriptDescriptor;
048    import org.jetbrains.jet.lang.descriptors.impl.PackageLikeBuilderDummy;
049    import org.jetbrains.jet.lang.psi.JetFile;
050    import org.jetbrains.jet.lang.psi.JetPsiUtil;
051    import org.jetbrains.jet.lang.psi.JetScript;
052    import org.jetbrains.jet.lang.resolve.*;
053    import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM;
054    import org.jetbrains.jet.lang.resolve.java.JvmClassName;
055    import org.jetbrains.jet.lang.resolve.name.FqName;
056    import org.jetbrains.jet.lang.resolve.scopes.JetScope;
057    import org.jetbrains.jet.lang.resolve.scopes.WritableScope;
058    import org.jetbrains.jet.lang.resolve.scopes.WritableScopeImpl;
059    import org.jetbrains.jet.lang.types.lang.InlineUtil;
060    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
061    import org.jetbrains.jet.plugin.JetLanguage;
062    import org.jetbrains.jet.storage.ExceptionTracker;
063    import org.jetbrains.jet.storage.LockBasedStorageManager;
064    import org.jetbrains.jet.utils.UtilsPackage;
065    
066    import java.io.File;
067    import java.io.PrintWriter;
068    import java.lang.reflect.Constructor;
069    import java.lang.reflect.Field;
070    import java.net.MalformedURLException;
071    import java.net.URL;
072    import java.net.URLClassLoader;
073    import java.util.Collections;
074    import java.util.List;
075    
076    import static org.jetbrains.jet.codegen.AsmUtil.asmTypeByFqNameWithoutInnerClasses;
077    import static org.jetbrains.jet.codegen.binding.CodegenBinding.registerClassNameForScript;
078    import static org.jetbrains.jet.lang.descriptors.DependencyKind.BINARIES;
079    import static org.jetbrains.jet.lang.descriptors.DependencyKind.BUILT_INS;
080    import static org.jetbrains.jet.lang.descriptors.DependencyKind.SOURCES;
081    
082    public class ReplInterpreter {
083    
084        private int lineNumber = 0;
085        @Nullable
086        private JetScope lastLineScope;
087        private List<EarlierLine> earlierLines = Lists.newArrayList();
088        private List<String> previousIncompleteLines = Lists.newArrayList();
089        private final ReplClassLoader classLoader;
090    
091        @NotNull
092        private final InjectorForTopDownAnalyzerForJvm injector;
093        @NotNull
094        private final TopDownAnalysisContext topDownAnalysisContext;
095        @NotNull
096        private final JetCoreEnvironment jetCoreEnvironment;
097        @NotNull
098        private final BindingTraceContext trace;
099        @NotNull
100        private final ModuleDescriptorImpl module;
101    
102        public ReplInterpreter(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) {
103            jetCoreEnvironment = JetCoreEnvironment.createForProduction(disposable, configuration);
104            Project project = jetCoreEnvironment.getProject();
105            trace = new BindingTraceContext();
106            module = AnalyzerFacadeForJVM.createJavaModule("<repl>");
107            TopDownAnalysisParameters topDownAnalysisParameters = new TopDownAnalysisParameters(
108                    new LockBasedStorageManager(),
109                    new ExceptionTracker(), // dummy
110                    Predicates.<PsiFile>alwaysTrue(),
111                    false,
112                    true,
113                    Collections.<AnalyzerScriptParameter>emptyList());
114            injector = new InjectorForTopDownAnalyzerForJvm(project, topDownAnalysisParameters, trace, module);
115            topDownAnalysisContext = new TopDownAnalysisContext(topDownAnalysisParameters);
116            module.addFragmentProvider(SOURCES, injector.getTopDownAnalyzer().getPackageFragmentProvider());
117            module.addFragmentProvider(BUILT_INS, KotlinBuiltIns.getInstance().getBuiltInsModule().getPackageFragmentProvider());
118            module.addFragmentProvider(BINARIES, injector.getJavaDescriptorResolver().getPackageFragmentProvider());
119    
120            List<URL> classpath = Lists.newArrayList();
121    
122            for (File file : configuration.getList(JVMConfigurationKeys.CLASSPATH_KEY)) {
123                try {
124                    classpath.add(file.toURI().toURL());
125                }
126                catch (MalformedURLException e) {
127                    throw UtilsPackage.rethrow(e);
128                }
129            }
130    
131            classLoader = new ReplClassLoader(new URLClassLoader(classpath.toArray(new URL[0])));
132        }
133    
134        public enum LineResultType {
135            SUCCESS,
136            ERROR,
137            INCOMPLETE,
138        }
139    
140        public static class LineResult {
141    
142            private final Object value;
143            private final boolean unit;
144            private final String errorText;
145            @NotNull
146            private final LineResultType type;
147    
148            private LineResult(Object value, boolean unit, String errorText, @NotNull LineResultType type) {
149                this.value = value;
150                this.unit = unit;
151                this.errorText = errorText;
152                this.type = type;
153            }
154    
155            @NotNull
156            public LineResultType getType() {
157                return type;
158            }
159    
160            private void checkSuccessful() {
161                if (!(getType() == LineResultType.SUCCESS)) {
162                    throw new IllegalStateException("it is error");
163                }
164            }
165    
166            public Object getValue() {
167                checkSuccessful();
168                return value;
169            }
170    
171            public boolean isUnit() {
172                checkSuccessful();
173                return unit;
174            }
175    
176            @NotNull
177            public String getErrorText() {
178                return errorText;
179            }
180    
181            public static LineResult successful(Object value, boolean unit) {
182                return new LineResult(value, unit, null, LineResultType.SUCCESS);
183            }
184    
185            public static LineResult error(@NotNull String errorText) {
186                if (errorText.isEmpty()) {
187                    errorText = "<unknown error>";
188                }
189                else if (!errorText.endsWith("\n")) {
190                    errorText = errorText + "\n";
191                }
192                return new LineResult(null, false, errorText, LineResultType.ERROR);
193            }
194    
195            public static LineResult incomplete() {
196                return new LineResult(null, false, null, LineResultType.INCOMPLETE);
197            }
198        }
199    
200        @NotNull
201        public LineResult eval(@NotNull String line) {
202            ++lineNumber;
203    
204            FqName scriptFqName = new FqName("Line" + lineNumber);
205            Type scriptClassType = asmTypeByFqNameWithoutInnerClasses(scriptFqName);
206    
207            StringBuilder fullText = new StringBuilder();
208            for (String prevLine : previousIncompleteLines) {
209                fullText.append(prevLine + "\n");
210            }
211            fullText.append(line);
212    
213            LightVirtualFile virtualFile = new LightVirtualFile("line" + lineNumber + ".ktscript", JetLanguage.INSTANCE, fullText.toString());
214            virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET);
215            JetFile psiFile = (JetFile) ((PsiFileFactoryImpl) PsiFileFactory.getInstance(jetCoreEnvironment.getProject())).trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false);
216    
217            MessageCollectorToString errorCollector = new MessageCollectorToString();
218    
219            AnalyzerWithCompilerReport.SyntaxErrorReport syntaxErrorReport =
220                    AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorCollector);
221    
222            if (syntaxErrorReport.isOnlyErrorAtEof()) {
223                previousIncompleteLines.add(line);
224                return LineResult.incomplete();
225            }
226    
227            previousIncompleteLines.clear();
228    
229            if (syntaxErrorReport.isHasErrors()) {
230                return LineResult.error(errorCollector.getString());
231            }
232    
233            injector.getTopDownAnalyzer().prepareForTheNextReplLine(topDownAnalysisContext);
234            trace.clearDiagnostics();
235    
236            psiFile.getScript().putUserData(ScriptHeaderResolver.PRIORITY_KEY, lineNumber);
237    
238            ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorCollector);
239            if (scriptDescriptor == null) {
240                return LineResult.error(errorCollector.getString());
241            }
242    
243            List<Pair<ScriptDescriptor, Type>> earlierScripts = Lists.newArrayList();
244    
245            for (EarlierLine earlierLine : earlierLines) {
246                earlierScripts.add(Pair.create(earlierLine.getScriptDescriptor(), earlierLine.getClassType()));
247            }
248    
249            BindingContext bindingContext = AnalyzeExhaust.success(trace.getBindingContext(), module).getBindingContext();
250            GenerationState generationState = new GenerationState(psiFile.getProject(), ClassBuilderFactories.BINARIES,
251                                                                  bindingContext, Collections.singletonList(psiFile), InlineUtil.DEFAULT_INLINE_FLAG);
252    
253            compileScript(psiFile.getScript(), scriptClassType, earlierScripts, generationState,
254                          CompilationErrorHandler.THROW_EXCEPTION);
255    
256            for (OutputFile outputFile : generationState.getFactory().asList()) {
257                classLoader.addClass(JvmClassName.byInternalName(outputFile.getRelativePath().replaceFirst("\\.class$", "")), outputFile.asByteArray());
258            }
259    
260            try {
261                Class<?> scriptClass = classLoader.loadClass(scriptFqName.asString());
262    
263                Class<?>[] constructorParams = new Class<?>[earlierLines.size()];
264                Object[] constructorArgs = new Object[earlierLines.size()];
265    
266                for (int i = 0; i < earlierLines.size(); ++i) {
267                    constructorParams[i] = earlierLines.get(i).getScriptClass();
268                    constructorArgs[i] = earlierLines.get(i).getScriptInstance();
269                }
270    
271                Constructor<?> scriptInstanceConstructor = scriptClass.getConstructor(constructorParams);
272                Object scriptInstance;
273                try {
274                    scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs);
275                } catch (Throwable e) {
276                    return LineResult.error(Throwables.getStackTraceAsString(e));
277                }
278                Field rvField = scriptClass.getDeclaredField("rv");
279                rvField.setAccessible(true);
280                Object rv = rvField.get(scriptInstance);
281    
282                earlierLines.add(new EarlierLine(line, scriptDescriptor, scriptClass, scriptInstance, scriptClassType));
283    
284                return LineResult.successful(rv, scriptDescriptor.getReturnType().equals(KotlinBuiltIns.getInstance().getUnitType()));
285            } catch (Throwable e) {
286                PrintWriter writer = new PrintWriter(System.err);
287                classLoader.dumpClasses(writer);
288                writer.flush();
289                throw UtilsPackage.rethrow(e);
290            }
291        }
292    
293        @Nullable
294        private ScriptDescriptor doAnalyze(@NotNull JetFile psiFile, @NotNull MessageCollector messageCollector) {
295            WritableScope scope = new WritableScopeImpl(
296                    JetScope.EMPTY, module,
297                    new TraceBasedRedeclarationHandler(trace), "Root scope in analyzePackage");
298    
299            scope.changeLockLevel(WritableScope.LockLevel.BOTH);
300    
301            // Import a scope that contains all top-level packages that come from dependencies
302            // This makes the packages visible at all, does not import themselves
303            scope.importScope(module.getPackage(FqName.ROOT).getMemberScope());
304    
305            if (lastLineScope != null) {
306                scope.importScope(lastLineScope);
307            }
308    
309            scope.changeLockLevel(WritableScope.LockLevel.READING);
310    
311            // dummy builder is used because "root" is module descriptor,
312            // packages added to module explicitly in
313            injector.getTopDownAnalyzer().doProcess(topDownAnalysisContext,
314                                                    scope, new PackageLikeBuilderDummy(), Collections.singletonList(psiFile));
315    
316            boolean hasErrors = AnalyzerWithCompilerReport.reportDiagnostics(trace.getBindingContext(), messageCollector);
317            if (hasErrors) {
318                return null;
319            }
320    
321            ScriptDescriptor scriptDescriptor = topDownAnalysisContext.getScripts().get(psiFile.getScript());
322            lastLineScope = trace.get(BindingContext.SCRIPT_SCOPE, scriptDescriptor);
323            if (lastLineScope == null) {
324                throw new IllegalStateException("last line scope is not initialized");
325            }
326    
327            return scriptDescriptor;
328        }
329    
330        public void dumpClasses(@NotNull PrintWriter out) {
331            classLoader.dumpClasses(out);
332        }
333    
334        private static void registerEarlierScripts(
335                @NotNull GenerationState state,
336                @NotNull List<Pair<ScriptDescriptor, Type>> earlierScripts
337        ) {
338            for (Pair<ScriptDescriptor, Type> t : earlierScripts) {
339                ScriptDescriptor earlierDescriptor = t.first;
340                Type earlierClassType = t.second;
341                registerClassNameForScript(state.getBindingTrace(), earlierDescriptor, earlierClassType);
342            }
343    
344            List<ScriptDescriptor> earlierScriptDescriptors = Lists.newArrayList();
345            for (Pair<ScriptDescriptor, Type> t : earlierScripts) {
346                ScriptDescriptor earlierDescriptor = t.first;
347                earlierScriptDescriptors.add(earlierDescriptor);
348            }
349            state.setEarlierScriptsForReplInterpreter(earlierScriptDescriptors);
350        }
351    
352        public static void compileScript(
353                @NotNull JetScript script,
354                @NotNull Type classType,
355                @NotNull List<Pair<ScriptDescriptor, Type>> earlierScripts,
356                @NotNull GenerationState state,
357                @NotNull CompilationErrorHandler errorHandler
358        ) {
359            registerEarlierScripts(state, earlierScripts);
360            registerClassNameForScript(state.getBindingTrace(), script, classType);
361    
362            state.beforeCompile();
363            KotlinCodegenFacade.generatePackage(
364                    state,
365                    JetPsiUtil.getFQName((JetFile) script.getContainingFile()),
366                    Collections.singleton((JetFile) script.getContainingFile()),
367                    errorHandler);
368        }
369    
370    }