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 }