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