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}