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