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 module, 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 }