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