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 017package org.jetbrains.jet.cli.jvm.repl; 018 019import com.google.common.base.Predicates; 020import com.google.common.base.Throwables; 021import com.google.common.collect.Lists; 022import com.intellij.openapi.Disposable; 023import com.intellij.openapi.project.Project; 024import com.intellij.openapi.util.Pair; 025import com.intellij.openapi.vfs.CharsetToolkit; 026import com.intellij.psi.PsiFile; 027import com.intellij.psi.PsiFileFactory; 028import com.intellij.psi.impl.PsiFileFactoryImpl; 029import com.intellij.testFramework.LightVirtualFile; 030import org.jetbrains.annotations.NotNull; 031import org.jetbrains.annotations.Nullable; 032import org.jetbrains.jet.analyzer.AnalyzeExhaust; 033import org.jetbrains.jet.cli.common.messages.AnalyzerWithCompilerReport; 034import org.jetbrains.jet.cli.common.messages.MessageCollector; 035import org.jetbrains.jet.cli.common.messages.MessageCollectorToString; 036import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys; 037import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment; 038import org.jetbrains.jet.codegen.ClassBuilderFactories; 039import org.jetbrains.jet.codegen.CompilationErrorHandler; 040import org.jetbrains.jet.codegen.state.GenerationState; 041import org.jetbrains.jet.config.CompilerConfiguration; 042import org.jetbrains.jet.di.InjectorForTopDownAnalyzerForJvm; 043import org.jetbrains.jet.lang.descriptors.ModuleDescriptorImpl; 044import org.jetbrains.jet.lang.descriptors.ScriptDescriptor; 045import org.jetbrains.jet.lang.descriptors.impl.NamespaceDescriptorImpl; 046import org.jetbrains.jet.lang.descriptors.impl.NamespaceLikeBuilderDummy; 047import org.jetbrains.jet.lang.psi.JetFile; 048import org.jetbrains.jet.lang.resolve.*; 049import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM; 050import org.jetbrains.jet.lang.resolve.java.JvmClassName; 051import org.jetbrains.jet.lang.resolve.name.FqName; 052import org.jetbrains.jet.lang.resolve.scopes.JetScope; 053import org.jetbrains.jet.lang.resolve.scopes.WritableScope; 054import org.jetbrains.jet.lang.resolve.scopes.WritableScopeImpl; 055import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; 056import org.jetbrains.jet.plugin.JetLanguage; 057import org.jetbrains.jet.utils.ExceptionUtils; 058 059import java.io.File; 060import java.io.PrintWriter; 061import java.lang.reflect.Constructor; 062import java.lang.reflect.Field; 063import java.net.MalformedURLException; 064import java.net.URL; 065import java.net.URLClassLoader; 066import java.util.Collections; 067import java.util.List; 068 069public class ReplInterpreter { 070 071 private int lineNumber = 0; 072 @Nullable 073 private JetScope lastLineScope; 074 private List<EarlierLine> earlierLines = Lists.newArrayList(); 075 private List<String> previousIncompleteLines = Lists.newArrayList(); 076 private final ReplClassLoader classLoader; 077 078 @NotNull 079 private final InjectorForTopDownAnalyzerForJvm injector; 080 @NotNull 081 private final JetCoreEnvironment jetCoreEnvironment; 082 @NotNull 083 private final BindingTraceContext trace; 084 @NotNull 085 private final ModuleDescriptorImpl module; 086 087 public ReplInterpreter(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) { 088 jetCoreEnvironment = new JetCoreEnvironment(disposable, configuration); 089 Project project = jetCoreEnvironment.getProject(); 090 trace = new BindingTraceContext(); 091 module = AnalyzerFacadeForJVM.createJavaModule("<repl>"); 092 TopDownAnalysisParameters topDownAnalysisParameters = new TopDownAnalysisParameters( 093 Predicates.<PsiFile>alwaysTrue(), 094 false, 095 true, 096 Collections.<AnalyzerScriptParameter>emptyList()); 097 injector = new InjectorForTopDownAnalyzerForJvm(project, topDownAnalysisParameters, trace, module); 098 module.setModuleConfiguration(injector.getJavaBridgeConfiguration()); 099 100 List<URL> classpath = Lists.newArrayList(); 101 102 for (File file : configuration.getList(JVMConfigurationKeys.CLASSPATH_KEY)) { 103 try { 104 classpath.add(file.toURI().toURL()); 105 } 106 catch (MalformedURLException e) { 107 throw ExceptionUtils.rethrow(e); 108 } 109 } 110 111 classLoader = new ReplClassLoader(new URLClassLoader(classpath.toArray(new URL[0]))); 112 } 113 114 public enum LineResultType { 115 SUCCESS, 116 ERROR, 117 INCOMPLETE, 118 } 119 120 public static class LineResult { 121 122 private final Object value; 123 private final boolean unit; 124 private final String errorText; 125 @NotNull 126 private final LineResultType type; 127 128 private LineResult(Object value, boolean unit, String errorText, @NotNull LineResultType type) { 129 this.value = value; 130 this.unit = unit; 131 this.errorText = errorText; 132 this.type = type; 133 } 134 135 @NotNull 136 public LineResultType getType() { 137 return type; 138 } 139 140 private void checkSuccessful() { 141 if (!(getType() == LineResultType.SUCCESS)) { 142 throw new IllegalStateException("it is error"); 143 } 144 } 145 146 public Object getValue() { 147 checkSuccessful(); 148 return value; 149 } 150 151 public boolean isUnit() { 152 checkSuccessful(); 153 return unit; 154 } 155 156 @NotNull 157 public String getErrorText() { 158 return errorText; 159 } 160 161 public static LineResult successful(Object value, boolean unit) { 162 return new LineResult(value, unit, null, LineResultType.SUCCESS); 163 } 164 165 public static LineResult error(@NotNull String errorText) { 166 if (errorText.isEmpty()) { 167 errorText = "<unknown error>"; 168 } 169 else if (!errorText.endsWith("\n")) { 170 errorText = errorText + "\n"; 171 } 172 return new LineResult(null, false, errorText, LineResultType.ERROR); 173 } 174 175 public static LineResult incomplete() { 176 return new LineResult(null, false, null, LineResultType.INCOMPLETE); 177 } 178 } 179 180 @NotNull 181 public LineResult eval(@NotNull String line) { 182 ++lineNumber; 183 184 JvmClassName scriptClassName = JvmClassName.byInternalName("Line" + lineNumber); 185 186 StringBuilder fullText = new StringBuilder(); 187 for (String prevLine : previousIncompleteLines) { 188 fullText.append(prevLine + "\n"); 189 } 190 fullText.append(line); 191 192 LightVirtualFile virtualFile = new LightVirtualFile("line" + lineNumber + ".ktscript", JetLanguage.INSTANCE, fullText.toString()); 193 virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET); 194 JetFile psiFile = (JetFile) ((PsiFileFactoryImpl) PsiFileFactory.getInstance(jetCoreEnvironment.getProject())).trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false); 195 196 MessageCollectorToString errorCollector = new MessageCollectorToString(); 197 198 AnalyzerWithCompilerReport.SyntaxErrorReport syntaxErrorReport = 199 AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorCollector); 200 201 if (syntaxErrorReport.isOnlyErrorAtEof()) { 202 previousIncompleteLines.add(line); 203 return LineResult.incomplete(); 204 } 205 206 previousIncompleteLines.clear(); 207 208 if (syntaxErrorReport.isHasErrors()) { 209 return LineResult.error(errorCollector.getString()); 210 } 211 212 injector.getTopDownAnalyzer().prepareForTheNextReplLine(); 213 trace.clearDiagnostics(); 214 215 psiFile.getScript().putUserData(ScriptHeaderResolver.PRIORITY_KEY, lineNumber); 216 217 ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorCollector); 218 if (scriptDescriptor == null) { 219 return LineResult.error(errorCollector.getString()); 220 } 221 222 List<Pair<ScriptDescriptor, JvmClassName>> earierScripts = Lists.newArrayList(); 223 224 for (EarlierLine earlierLine : earlierLines) { 225 earierScripts.add(Pair.create(earlierLine.getScriptDescriptor(), earlierLine.getClassName())); 226 } 227 228 BindingContext bindingContext = AnalyzeExhaust.success(trace.getBindingContext(), module).getBindingContext(); 229 GenerationState generationState = new GenerationState(psiFile.getProject(), ClassBuilderFactories.binaries(false), 230 bindingContext, Collections.singletonList(psiFile)); 231 generationState.getScriptCodegen().compileScript(psiFile.getScript(), scriptClassName, earierScripts, 232 CompilationErrorHandler.THROW_EXCEPTION); 233 234 for (String file : generationState.getFactory().files()) { 235 classLoader.addClass(JvmClassName.byInternalName(file.replaceFirst("\\.class$", "")), generationState.getFactory().asBytes(file)); 236 } 237 238 try { 239 Class<?> scriptClass = classLoader.loadClass(scriptClassName.getFqName().asString()); 240 241 Class<?>[] constructorParams = new Class<?>[earlierLines.size()]; 242 Object[] constructorArgs = new Object[earlierLines.size()]; 243 244 for (int i = 0; i < earlierLines.size(); ++i) { 245 constructorParams[i] = earlierLines.get(i).getScriptClass(); 246 constructorArgs[i] = earlierLines.get(i).getScriptInstance(); 247 } 248 249 Constructor<?> scriptInstanceConstructor = scriptClass.getConstructor(constructorParams); 250 Object scriptInstance; 251 try { 252 scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs); 253 } catch (Throwable e) { 254 return LineResult.error(Throwables.getStackTraceAsString(e)); 255 } 256 Field rvField = scriptClass.getDeclaredField("rv"); 257 rvField.setAccessible(true); 258 Object rv = rvField.get(scriptInstance); 259 260 earlierLines.add(new EarlierLine(line, scriptDescriptor, scriptClass, scriptInstance, scriptClassName)); 261 262 return LineResult.successful(rv, scriptDescriptor.getReturnType().equals(KotlinBuiltIns.getInstance().getUnitType())); 263 } catch (Throwable e) { 264 PrintWriter writer = new PrintWriter(System.err); 265 classLoader.dumpClasses(writer); 266 writer.flush(); 267 throw ExceptionUtils.rethrow(e); 268 } 269 } 270 271 @Nullable 272 private ScriptDescriptor doAnalyze(@NotNull JetFile psiFile, @NotNull MessageCollector messageCollector) { 273 WritableScope scope = new WritableScopeImpl( 274 JetScope.EMPTY, module, 275 new TraceBasedRedeclarationHandler(trace), "Root scope in analyzeNamespace"); 276 277 scope.changeLockLevel(WritableScope.LockLevel.BOTH); 278 279 NamespaceDescriptorImpl rootNs = injector.getNamespaceFactory().createNamespaceDescriptorPathIfNeeded(FqName.ROOT); 280 281 // map "jet" namespace into KotlinBuiltIns 282 // @see DefaultModuleConfiguraiton#extendNamespaceScope 283 injector.getNamespaceFactory().createNamespaceDescriptorPathIfNeeded(KotlinBuiltIns.getInstance().getBuiltInsPackageFqName()); 284 285 // Import a scope that contains all top-level namespaces that come from dependencies 286 // This makes the namespaces visible at all, does not import themselves 287 scope.importScope(rootNs.getMemberScope()); 288 289 if (lastLineScope != null) { 290 scope.importScope(lastLineScope); 291 } 292 293 scope.changeLockLevel(WritableScope.LockLevel.READING); 294 295 // dummy builder is used because "root" is module descriptor, 296 // namespaces added to module explicitly in 297 injector.getTopDownAnalyzer().doProcess(scope, new NamespaceLikeBuilderDummy(), Collections.singletonList(psiFile)); 298 299 boolean hasErrors = AnalyzerWithCompilerReport.reportDiagnostics(trace.getBindingContext(), messageCollector); 300 if (hasErrors) { 301 return null; 302 } 303 304 ScriptDescriptor scriptDescriptor = injector.getTopDownAnalysisContext().getScripts().get(psiFile.getScript()); 305 lastLineScope = trace.get(BindingContext.SCRIPT_SCOPE, scriptDescriptor); 306 if (lastLineScope == null) { 307 throw new IllegalStateException("last line scope is not initialized"); 308 } 309 310 return scriptDescriptor; 311 } 312 313 public void dumpClasses(@NotNull PrintWriter out) { 314 classLoader.dumpClasses(out); 315 } 316}