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