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    }