Editable default string in Haskell's terminal input - haskell

I want to be able to prompt the user for input (let's say a FilePath), but also to offer a mutable/interactive string as a default, so instead of having the user type the full path, I can prompt with:
C:\Users\John\project\test
and have them be able to backspace 4 times and enter final to yield C:\Users\John\project\final, rather than type the entire path.
However printing a default string with putStr or System.IO.hPutStr stdout does print this default to the terminal, but does not allow me to alter any of it. E.g.
import System.IO
main = do
hSetBuffering stdout NoBuffering
putStr "C:\\Users\\John\\project\\test"
l <- getLine
doSomethingWith l
I suspect Data.Text.IO's interact may be able to do what I want but I could not get it to work.
Any suggestions would be greatly appreciated.

getLine doesn’t offer any facility for line editing. For this you can use a library like haskeline instead, for example:
import System.Console.Haskeline
main :: IO ()
main = do
runInputT defaultSettings $ do
mInput <- getInputLineWithInitial "Enter path: "
("C:\\Users\\John\\project\\test", "")
case mInput of
Nothing -> do
outputStrLn "No entry."
Just input -> do
outputStrLn $ "Entry: " ++ show input
An alternative is to invoke the program with a wrapper that provides line editing, such as rlwrap. For building a more complex fullscreen text UI, there is also brick, which provides a simple text editing component in Brick.Widgets.Edit.

Related

Process arrow key ANSI escape sequences on haskell stdin

I'm trying to process arrow key ANSI escape sequences i.e.
up - "\033[A"
down - "\033[B"
left - "\033[D"
right - "\033[C"
in my programme so when I press the up/down/left/right arrow key, it won't have to look like this:
% stack runghc test.hs
Input a name?
^[[A^[[B^[[C^[[D^
on my stdin, but rather I would like those keys to be suppressed or even better,
for them to actually work(i.e move the cursor left/right). My code is as follows:
main = do putStrLn "Input a name?"
name <- getLine
putStrLn $ ("His name is " ++ name)
Any help would be appreciated. Thanks in advance.
The easiest way to get readline-like functionality is to just use readline. For most simple use cases, rlwrap is good enough, as described in One REPL to bind them all?. If you need to do fancier integrations, you can use the readline package.
I had trouble installing the readline library due to some errors and decided to use the haskeline library, which is a more portable pure-haskell replacement for it.
Using its syntax and modifying the earlier code, I got:
main :: IO ()
main = do putStrLn "Input a name?"
runInputT defaultSettings insertion
where
insertion :: InputT IO ()
insertion = do
minput <- getInputLine ""
case minput of
Nothing -> return ()
Just input -> do outputStrLn $ "His name is " ++ input
Which solves the problem as I am now able to move my cursor with the arrow keys freely without having to see any trailing ANSI escape sequences as shown below:

Haskell placing putStr and putStrLn at the end of a program instead of during execution

I have a simple program that just takes a string from the user and a key and encrypts the string with a caesar cipher function. The function itself works so I won't show to source for that. The issue is that when the compiler compiles the program it will allow me to input all the getLines and then after having inputted everything the program will print all the putStr and putStrLn then close. This ONLY happens when the program is executed using "runhaskell" or compiled and executed as an exe, ie. not in the interpreter. Here is the program:
main = do
choice <- prompt "Would you like to encrypt (1) or decrypt (2)? "
if choice == "1" then do
encrypt <- prompt "Enter code for encryption: "
k <- prompt "Enter key for the code: "
let key = read k in
putStrLn ("Encrypted message: " ++ (caesar key encrypt) ++ "\n")
else do
decrypt <- prompt "Enter code for decryption: "
k <- prompt "Enter key for the code: "
let key = read k in
putStrLn ("Decrypted message: " ++ (caesar (-key) decrypt) ++ "\n")
getLine
prompt str = do
putStr str
getLine
Output when run in the interpreter:
Prelude Main> main
Would you like to encrypt (1) or decrypt (2)? 1 <- the one is user input
Enter code for encryption: Hello world <- user input
Enter key for the code: 2 <- user input
Encrypted message: Jgnnq"yqtnf <- program output
Output when executed after compilation:
1 <- user has to input before the console is printed out
Hello world <--┘
2 <--┘
Would you like to encrypt (1) or decrypt (2)? Enter code for encryption: Enter key for the code: Encrypted message: Jgnnq"yqtnf
Is there something about putStrLn and putStr that I am overlooking? Do they only execute as the result of a function or something?
Also, the "prompt" function I created is not the issue because I replaced all the uses of prompt with their respective putStr's and getLine's and it still did the same thing.
runhaskell and ghci are designed to start your program as quickly as possible, and de-emphasize the efficiency of running the program. For that reason they make many sub-optimal efficiency decisions compared to the ghc, and one that's biting you here is that they use no buffering on standard input or output by default, while ghc uses the more efficient line buffering by default. Since you never print a line ending during your prompts, in the compiled version, the buffer is not shown to the user... until you reach the putStrLn at the end of the program that prints a line ending, and the whole buffer is shown at once.
You have some choices:
Explicitly request that there be no buffering. This will make your compiled program marginally slower (very unlikely to be noticed at human interaction speeds) but behave more like the interpreted version. Import System.IO and use hSetBuffering stdout NoBuffering at the beginning of main.
Explicitly flush the buffer when you know you are going to pause for user input. Import System.IO and use hFlush stdout just before each call to getLine.
Do output in a way that behaves the same between the two buffering modes. Use putStrLn instead of putStr everywhere.

Haskell: Why does this function keep asking for user input and not terminating

I'm learning some Haskell and I came across this small program
reverseLines :: String -> String
reverseLines input =
unlines (map reverse (lines input))
main :: IO ()
main = interact reverseLines
This program will keep asking the user for more input and reverse the input and print it on the screen.
Most of this is straight forward but one thing I can't wrap my head around is why does this function keeps running and ask the user for more input whereas if I just replace the reverseLines function with a function the simply returns some string it will not happen.
This program will stop after one execution:
foo input = "Stops"
main :: IO ()
main = interact foo
Why?
If you look at the source of interact you see this:
interact f = do s <- getContents
putStr (f s)
see the getContents? This is where the magic starts - it will read everything till EOF
Now in Haskell this is lazy-IO which can be bad but here is almost magical - see the string is read lazily and passed to your reverseLines - this one of course will only generate output as soon as it saw \n characters (the lines) and so it seems your program is some kind of REPL.
In the second one you don't consume any of the lazy-string at all so it stops ASAP
As I wrote in the comments you can play with this by either passing content into the program using a file (or echo) and pipes on the terminal:
echo "Hello World\nBye Bye" | runhaskell LazyIO.hs
or using CTRL-D to pass in the EOF yourself.
To get a feeling for it I would play with the functions more - what happens if you use something that needs to see the complete input first (try reverse without the maps)? What happens with words instead of lines, ...?
Have fun!

Adding the possibility to write a AST-file to my (rail-)compiler

I'm writing rail-compiler (rail is an esoteric language) in Haskell and I get some problems within the main-function of my mainmodule.
1) I want my program to ask wheter I want to run the compiling-pipeline or simply stop after the lexer and write the AST to a file so another compiler can deal with my AST (Abstract Synatx Tree). Here is my program:
module Main (
main -- main function to run the program
)
where
-- imports --
import InterfaceDT as IDT
import qualified Testing as Test
import qualified Preprocessor as PreProc
import qualified Lexer
import qualified SyntacticalAnalysis as SynAna
import qualified SemanticalAnalysis as SemAna
import qualified IntermediateCode as InterCode
import qualified CodeOptimization as CodeOpt
import qualified Backend
-- functions --
main :: IO()
main = do putStr "Enter inputfile (path): "
inputfile <- getLine
input <- readFile inputfile
putStr "Enter outputfile (path): "
outputfile <- getLine
input <- readFile inputfile
putStr "Only create AST (True/False): "
onlyAST <- getLine
when (onlyAST=="True") do putStrLn "Building AST..."
writeFile outputfile ((Lexer.process . PreProc.process) input)
when (onlyAST=="False") do putStrLn ("Compiling "++inputfile++" to "++outputfile)
writeFile outputfile ((Backend.process . CodeOpt.process . InterCode.process . SemAna.process . SynAna.process . Lexer.process . PreProc.process) input)
I get an error in Line 21 (input <- readFile inputfile) caused by the <-. Why?
How should I do it?
2) Next thing is that I want to refactor the program in that way, that I can call it from the terminal with parameters like runhaskell Main(AST) (in that way it should just create the AST) or like runhaskell Main.hs (in that way it should do the whole pipeline).
I hope for your help!
For your error in (1), your program doesn't look syntactically incorrect at line 21 to me. However an error at <- would happen if that line were indented differently from the previous one. I suspect that you are having an indentation error due to mixing tabs and spaces in a way that looks correct in your editor but disagrees with Haskell's interpretation of tabs. The simplest recommendation is to always use spaces and never tabs.
You also have an extra copy of that line later, which you might want to remove.
I also suspect you may need to use hFlush stdin after your putStr's, for them to work as prompts.
For (2), I'd suggest using a library for proper command line argument and option parsing, such as System.Console.GetOpt which is included with GHC, or one of the fancier ones which you can find on Hackage.

Haskell - Passing a variable to a sub-function

I have a function main, which has a sub-function menu. In main, I load a file, ask for the user to input their name, then call menu to display a menu. After each action in the menu has been completed, I'm going to call menu again until an exit condition is met.
I'm having trouble passing the file I'm loading in main into menu - I want to perform an action on the database for each command.
This is my code so far (i've cut out bits that are irrelevant):
main :: IO ()
main = do contents <- readFile "myfile.txt"
let finalDatabase = (read contents :: [Film])
putStr "Please enter your name: "
name <- getLine
menu finalDatabase
where menu db = do putStrLn "Please select an option:"
putStrLn "1: Display all films"
putStr "Choice: "
choice <- getLine
case choice of {
"1" -> displayAll db;
}
menu
The displayAll function just prints out a Database nicely.
I'm getting the following error in WinHugs:
ERROR file:.\films.hs:152 - Type error in function binding
*** Term : menu
*** Type : [([Char],[[Char]],Int,[[Char]])] -> IO a
*** Does not match : IO a
I thought that if I didn't specify a line like menu :: Database -> IO (), it would accept any parameters without being concerned with their type.
Any suggestions?
Edit:
Stupid mistake, I just forgot to pass the database when calling menu again after the case statement!
I thought that if I didn't specify a line like menu :: Database -> IO (), it would accept any parameters without being concerned with their type.
That's not true, in your case menu really has the type Database -> IO () so in the last line before the where, you need to give it a Database to get an IO () - probably db (as in menu db) but maybe later, you'll start modifying the database and then you can pass the new one in. Haskell doesn't just look around at what's in scope to find a suitable value of type Database!
If the database truly is fixed you don't need to pass it in and you could use something like:
main = do contents <- readFile "myfile.txt"
let finalDatabase = (read contents :: [Film])
putStr "Please enter your name: "
name <- getLine
menu
where menu = do putStrLn "Please select an option:"
putStrLn "1: Display all films"
putStr "Choice: "
choice <- getLine
case choice of {
"1" -> displayAll finalDatabase -- instead of db
}
menu
But, as said, that's going to break when you start modifying the 'database'. Let's say you define a function doSomethingWithDatabaseDependingOnChoice :: String -> [Film] -> IO [Film] which takes the choice and the old database, performs some in/output and returns a new database, you could use it as follows:
<same as before>
menu finalDatabase
where menu db = do putStrLn "Please select an option:"
putStrLn "1: Display all films"
putStr "Choice: "
choice <- getLine
newDb <- doSomethingWithDatabaseDependingOnChoice choice db
menu newDb
Another option would be to use implicit parameters, but I'm guessing that's a bit too advanced at the moment!
First of all, no, not specifying a type, does not mean that the function will accept any type. It means that the type will be inferred.
The reason that you're getting that particular type error is that at the end of menu's do block, you write menu, which is a function and not a value of type IO. I'm not exactly sure why you do that. If you want to create an infinite loop, you should change the last call to menu db, instead of returning the function unapplied. If you don't want an infinite loop, I don't see why you use menu at the end at all.

Resources