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.NamespaceDescriptorImpl; 049 import org.jetbrains.jet.lang.descriptors.impl.NamespaceLikeBuilderDummy; 050 import org.jetbrains.jet.lang.psi.JetFile; 051 import org.jetbrains.jet.lang.psi.JetPsiUtil; 052 import org.jetbrains.jet.lang.psi.JetScript; 053 import org.jetbrains.jet.lang.resolve.*; 054 import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM; 055 import org.jetbrains.jet.lang.resolve.java.JvmClassName; 056 import org.jetbrains.jet.lang.resolve.name.FqName; 057 import org.jetbrains.jet.lang.resolve.scopes.JetScope; 058 import org.jetbrains.jet.lang.resolve.scopes.WritableScope; 059 import org.jetbrains.jet.lang.resolve.scopes.WritableScopeImpl; 060 import org.jetbrains.jet.lang.types.lang.InlineUtil; 061 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; 062 import org.jetbrains.jet.plugin.JetLanguage; 063 import org.jetbrains.jet.utils.ExceptionUtils; 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.Collections; 073 import java.util.List; 074 075 import static org.jetbrains.jet.codegen.AsmUtil.asmTypeByFqNameWithoutInnerClasses; 076 import static org.jetbrains.jet.codegen.binding.CodegenBinding.registerClassNameForScript; 077 078 public class ReplInterpreter { 079 080 private int lineNumber = 0; 081 @Nullable 082 private JetScope lastLineScope; 083 private List<EarlierLine> earlierLines = Lists.newArrayList(); 084 private List<String> previousIncompleteLines = Lists.newArrayList(); 085 private final ReplClassLoader classLoader; 086 087 @NotNull 088 private final InjectorForTopDownAnalyzerForJvm injector; 089 @NotNull 090 private final JetCoreEnvironment jetCoreEnvironment; 091 @NotNull 092 private final BindingTraceContext trace; 093 @NotNull 094 private final ModuleDescriptorImpl module; 095 096 public ReplInterpreter(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) { 097 jetCoreEnvironment = JetCoreEnvironment.createForProduction(disposable, configuration); 098 Project project = jetCoreEnvironment.getProject(); 099 trace = new BindingTraceContext(); 100 module = AnalyzerFacadeForJVM.createJavaModule("<repl>"); 101 TopDownAnalysisParameters topDownAnalysisParameters = new TopDownAnalysisParameters( 102 Predicates.<PsiFile>alwaysTrue(), 103 false, 104 true, 105 Collections.<AnalyzerScriptParameter>emptyList()); 106 injector = new InjectorForTopDownAnalyzerForJvm(project, topDownAnalysisParameters, trace, module); 107 module.setModuleConfiguration(injector.getJavaBridgeConfiguration()); 108 109 List<URL> classpath = Lists.newArrayList(); 110 111 for (File file : configuration.getList(JVMConfigurationKeys.CLASSPATH_KEY)) { 112 try { 113 classpath.add(file.toURI().toURL()); 114 } 115 catch (MalformedURLException e) { 116 throw ExceptionUtils.rethrow(e); 117 } 118 } 119 120 classLoader = new ReplClassLoader(new URLClassLoader(classpath.toArray(new URL[0]))); 121 } 122 123 public enum LineResultType { 124 SUCCESS, 125 ERROR, 126 INCOMPLETE, 127 } 128 129 public static class LineResult { 130 131 private final Object value; 132 private final boolean unit; 133 private final String errorText; 134 @NotNull 135 private final LineResultType type; 136 137 private LineResult(Object value, boolean unit, String errorText, @NotNull LineResultType type) { 138 this.value = value; 139 this.unit = unit; 140 this.errorText = errorText; 141 this.type = type; 142 } 143 144 @NotNull 145 public LineResultType getType() { 146 return type; 147 } 148 149 private void checkSuccessful() { 150 if (!(getType() == LineResultType.SUCCESS)) { 151 throw new IllegalStateException("it is error"); 152 } 153 } 154 155 public Object getValue() { 156 checkSuccessful(); 157 return value; 158 } 159 160 public boolean isUnit() { 161 checkSuccessful(); 162 return unit; 163 } 164 165 @NotNull 166 public String getErrorText() { 167 return errorText; 168 } 169 170 public static LineResult successful(Object value, boolean unit) { 171 return new LineResult(value, unit, null, LineResultType.SUCCESS); 172 } 173 174 public static LineResult error(@NotNull String errorText) { 175 if (errorText.isEmpty()) { 176 errorText = "<unknown error>"; 177 } 178 else if (!errorText.endsWith("\n")) { 179 errorText = errorText + "\n"; 180 } 181 return new LineResult(null, false, errorText, LineResultType.ERROR); 182 } 183 184 public static LineResult incomplete() { 185 return new LineResult(null, false, null, LineResultType.INCOMPLETE); 186 } 187 } 188 189 @NotNull 190 public LineResult eval(@NotNull String line) { 191 ++lineNumber; 192 193 FqName scriptFqName = new FqName("Line" + lineNumber); 194 Type scriptClassType = asmTypeByFqNameWithoutInnerClasses(scriptFqName); 195 196 StringBuilder fullText = new StringBuilder(); 197 for (String prevLine : previousIncompleteLines) { 198 fullText.append(prevLine + "\n"); 199 } 200 fullText.append(line); 201 202 LightVirtualFile virtualFile = new LightVirtualFile("line" + lineNumber + ".ktscript", JetLanguage.INSTANCE, fullText.toString()); 203 virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET); 204 JetFile psiFile = (JetFile) ((PsiFileFactoryImpl) PsiFileFactory.getInstance(jetCoreEnvironment.getProject())).trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false); 205 206 MessageCollectorToString errorCollector = new MessageCollectorToString(); 207 208 AnalyzerWithCompilerReport.SyntaxErrorReport syntaxErrorReport = 209 AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorCollector); 210 211 if (syntaxErrorReport.isOnlyErrorAtEof()) { 212 previousIncompleteLines.add(line); 213 return LineResult.incomplete(); 214 } 215 216 previousIncompleteLines.clear(); 217 218 if (syntaxErrorReport.isHasErrors()) { 219 return LineResult.error(errorCollector.getString()); 220 } 221 222 injector.getTopDownAnalyzer().prepareForTheNextReplLine(); 223 trace.clearDiagnostics(); 224 225 psiFile.getScript().putUserData(ScriptHeaderResolver.PRIORITY_KEY, lineNumber); 226 227 ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorCollector); 228 if (scriptDescriptor == null) { 229 return LineResult.error(errorCollector.getString()); 230 } 231 232 List<Pair<ScriptDescriptor, Type>> earlierScripts = Lists.newArrayList(); 233 234 for (EarlierLine earlierLine : earlierLines) { 235 earlierScripts.add(Pair.create(earlierLine.getScriptDescriptor(), earlierLine.getClassType())); 236 } 237 238 BindingContext bindingContext = AnalyzeExhaust.success(trace.getBindingContext(), module).getBindingContext(); 239 GenerationState generationState = new GenerationState(psiFile.getProject(), ClassBuilderFactories.BINARIES, 240 bindingContext, Collections.singletonList(psiFile), InlineUtil.DEFAULT_INLINE_FLAG); 241 242 compileScript(psiFile.getScript(), scriptClassType, earlierScripts, generationState, 243 CompilationErrorHandler.THROW_EXCEPTION); 244 245 for (OutputFile outputFile : generationState.getFactory().asList()) { 246 classLoader.addClass(JvmClassName.byInternalName(outputFile.getRelativePath().replaceFirst("\\.class$", "")), outputFile.asByteArray()); 247 } 248 249 try { 250 Class<?> scriptClass = classLoader.loadClass(scriptFqName.asString()); 251 252 Class<?>[] constructorParams = new Class<?>[earlierLines.size()]; 253 Object[] constructorArgs = new Object[earlierLines.size()]; 254 255 for (int i = 0; i < earlierLines.size(); ++i) { 256 constructorParams[i] = earlierLines.get(i).getScriptClass(); 257 constructorArgs[i] = earlierLines.get(i).getScriptInstance(); 258 } 259 260 Constructor<?> scriptInstanceConstructor = scriptClass.getConstructor(constructorParams); 261 Object scriptInstance; 262 try { 263 scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs); 264 } catch (Throwable e) { 265 return LineResult.error(Throwables.getStackTraceAsString(e)); 266 } 267 Field rvField = scriptClass.getDeclaredField("rv"); 268 rvField.setAccessible(true); 269 Object rv = rvField.get(scriptInstance); 270 271 earlierLines.add(new EarlierLine(line, scriptDescriptor, scriptClass, scriptInstance, scriptClassType)); 272 273 return LineResult.successful(rv, scriptDescriptor.getReturnType().equals(KotlinBuiltIns.getInstance().getUnitType())); 274 } catch (Throwable e) { 275 PrintWriter writer = new PrintWriter(System.err); 276 classLoader.dumpClasses(writer); 277 writer.flush(); 278 throw ExceptionUtils.rethrow(e); 279 } 280 } 281 282 @Nullable 283 private ScriptDescriptor doAnalyze(@NotNull JetFile psiFile, @NotNull MessageCollector messageCollector) { 284 WritableScope scope = new WritableScopeImpl( 285 JetScope.EMPTY, module, 286 new TraceBasedRedeclarationHandler(trace), "Root scope in analyzeNamespace"); 287 288 scope.changeLockLevel(WritableScope.LockLevel.BOTH); 289 290 NamespaceDescriptorImpl rootNs = injector.getNamespaceFactory().createNamespaceDescriptorPathIfNeeded(FqName.ROOT); 291 292 // map "jet" namespace into KotlinBuiltIns 293 // @see DefaultModuleConfiguraiton#extendNamespaceScope 294 injector.getNamespaceFactory().createNamespaceDescriptorPathIfNeeded(KotlinBuiltIns.getInstance().getBuiltInsPackageFqName()); 295 296 // Import a scope that contains all top-level namespaces that come from dependencies 297 // This makes the namespaces visible at all, does not import themselves 298 scope.importScope(rootNs.getMemberScope()); 299 300 if (lastLineScope != null) { 301 scope.importScope(lastLineScope); 302 } 303 304 scope.changeLockLevel(WritableScope.LockLevel.READING); 305 306 // dummy builder is used because "root" is module descriptor, 307 // namespaces added to module explicitly in 308 injector.getTopDownAnalyzer().doProcess(scope, new NamespaceLikeBuilderDummy(), Collections.singletonList(psiFile)); 309 310 boolean hasErrors = AnalyzerWithCompilerReport.reportDiagnostics(trace.getBindingContext(), messageCollector); 311 if (hasErrors) { 312 return null; 313 } 314 315 ScriptDescriptor scriptDescriptor = injector.getTopDownAnalysisContext().getScripts().get(psiFile.getScript()); 316 lastLineScope = trace.get(BindingContext.SCRIPT_SCOPE, scriptDescriptor); 317 if (lastLineScope == null) { 318 throw new IllegalStateException("last line scope is not initialized"); 319 } 320 321 return scriptDescriptor; 322 } 323 324 public void dumpClasses(@NotNull PrintWriter out) { 325 classLoader.dumpClasses(out); 326 } 327 328 private static void registerEarlierScripts( 329 @NotNull GenerationState state, 330 @NotNull List<Pair<ScriptDescriptor, Type>> earlierScripts 331 ) { 332 for (Pair<ScriptDescriptor, Type> t : earlierScripts) { 333 ScriptDescriptor earlierDescriptor = t.first; 334 Type earlierClassType = t.second; 335 registerClassNameForScript(state.getBindingTrace(), earlierDescriptor, earlierClassType); 336 } 337 338 List<ScriptDescriptor> earlierScriptDescriptors = Lists.newArrayList(); 339 for (Pair<ScriptDescriptor, Type> t : earlierScripts) { 340 ScriptDescriptor earlierDescriptor = t.first; 341 earlierScriptDescriptors.add(earlierDescriptor); 342 } 343 state.setEarlierScriptsForReplInterpreter(earlierScriptDescriptors); 344 } 345 346 public static void compileScript( 347 @NotNull JetScript script, 348 @NotNull Type classType, 349 @NotNull List<Pair<ScriptDescriptor, Type>> earlierScripts, 350 @NotNull GenerationState state, 351 @NotNull CompilationErrorHandler errorHandler 352 ) { 353 registerEarlierScripts(state, earlierScripts); 354 registerClassNameForScript(state.getBindingTrace(), script, classType); 355 356 state.beforeCompile(); 357 KotlinCodegenFacade.generateNamespace( 358 state, 359 JetPsiUtil.getFQName((JetFile) script.getContainingFile()), 360 Collections.singleton((JetFile) script.getContainingFile()), 361 errorHandler); 362 } 363 364 }