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.di.InjectorForTopDownAnalyzerForJvm;
045 import org.jetbrains.jet.lang.descriptors.ScriptDescriptor;
046 import org.jetbrains.jet.lang.descriptors.impl.ModuleDescriptorImpl;
047 import org.jetbrains.jet.lang.descriptors.impl.PackageLikeBuilderDummy;
048 import org.jetbrains.jet.lang.parsing.JetParserDefinition;
049 import org.jetbrains.jet.lang.psi.JetFile;
050 import org.jetbrains.jet.lang.psi.JetScript;
051 import org.jetbrains.jet.lang.resolve.*;
052 import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM;
053 import org.jetbrains.jet.lang.resolve.java.JvmClassName;
054 import org.jetbrains.jet.lang.resolve.name.FqName;
055 import org.jetbrains.jet.lang.resolve.scopes.JetScope;
056 import org.jetbrains.jet.lang.resolve.scopes.WritableScope;
057 import org.jetbrains.jet.lang.resolve.scopes.WritableScopeImpl;
058 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
059 import org.jetbrains.jet.plugin.JetLanguage;
060 import org.jetbrains.jet.storage.ExceptionTracker;
061 import org.jetbrains.jet.storage.LockBasedStorageManager;
062 import org.jetbrains.jet.utils.UtilsPackage;
063 import org.jetbrains.org.objectweb.asm.Type;
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.ArrayList;
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.*;
079
080 public class ReplInterpreter {
081
082 private int lineNumber = 0;
083 @Nullable
084 private JetScope lastLineScope;
085 private final List<EarlierLine> earlierLines = Lists.newArrayList();
086 private final List<String> previousIncompleteLines = Lists.newArrayList();
087 private final ReplClassLoader classLoader;
088
089 @NotNull
090 private final InjectorForTopDownAnalyzerForJvm injector;
091 @NotNull
092 private final TopDownAnalysisContext topDownAnalysisContext;
093 @NotNull
094 private final JetCoreEnvironment jetCoreEnvironment;
095 @NotNull
096 private final BindingTraceContext trace;
097 @NotNull
098 private final ModuleDescriptorImpl module;
099
100 public ReplInterpreter(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) {
101 jetCoreEnvironment = JetCoreEnvironment.createForProduction(disposable, configuration);
102 Project project = jetCoreEnvironment.getProject();
103 trace = new BindingTraceContext();
104 module = AnalyzerFacadeForJVM.createJavaModule("<repl>");
105 TopDownAnalysisParameters topDownAnalysisParameters = TopDownAnalysisParameters.createForLocalDeclarations(
106 new LockBasedStorageManager(),
107 new ExceptionTracker(), // dummy
108 Predicates.<PsiFile>alwaysTrue()
109 );
110 injector = new InjectorForTopDownAnalyzerForJvm(project, topDownAnalysisParameters, trace, module);
111 topDownAnalysisContext = new TopDownAnalysisContext(topDownAnalysisParameters);
112 module.addFragmentProvider(SOURCES, injector.getTopDownAnalyzer().getPackageFragmentProvider());
113 module.addFragmentProvider(BUILT_INS, KotlinBuiltIns.getInstance().getBuiltInsModule().getPackageFragmentProvider());
114 module.addFragmentProvider(BINARIES, injector.getJavaDescriptorResolver().getPackageFragmentProvider());
115
116 List<URL> classpath = Lists.newArrayList();
117
118 for (File file : configuration.getList(JVMConfigurationKeys.CLASSPATH_KEY)) {
119 try {
120 classpath.add(file.toURI().toURL());
121 }
122 catch (MalformedURLException e) {
123 throw UtilsPackage.rethrow(e);
124 }
125 }
126
127 classLoader = new ReplClassLoader(new URLClassLoader(classpath.toArray(new URL[0])));
128 }
129
130 private static void prepareForTheNextReplLine(@NotNull TopDownAnalysisContext c) {
131 c.getScripts().clear();
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 + JetParserDefinition.STD_SCRIPT_EXT, 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 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 module, bindingContext, Collections.singletonList(psiFile)
252 );
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().getDiagnostics(), 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 }