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.descriptors.serialization.descriptors.MemberFilter;
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.parsing.JetParserDefinition;
050 import org.jetbrains.jet.lang.psi.JetFile;
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.KotlinBuiltIns;
060 import org.jetbrains.jet.plugin.JetLanguage;
061 import org.jetbrains.jet.storage.ExceptionTracker;
062 import org.jetbrains.jet.storage.LockBasedStorageManager;
063 import org.jetbrains.jet.utils.UtilsPackage;
064 import org.jetbrains.org.objectweb.asm.Type;
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.ArrayList;
074 import java.util.Collections;
075 import java.util.List;
076
077 import static org.jetbrains.jet.codegen.AsmUtil.asmTypeByFqNameWithoutInnerClasses;
078 import static org.jetbrains.jet.codegen.binding.CodegenBinding.registerClassNameForScript;
079 import static org.jetbrains.jet.lang.descriptors.DependencyKind.*;
080
081 public class ReplInterpreter {
082
083 private int lineNumber = 0;
084 @Nullable
085 private JetScope lastLineScope;
086 private final List<EarlierLine> earlierLines = Lists.newArrayList();
087 private final List<String> previousIncompleteLines = Lists.newArrayList();
088 private final ReplClassLoader classLoader;
089
090 @NotNull
091 private final InjectorForTopDownAnalyzerForJvm injector;
092 @NotNull
093 private final TopDownAnalysisContext topDownAnalysisContext;
094 @NotNull
095 private final JetCoreEnvironment jetCoreEnvironment;
096 @NotNull
097 private final BindingTraceContext trace;
098 @NotNull
099 private final ModuleDescriptorImpl module;
100
101 public ReplInterpreter(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) {
102 jetCoreEnvironment = JetCoreEnvironment.createForProduction(disposable, configuration);
103 Project project = jetCoreEnvironment.getProject();
104 trace = new BindingTraceContext();
105 module = AnalyzerFacadeForJVM.createJavaModule("<repl>");
106 TopDownAnalysisParameters topDownAnalysisParameters = TopDownAnalysisParameters.createForLocalDeclarations(
107 new LockBasedStorageManager(),
108 new ExceptionTracker(), // dummy
109 Predicates.<PsiFile>alwaysTrue()
110 );
111 injector = new InjectorForTopDownAnalyzerForJvm(project, topDownAnalysisParameters, trace, module, MemberFilter.ALWAYS_TRUE);
112 topDownAnalysisContext = new TopDownAnalysisContext(topDownAnalysisParameters);
113 module.addFragmentProvider(SOURCES, injector.getTopDownAnalyzer().getPackageFragmentProvider());
114 module.addFragmentProvider(BUILT_INS, KotlinBuiltIns.getInstance().getBuiltInsModule().getPackageFragmentProvider());
115 module.addFragmentProvider(BINARIES, injector.getJavaDescriptorResolver().getPackageFragmentProvider());
116
117 List<URL> classpath = Lists.newArrayList();
118
119 for (File file : configuration.getList(JVMConfigurationKeys.CLASSPATH_KEY)) {
120 try {
121 classpath.add(file.toURI().toURL());
122 }
123 catch (MalformedURLException e) {
124 throw UtilsPackage.rethrow(e);
125 }
126 }
127
128 classLoader = new ReplClassLoader(new URLClassLoader(classpath.toArray(new URL[0])));
129 }
130
131 private static void prepareForTheNextReplLine(@NotNull TopDownAnalysisContext c) {
132 c.getScripts().clear();
133 }
134
135 public enum LineResultType {
136 SUCCESS,
137 ERROR,
138 INCOMPLETE,
139 }
140
141 public static class LineResult {
142
143 private final Object value;
144 private final boolean unit;
145 private final String errorText;
146 @NotNull
147 private final LineResultType type;
148
149 private LineResult(Object value, boolean unit, String errorText, @NotNull LineResultType type) {
150 this.value = value;
151 this.unit = unit;
152 this.errorText = errorText;
153 this.type = type;
154 }
155
156 @NotNull
157 public LineResultType getType() {
158 return type;
159 }
160
161 private void checkSuccessful() {
162 if (!(getType() == LineResultType.SUCCESS)) {
163 throw new IllegalStateException("it is error");
164 }
165 }
166
167 public Object getValue() {
168 checkSuccessful();
169 return value;
170 }
171
172 public boolean isUnit() {
173 checkSuccessful();
174 return unit;
175 }
176
177 @NotNull
178 public String getErrorText() {
179 return errorText;
180 }
181
182 public static LineResult successful(Object value, boolean unit) {
183 return new LineResult(value, unit, null, LineResultType.SUCCESS);
184 }
185
186 public static LineResult error(@NotNull String errorText) {
187 if (errorText.isEmpty()) {
188 errorText = "<unknown error>";
189 }
190 else if (!errorText.endsWith("\n")) {
191 errorText = errorText + "\n";
192 }
193 return new LineResult(null, false, errorText, LineResultType.ERROR);
194 }
195
196 public static LineResult incomplete() {
197 return new LineResult(null, false, null, LineResultType.INCOMPLETE);
198 }
199 }
200
201 @NotNull
202 public LineResult eval(@NotNull String line) {
203 ++lineNumber;
204
205 FqName scriptFqName = new FqName("Line" + lineNumber);
206 Type scriptClassType = asmTypeByFqNameWithoutInnerClasses(scriptFqName);
207
208 StringBuilder fullText = new StringBuilder();
209 for (String prevLine : previousIncompleteLines) {
210 fullText.append(prevLine + "\n");
211 }
212 fullText.append(line);
213
214 LightVirtualFile virtualFile = new LightVirtualFile("line" + lineNumber + JetParserDefinition.STD_SCRIPT_EXT, JetLanguage.INSTANCE, fullText.toString());
215 virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET);
216 JetFile psiFile = (JetFile) ((PsiFileFactoryImpl) PsiFileFactory.getInstance(jetCoreEnvironment.getProject())).trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false);
217
218 MessageCollectorToString errorCollector = new MessageCollectorToString();
219
220 AnalyzerWithCompilerReport.SyntaxErrorReport syntaxErrorReport =
221 AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorCollector);
222
223 if (syntaxErrorReport.isOnlyErrorAtEof()) {
224 previousIncompleteLines.add(line);
225 return LineResult.incomplete();
226 }
227
228 previousIncompleteLines.clear();
229
230 if (syntaxErrorReport.isHasErrors()) {
231 return LineResult.error(errorCollector.getString());
232 }
233
234 prepareForTheNextReplLine(topDownAnalysisContext);
235 trace.clearDiagnostics();
236
237 psiFile.getScript().putUserData(ScriptHeaderResolver.PRIORITY_KEY, lineNumber);
238
239 ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorCollector);
240 if (scriptDescriptor == null) {
241 return LineResult.error(errorCollector.getString());
242 }
243
244 List<Pair<ScriptDescriptor, Type>> earlierScripts = Lists.newArrayList();
245
246 for (EarlierLine earlierLine : earlierLines) {
247 earlierScripts.add(Pair.create(earlierLine.getScriptDescriptor(), earlierLine.getClassType()));
248 }
249
250 BindingContext bindingContext = AnalyzeExhaust.success(trace.getBindingContext(), module).getBindingContext();
251 GenerationState generationState = new GenerationState(psiFile.getProject(), ClassBuilderFactories.BINARIES,
252 bindingContext, Collections.singletonList(psiFile));
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(), 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 }