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