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