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 }