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