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    
017    package org.jetbrains.jet.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.jet.config.CompilerConfiguration;
025    import org.jetbrains.jet.utils.ExceptionUtils;
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                    } 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    }