1

I'm working on a Windows 7 machine.

I'm working on an application which is a front for the GHCi interpreter for Haskell. The user will input a command, then Java will execute the command via the exec() method on Runtime, and then the application will display the text that would display if the user was just running GHCi using command prompt.

Right now, I'm running into issues with the loop that prints the output.

Here is the code I have right now.

public class GHCiTest {
public static Scanner rd, sc;

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {

try {

  System.out.println("Starting... ");

  Process p = Runtime.getRuntime().exec("ghci");

  PrintStream hugsin  = new PrintStream(p.getOutputStream());
  InputStream hugsout = p.getInputStream();

  sc = new Scanner(hugsout);
  rd = new Scanner(System.in);

  String rdnextline;



  while (true){
    while (sc.hasNextLine()){
        System.out.println(sc.nextLine());

    }
      System.out.println("yay");
      rdnextline = rd.nextLine();
    if (rdnextline == "quit"){break;}
    hugsin.println(rdnextline);
    hugsin.flush();

  }
  System.out.println(" ... successful completion.");
}
catch(IOException e) {
  e.printStackTrace();
}
}

}

I know that the initial starting of GHCi is working, because the program is printing out "GHCi, version 7.10.3: http://www.haskell.org/ghc/ :? for help". However, the issue appears to be the while(sc.hasNextLine()) loop, which is supposed to read the output of the command prompt and output it until there's nothing left, as it won't break out of the loop and proceed to read the user input. I know this because the program isn't printing the "yay" flag I put in after the loop.

maxematician
  • 199
  • 7
  • Have you tried using a buffered reader instead of Scanner? – Norsk Apr 18 '16 at 04:22
  • 2
    Because `sc.hasNextLine()` waits until the **end of stream** has been reached (if there's no input)... And end of stream will happen only when the program quits. – Codebender Apr 18 '16 at 04:25
  • 1
    Two concerns: 1. GHCi isn't really designed to be used like this. It's an interactive development tool. Relying on any details of its interactive user interface could make your program break when a new version comes out. Could you just use `ghc -e` for your purposes? 2. You'd better not be exposing GHCi to the web, unless you have some major sandbox around it; full GHCi access is like full shell access. – dfeuer Apr 21 '16 at 16:03

2 Answers2

2

Your loop won't exit until the end of the stream has been reached:

while (sc.hasNextLine()){
    System.out.println(sc.nextLine());
}

The end of the stream is the end of your process. So, your Java program is waiting for the sub process to run to completion, and terminate. Once that happens, the loop will end, and the Java program will send the desired commands to the process.

Sorry, I mean, try to send the desired commands to the process; it won't succeed because the process has terminated.

If the GHCi process outputs a "prompt" of some kind, you could try to break your while(...) { print } at that moment, get input from the user, send that to the process, and then loop back and re-enter your while(...) { print }, waiting for the next prompt.

Assuming the prompt does not end with a newline, but rather appears at the start of a line where the user input gets typed, you cannot use a while(sc.hasNextLine()) { ... } type of loop, because the prompt is not a complete line. You might have to resort to reading character by character, looking for the prompt sequence in the last "n" characters.

Looks like you can change the prompt in GHCi. See here for details. If you change the prompt to end with a newline, you could still read the stream in lines.

while (sc.hasNextLine()){
    String line = sc.nextLine();
    if (line.equals("YourPromptHere"))
         break;
    System.out.println(line);
}

(Alternately, you might be able to do something with threads to allow both parts to run without blocking each other. Of course, threading comes with its own issues and complexity.)


EDIT

I had a blinding flash of the obvious. Assuming GHC's prompt looks like this ...

GHCi, version 7.10.3
yada, yada, yada ...
Main> _

... you could set the scanner's delimiter to be the prompt string Main>.

// Set scanner delimiter to GHCi's Prompt string
sc = new Scanner(hugsout).setDelimiter("^Main> ");

while (sc.hasNext()) {

    // Echo GHCi's output upto the delimiter (prompt)
    System.out.println(sc.next());

    // Read user input & transfer to GHCi.
    System.out.print("Replacement Prompt> ");
    rdnextline = rd.nextLine();
    if (rdnextline == "quit") {
        break;
    }
    hugsin.println(rdnextline);
    hugsin.flush();
 }

Note: This does not take into account the secondary prompt, used when GHCi expects more input to complete the command. You could use a regex something like "^Main> |\bAlt> " that matches either prompt, but you would not be able to tell which prompt the delimiter matched. The first subexpression "^Main> " matches the start of a line, followed by "Main> ", where as the second subexpression "\bAlt> " only matches a word boundary followed by "Alt> ". This is because the output stream of the GHCi, would look like "\nMain> Alt> " with a long pause before the Alt>; the "newline" before Alt> would normally come from the echoing of the Enter keypressed on the input stream.

Community
  • 1
  • 1
AJNeufeld
  • 8,526
  • 1
  • 25
  • 44
  • Do you know how I could have it print out everything until the user has to enter input? – maxematician Apr 18 '16 at 04:33
  • Watch the output stream, looking for a prompt from `GHCi`. See edit – AJNeufeld Apr 18 '16 at 04:35
  • Are you saying after I receive a new line, I should check to see if it is equal to "Main>", which is what GHCi prints before requesting user input? Is there a general solution to determining whether the scanner is blocked from reading the next character in an InputStream? – maxematician Apr 21 '16 at 08:42
  • If you can change the prompt to "Main>\n", (note the newline at the end), then yes. `in.nextLine()` would return the entire prompt (minus the newline), which would be an indication that GHCi will be expecting user input. Is there a general solution? No. The Haskell program may take a long time to produce output; is the scanner blocked? Alternately, the Haskell program might be expecting input from the user. If you wait (say) 10 seconds after the last character appeared on the output, that might indicate it is waiting for input ... or it could be just slow. – AJNeufeld Apr 21 '16 at 14:13
  • Blinding flash of the obvious. Instead of trying to force GHCi's prompt to match the `nextLine()` delimiter `\n`, change the scanner's delimiter to match the GHCi's prompt. See edit. – AJNeufeld Apr 21 '16 at 15:49
  • The issue with waiting is that I'm having GHCi execute some potentially lengthy calculations, and it's difficult to know how long I should wait. I got it to work by using "Main>" as a delimiter, but now I'm curious if there's a general method to know when a process is waiting for input. Like if there's an "isWaiting()" method. – maxematician Apr 21 '16 at 22:21
  • No, as long as the process hasn't terminated or closed its standard input, it is "waiting for input". But, if you are using the "Main>" delimiter, you don't really need to know how long to wait for. The `in.hasNext()` will block until the stream is closed or the prompt is encountered, so it is doing the waiting for you. – AJNeufeld Apr 21 '16 at 22:31
2

Receive output of ghci in another thread like this.

System.out.println("Starting... ");

Process p = Runtime.getRuntime().exec("ghci");

PrintStream hugsin = new PrintStream(p.getOutputStream());
InputStream hugsout = p.getInputStream();
Scanner rd = new Scanner(System.in);
new Thread(() -> {
    try (Reader r = new InputStreamReader(hugsout)) {
        int ch;
        while ((ch = r.read()) != -1)
            System.out.print((char)ch);
    } catch (IOException e ) {}
}).start();
Scanner sc = new Scanner(hugsout);
String rdnextline;
while (true) {
    rdnextline = rd.nextLine();
    hugsin.println(rdnextline);
    hugsin.flush();
    if (rdnextline.equals("quit")) {
        break;
    }
}
System.out.println(" ... successful completion.");
  • Thanks for that, it seems to be working. It appears you take a reader and have it read the characters and print them out one at a time. Why are you checking if the character is not -1? – maxematician Apr 18 '16 at 05:03
  • And how come the while(true) loop only continuously retrieves user input? It appears that the program only prints the output of GHCi once, yet it loops just fine. – maxematician Apr 18 '16 at 05:11