001 /* 002 * Copyright 2010-2015 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.kotlin.cli.jvm.repl; 018 019 import com.intellij.openapi.Disposable; 020 import com.intellij.openapi.util.io.FileUtil; 021 import org.jetbrains.annotations.NotNull; 022 import org.jetbrains.kotlin.cli.common.KotlinVersion; 023 import org.jetbrains.kotlin.cli.jvm.repl.messages.*; 024 import org.jetbrains.kotlin.cli.jvm.repl.reader.ConsoleReplCommandReader; 025 import org.jetbrains.kotlin.cli.jvm.repl.reader.IdeReplCommandReader; 026 import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommandReader; 027 import org.jetbrains.kotlin.config.CompilerConfiguration; 028 import org.jetbrains.kotlin.utils.ExceptionUtilsKt; 029 030 import java.io.File; 031 import java.io.PrintWriter; 032 import java.util.Arrays; 033 import java.util.List; 034 035 public class ReplFromTerminal { 036 037 private ReplInterpreter replInterpreter; 038 private Throwable replInitializationFailed; 039 private final Object waitRepl = new Object(); 040 041 private final boolean ideMode; 042 private ReplSystemInWrapper replReader; 043 private final ReplWriter replWriter; 044 private final ReplErrorLogger replErrorLogger; 045 046 private ReplCommandReader commandReader; 047 048 public ReplFromTerminal( 049 @NotNull final Disposable disposable, 050 @NotNull final CompilerConfiguration compilerConfiguration 051 ) { 052 String replIdeMode = System.getProperty("kotlin.repl.ideMode"); 053 ideMode = replIdeMode != null && replIdeMode.equals("true"); 054 055 // wrapper for `out` is required to escape every input in [ideMode]; 056 // if [ideMode == false] then just redirects all input to [System.out] 057 // if user calls [System.setOut(...)] then undefined behaviour 058 if (ideMode) { 059 ReplSystemOutWrapperForIde soutWrapper = new ReplSystemOutWrapperForIde(System.out); 060 replWriter = soutWrapper; 061 System.setOut(soutWrapper); 062 } 063 else { 064 replWriter = new ReplConsoleWriter(); 065 } 066 067 // wrapper for `in` is required to give user possibility of calling 068 // [readLine] from ide-console repl 069 if (ideMode) { 070 replReader = new ReplSystemInWrapper(System.in, replWriter); 071 System.setIn(replReader); 072 } 073 074 replErrorLogger = new ReplErrorLogger(ideMode, replWriter); 075 076 new Thread("initialize-repl") { 077 @Override 078 public void run() { 079 try { 080 replInterpreter = new ReplInterpreter(disposable, compilerConfiguration, ideMode, replReader); 081 } 082 catch (Throwable e) { 083 replInitializationFailed = e; 084 } 085 synchronized (waitRepl) { 086 waitRepl.notifyAll(); 087 } 088 } 089 }.start(); 090 091 try { 092 commandReader = createCommandReader(); 093 } 094 catch (Exception e) { 095 replErrorLogger.logException(e); 096 } 097 } 098 099 @NotNull 100 private ReplCommandReader createCommandReader() { 101 return ideMode ? new IdeReplCommandReader() 102 : new ConsoleReplCommandReader(); 103 } 104 105 private ReplInterpreter getReplInterpreter() { 106 if (replInterpreter != null) { 107 return replInterpreter; 108 } 109 synchronized (waitRepl) { 110 while (replInterpreter == null && replInitializationFailed == null) { 111 try { 112 waitRepl.wait(); 113 } 114 catch (Throwable e) { 115 throw ExceptionUtilsKt.rethrow(e); 116 } 117 } 118 if (replInterpreter != null) { 119 return replInterpreter; 120 } 121 throw ExceptionUtilsKt.rethrow(replInitializationFailed); 122 } 123 } 124 125 private void doRun() { 126 try { 127 replWriter.printlnWelcomeMessage("Welcome to Kotlin version " + KotlinVersion.VERSION + 128 " (JRE " + System.getProperty("java.runtime.version") + ")"); 129 replWriter.printlnWelcomeMessage("Type :help for help, :quit for quit"); 130 WhatNextAfterOneLine next = WhatNextAfterOneLine.READ_LINE; 131 while (true) { 132 next = one(next); 133 if (next == WhatNextAfterOneLine.QUIT) { 134 break; 135 } 136 } 137 } 138 catch (Exception e) { 139 replErrorLogger.logException(e); 140 } 141 finally { 142 try { 143 commandReader.flushHistory(); 144 } 145 catch (Exception e) { 146 replErrorLogger.logException(e); 147 } 148 } 149 } 150 151 public enum WhatNextAfterOneLine { 152 READ_LINE, 153 INCOMPLETE, 154 QUIT, 155 } 156 157 @NotNull 158 private WhatNextAfterOneLine one(@NotNull WhatNextAfterOneLine next) { 159 try { 160 String line = commandReader.readLine(next); 161 162 if (line == null) { 163 return WhatNextAfterOneLine.QUIT; 164 } 165 166 line = UnescapeUtilsKt.unescapeLineBreaks(line); 167 168 if (line.startsWith(":") && (line.length() == 1 || line.charAt(1) != ':')) { 169 boolean notQuit = oneCommand(line.substring(1)); 170 return notQuit ? WhatNextAfterOneLine.READ_LINE : WhatNextAfterOneLine.QUIT; 171 } 172 173 ReplInterpreter.LineResultType lineResultType = eval(line); 174 if (lineResultType == ReplInterpreter.LineResultType.INCOMPLETE) { 175 return WhatNextAfterOneLine.INCOMPLETE; 176 } 177 else { 178 return WhatNextAfterOneLine.READ_LINE; 179 } 180 } 181 catch (Exception e) { 182 throw ExceptionUtilsKt.rethrow(e); 183 } 184 } 185 186 @NotNull 187 private ReplInterpreter.LineResultType eval(@NotNull String line) { 188 ReplInterpreter.LineResult lineResult = getReplInterpreter().eval(line); 189 if (lineResult.getType() == ReplInterpreter.LineResultType.SUCCESS) { 190 replWriter.notifyCommandSuccess(); 191 if (!lineResult.isUnit()) { 192 replWriter.outputCommandResult(lineResult.getValue()); 193 } 194 } 195 else if (lineResult.getType() == ReplInterpreter.LineResultType.INCOMPLETE) { 196 replWriter.notifyIncomplete(); 197 } 198 else if (lineResult.getType() == ReplInterpreter.LineResultType.COMPILE_ERROR) { 199 replWriter.outputCompileError(lineResult.getErrorText()); 200 } 201 else if (lineResult.getType() == ReplInterpreter.LineResultType.RUNTIME_ERROR) { 202 replWriter.outputRuntimeError(lineResult.getErrorText()); 203 } 204 else { 205 throw new IllegalStateException("unknown line result type: " + lineResult); 206 } 207 return lineResult.getType(); 208 } 209 210 private boolean oneCommand(@NotNull String command) throws Exception { 211 List<String> split = splitCommand(command); 212 if (split.size() >= 1 && command.equals("help")) { 213 replWriter.printlnHelpMessage("Available commands:\n" + 214 ":help show this help\n" + 215 ":quit exit the interpreter\n" + 216 ":dump bytecode dump classes to terminal\n" + 217 ":load <file> load script from specified file" 218 ); 219 return true; 220 } 221 else if (split.size() >= 2 && split.get(0).equals("dump") && split.get(1).equals("bytecode")) { 222 getReplInterpreter().dumpClasses(new PrintWriter(System.out)); 223 return true; 224 } 225 else if (split.size() >= 1 && split.get(0).equals("quit")) { 226 return false; 227 } 228 else if (split.size() >= 2 && split.get(0).equals("load")) { 229 String fileName = split.get(1); 230 String scriptText = FileUtil.loadFile(new File(fileName)); 231 eval(scriptText); 232 return true; 233 } 234 else { 235 replWriter.printlnHelpMessage("Unknown command\n" + 236 "Type :help for help" 237 ); 238 return true; 239 } 240 } 241 242 private static List<String> splitCommand(@NotNull String command) { 243 return Arrays.asList(command.split(" ")); 244 } 245 246 public static void run(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) { 247 new ReplFromTerminal(disposable, configuration).doRun(); 248 } 249 250 }