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.analyzer.AnalyzeExhaust;
033    import org.jetbrains.jet.cli.common.messages.AnalyzerWithCompilerReport;
034    import org.jetbrains.jet.cli.common.messages.MessageCollector;
035    import org.jetbrains.jet.cli.common.messages.MessageCollectorToString;
036    import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys;
037    import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment;
038    import org.jetbrains.jet.codegen.ClassBuilderFactories;
039    import org.jetbrains.jet.codegen.CompilationErrorHandler;
040    import org.jetbrains.jet.codegen.state.GenerationState;
041    import org.jetbrains.jet.config.CompilerConfiguration;
042    import org.jetbrains.jet.di.InjectorForTopDownAnalyzerForJvm;
043    import org.jetbrains.jet.lang.descriptors.ModuleDescriptorImpl;
044    import org.jetbrains.jet.lang.descriptors.ScriptDescriptor;
045    import org.jetbrains.jet.lang.descriptors.impl.NamespaceDescriptorImpl;
046    import org.jetbrains.jet.lang.descriptors.impl.NamespaceLikeBuilderDummy;
047    import org.jetbrains.jet.lang.psi.JetFile;
048    import org.jetbrains.jet.lang.resolve.*;
049    import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM;
050    import org.jetbrains.jet.lang.resolve.java.JvmClassName;
051    import org.jetbrains.jet.lang.resolve.name.FqName;
052    import org.jetbrains.jet.lang.resolve.scopes.JetScope;
053    import org.jetbrains.jet.lang.resolve.scopes.WritableScope;
054    import org.jetbrains.jet.lang.resolve.scopes.WritableScopeImpl;
055    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
056    import org.jetbrains.jet.plugin.JetLanguage;
057    import org.jetbrains.jet.utils.ExceptionUtils;
058    
059    import java.io.File;
060    import java.io.PrintWriter;
061    import java.lang.reflect.Constructor;
062    import java.lang.reflect.Field;
063    import java.net.MalformedURLException;
064    import java.net.URL;
065    import java.net.URLClassLoader;
066    import java.util.Collections;
067    import java.util.List;
068    
069    public 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    }