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 jline.console.ConsoleReader; 022 import jline.console.history.FileHistory; 023 import org.jetbrains.annotations.NotNull; 024 import org.jetbrains.kotlin.config.CompilerConfiguration; 025 import org.jetbrains.kotlin.utils.UtilsPackage; 026 027 import java.io.File; 028 import java.io.PrintWriter; 029 import java.util.Arrays; 030 import java.util.List; 031 032 public class ReplFromTerminal { 033 034 private ReplInterpreter replInterpreter; 035 private Throwable replInitializationFailed; 036 private final Object waitRepl = new Object(); 037 038 private final ConsoleReader consoleReader; 039 040 public ReplFromTerminal( 041 @NotNull final Disposable disposable, 042 @NotNull final CompilerConfiguration compilerConfiguration) { 043 new Thread("initialize-repl") { 044 @Override 045 public void run() { 046 try { 047 replInterpreter = new ReplInterpreter(disposable, compilerConfiguration); 048 } 049 catch (Throwable e) { 050 replInitializationFailed = e; 051 } 052 synchronized (waitRepl) { 053 waitRepl.notifyAll(); 054 } 055 } 056 }.start(); 057 058 try { 059 consoleReader = new ConsoleReader("kotlin", System.in, System.out, null); 060 consoleReader.setHistoryEnabled(true); 061 consoleReader.setExpandEvents(false); 062 consoleReader.setHistory(new FileHistory(new File(new File(System.getProperty("user.home")), ".kotlin_history"))); 063 } 064 catch (Exception e) { 065 throw UtilsPackage.rethrow(e); 066 } 067 } 068 069 private ReplInterpreter getReplInterpreter() { 070 if (replInterpreter != null) { 071 return replInterpreter; 072 } 073 synchronized (waitRepl) { 074 while (replInterpreter == null && replInitializationFailed == null) { 075 try { 076 waitRepl.wait(); 077 } 078 catch (Throwable e) { 079 throw UtilsPackage.rethrow(e); 080 } 081 } 082 if (replInterpreter != null) { 083 return replInterpreter; 084 } 085 throw UtilsPackage.rethrow(replInitializationFailed); 086 } 087 } 088 089 private void doRun() { 090 try { 091 System.out.println("Kotlin interactive shell"); 092 System.out.println("Type :help for help, :quit for quit"); 093 WhatNextAfterOneLine next = WhatNextAfterOneLine.READ_LINE; 094 while (true) { 095 next = one(next); 096 if (next == WhatNextAfterOneLine.QUIT) { 097 break; 098 } 099 } 100 } 101 catch (Exception e) { 102 throw UtilsPackage.rethrow(e); 103 } 104 finally { 105 try { 106 ((FileHistory) consoleReader.getHistory()).flush(); 107 } 108 catch (Exception e) { 109 System.err.println("failed to flush history: " + e); 110 } 111 } 112 } 113 114 private enum WhatNextAfterOneLine { 115 READ_LINE, 116 INCOMPLETE, 117 QUIT, 118 } 119 120 @NotNull 121 private WhatNextAfterOneLine one(@NotNull WhatNextAfterOneLine next) { 122 try { 123 String line = consoleReader.readLine(next == WhatNextAfterOneLine.INCOMPLETE ? "... " : ">>> "); 124 if (line == null) { 125 return WhatNextAfterOneLine.QUIT; 126 } 127 128 if (line.startsWith(":") && (line.length() == 1 || line.charAt(1) != ':')) { 129 boolean notQuit = oneCommand(line.substring(1)); 130 return notQuit ? WhatNextAfterOneLine.READ_LINE : WhatNextAfterOneLine.QUIT; 131 } 132 133 ReplInterpreter.LineResultType lineResultType = eval(line); 134 if (lineResultType == ReplInterpreter.LineResultType.INCOMPLETE) { 135 return WhatNextAfterOneLine.INCOMPLETE; 136 } 137 else { 138 return WhatNextAfterOneLine.READ_LINE; 139 } 140 } 141 catch (Exception e) { 142 throw UtilsPackage.rethrow(e); 143 } 144 } 145 146 @NotNull 147 private ReplInterpreter.LineResultType eval(@NotNull String line) { 148 ReplInterpreter.LineResult lineResult = getReplInterpreter().eval(line); 149 if (lineResult.getType() == ReplInterpreter.LineResultType.SUCCESS) { 150 if (!lineResult.isUnit()) { 151 System.out.println(lineResult.getValue()); 152 } 153 } 154 else if (lineResult.getType() == ReplInterpreter.LineResultType.INCOMPLETE) { 155 } 156 else if (lineResult.getType() == ReplInterpreter.LineResultType.ERROR) { 157 System.out.print(lineResult.getErrorText()); 158 } 159 else { 160 throw new IllegalStateException("unknown line result type: " + lineResult); 161 } 162 return lineResult.getType(); 163 } 164 165 private boolean oneCommand(@NotNull String command) throws Exception { 166 List<String> split = splitCommand(command); 167 if (split.size() >= 1 && command.equals("help")) { 168 System.out.println("This is Kotlin REPL help"); 169 System.out.println("Available commands are:"); 170 System.out.println(":help show this help"); 171 System.out.println(":quit exit the interpreter"); 172 System.out.println(":dump bytecode dump classes to terminal"); 173 System.out.println(":load <file> load script from specified file"); 174 return true; 175 } 176 else if (split.size() >= 2 && split.get(0).equals("dump") && split.get(1).equals("bytecode")) { 177 getReplInterpreter().dumpClasses(new PrintWriter(System.out)); 178 return true; 179 } 180 else if (split.size() >= 1 && split.get(0).equals("quit")) { 181 return false; 182 } 183 else if (split.size() >= 2 && split.get(0).equals("load")) { 184 String fileName = split.get(1); 185 String scriptText = FileUtil.loadFile(new File(fileName)); 186 eval(scriptText); 187 return true; 188 } 189 else { 190 System.out.println("Unknown command"); 191 System.out.println("Type :help for help"); 192 return true; 193 } 194 } 195 196 private static List<String> splitCommand(@NotNull String command) { 197 return Arrays.asList(command.split(" ")); 198 } 199 200 public static void run(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) { 201 new ReplFromTerminal(disposable, configuration).doRun(); 202 } 203 204 }