package org.rascalmpl.library.experiments.Compiler.RVM.Interpreter.repl;
import static org.rascalmpl.interpreter.utils.ReadEvalPrintDialogMessages.parseErrorMessage;
import static org.rascalmpl.interpreter.utils.ReadEvalPrintDialogMessages.staticErrorMessage;
import static org.rascalmpl.interpreter.utils.ReadEvalPrintDialogMessages.throwMessage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.rascalmpl.interpreter.control_exceptions.QuitException;
import org.rascalmpl.interpreter.control_exceptions.Throw;
import org.rascalmpl.interpreter.result.IRascalResult;
import org.rascalmpl.interpreter.staticErrors.StaticError;
import org.rascalmpl.interpreter.utils.Timing;
import org.rascalmpl.library.experiments.Compiler.RVM.Interpreter.NoSuchRascalFunction;
import org.rascalmpl.library.experiments.Compiler.RVM.Interpreter.ideservices.IDEServices;
import org.rascalmpl.library.util.PathConfig;
import org.rascalmpl.parser.gtd.exception.ParseError;
import org.rascalmpl.repl.BaseRascalREPL;
import org.rascalmpl.repl.CompletionResult;
import org.rascalmpl.uri.URIUtil;
import org.rascalmpl.value.IValue;
import org.rascalmpl.value.type.Type;
import jline.Terminal;
public abstract class CompiledRascalREPL extends BaseRascalREPL {
protected CommandExecutor executor;
private boolean measureCommandTime;
// private boolean semiColonAdded = false;
public static final TreeSet<String> SHELL_VERBS;
static {
String[] shellVerbValues = {
// General commands
"apropos", "clean", "declarations", "edit", "help", /*"modules",*/ "quit", "set", "test", "undeclare", "unimport",
// Debugging commands
"break", "clear", "disable", "enable", "ignore"
};
SHELL_VERBS = new TreeSet<String>(Arrays.asList(shellVerbValues));
}
private PathConfig pcfg;
protected final IDEServices ideServices;
public CompiledRascalREPL(PathConfig pcfg, InputStream stdin, OutputStream stdout, boolean prettyPrompt, boolean allowColors, File persistentHistory, Terminal terminal, IDEServices ideServices)
throws IOException, URISyntaxException {
super(pcfg, stdin, stdout, prettyPrompt, allowColors, persistentHistory, terminal, ideServices);
this.pcfg = pcfg;
this.ideServices = ideServices;
}
@Override
protected boolean isREPLCommand(String line){
if(line.length() > 0){
int idx = line.indexOf(" ");
if(idx > 0){
return SHELL_VERBS.contains(line.substring(0, idx));
}
return SHELL_VERBS.contains(line);
}
return false;
}
@Override
protected PrintWriter getErrorWriter() {
return executor.getStdErr();
}
@Override
protected PrintWriter getOutputWriter() {
return executor.getStdOut();
}
public void setMeasureCommandTime(boolean measureCommandTime) {
this.measureCommandTime = measureCommandTime;
}
public boolean getMeasureCommandTime() {
return measureCommandTime;
}
@Override
protected void cancelRunningCommandRequested() {
stop();
}
@Override
protected void terminateRequested() {
stop();
}
@Override
protected void stackTraceRequested() {
// TODO: print current stack trace, without stopping the running code.
// reminder: this method is called from a different thread.
}
@Override
protected void initialize(PathConfig pcfg, Writer stdout, Writer stderr, IDEServices ideServices) throws IOException, URISyntaxException {
try {
executor = constructCommandExecutor(pcfg, new PrintWriter(stdout), new PrintWriter(stderr), ideServices);
} catch (NoSuchRascalFunction e) {
throw new RuntimeException(e);
}
}
protected abstract CommandExecutor constructCommandExecutor(PathConfig pcfg, PrintWriter stdout, PrintWriter stderr, IDEServices ideServices) throws IOException, NoSuchRascalFunction, URISyntaxException;
@Override
protected boolean isStatementComplete(String command) {
return executor.isStatementComplete(command);
}
@Override
protected IRascalResult evalStatement(String statement, String lastLine) throws InterruptedException {
try {
if(executor.semiColonAdded){
statement = statement + ";";
executor.semiColonAdded = false;
}
String[] words = statement.split(" ");
if(words.length > 0 && SHELL_VERBS.contains(words[0])){
if(words[0].equals("quit")){
executor.shutdown();
stop();
return null;
}
executor.evalShellCommand(words);
return null;
} else {
Timing tm = new Timing();
tm.start();
IValue value = executor.eval(statement, URIUtil.rootLocation("prompt"));
if(value != null){
Type tp = value.getType();
if(tp.isAbstractData() && tp.getName().equals("RuntimeException")){
throw new RascalShellExecutionException("Error: " + value.toString());
}
}
long duration = tm.duration();
if (measureCommandTime) {
executor.getStdErr().println("Time: " + (duration / 1000000) + "ms");
}
return new IRascalResult() {
@Override
public IValue getValue() {
return value;
}
@Override
public Type getType() {
return value.getType(); // TODO: change to static type?
}
};
}
}
catch (ParseError pe) {
executor.getStdErr().println(parseErrorMessage(lastLine, "prompt", pe));
return null;
}
catch (StaticError e) {
executor.getStdErr().println(staticErrorMessage(e));
return null;
}
catch (Throw e) {
executor.getStdErr().println(throwMessage(e));
return null;
}
catch (QuitException q) {
executor.getStdErr().println("Quiting REPL");
throw new InterruptedException();
}
// catch (Throwable e) {
// eval.getStdErr().println(throwableMessage(e, eval.getStackTrace()));
// return null;
// }
catch (RascalShellExecutionException e) {
executor.getStdErr().println(e.getMessage());
return null;
}
catch (IOException e) {
executor.getStdErr().println(e.getMessage());
e.printStackTrace();
return null;
}
}
@Override
protected boolean printSpaceAfterFullCompletion() {
return false;
}
@Override
protected Collection<String> completeModule(String qualifier, String partialModuleName) {
List<String> entries = pcfg.listModuleEntries(qualifier);
if (entries != null && entries.size() > 0) {
if (entries.contains(partialModuleName)) {
// we have a full directory name (at least the option)
List<String> subEntries = pcfg.listModuleEntries(qualifier + "::" + partialModuleName);
if (subEntries != null) {
entries.remove(partialModuleName);
subEntries.forEach(e -> entries.add(partialModuleName + "::" + e));
}
}
return entries.stream()
.filter(m -> m.startsWith(partialModuleName))
.map(s -> qualifier.isEmpty() ? s : qualifier + "::" + s)
.sorted()
.collect(Collectors.toList());
}
return null;
}
@Override
protected Collection<String> completePartialIdentifier(String line, int cursor, String qualifier, String term) {
Collection<String> result = executor.completePartialIdentifier(qualifier, term);
if(result == null){
result = new TreeSet<String>();
}
if(cursor == term.length()){
for(String verb : SHELL_VERBS){
if(verb.startsWith(term)){
result.add(verb);
}
}
}
return result;
}
private static final SortedSet<String> commandLineOptions = new TreeSet<String>();
static {
commandLineOptions.add("profile");
commandLineOptions.add("trace");
commandLineOptions.add("coverage");
}
@Override
protected SortedSet<String> getCommandLineOptions() {
return commandLineOptions;
}
@Override
protected CompletionResult completeREPLCommand(String line, int cursor) {
return RascalCommandCompletion.complete(line, cursor, commandLineOptions, (l,i) -> completeIdentifier(l,i).joinWith(completeModule(l, i)), (l,i) -> completeModule(l,i), executor);
}
}