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.

3
  • Have you tried using a buffered reader instead of Scanner? Commented Apr 18, 2016 at 4: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. Commented Apr 18, 2016 at 4: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. Commented Apr 21, 2016 at 16:03

2 Answers 2

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.");
Sign up to request clarification or add additional context in comments.

2 Comments

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?
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.
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.

7 Comments

Do you know how I could have it print out everything until the user has to enter input?
Watch the output stream, looking for a prompt from GHCi. See edit
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?
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.
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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.