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