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.ModuleDescriptorImpl; 046 import org.jetbrains.jet.lang.descriptors.ScriptDescriptor; 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 }