2

I have a program that depending on the arguments given works in different ways:

  • If there are 2 arguments - it takes 2nd argument as a filename, reads from it and then simply prints it out.
  • If there is 1 argument - it reads from stdin and also prints it out.

Here is the code:

main :: IO ()
main = do

-- Read given arguments
args <- getArgs

-- If file containing gramma was given
if length args == 2 then do
    hfile <- openFile (last args) ReadMode
    content <- hGetContents hfile
    let inGramma = getGramma content
    doJob (head args) inGramma
    hClose hfile
    return ()

-- If no file was given - reads from stdin
else if length args == 1 then do
    content <- getContents
    let inGramma = getGramma content
    doJob (head args) inGramma
    return ()

else do putStrLn "Invalid count of arguments!"

The problem is, when it reads from stdin, after every new line (enter pressed), it prints that line and than reads next. I need it to wait for the whole input and than print it out (after Ctrl+D).

Here are the functions used in that code:

-- | Structure of gramma
data GrammaStruct = Gramma
    { nonTerminals :: NonTerminals
    , terminals :: Terminals
    , start :: Start
    , rules :: Rules
    } deriving (Eq)

-- | Representation of gramma
instance Show GrammaStruct where
    show (Gramma n t s r) = 
                init (showSplit n) ++ 
        "\n" ++ init (showSplit t) ++ 
        "\n" ++ showStart s ++ 
        "\n" ++ init (showRules r)

-- | Print gramma
showGramma :: GrammaStruct -> IO ()
showGramma gr = do
    putStrLn $ show gr

-- | Transforms string given from file of stdin into gramma representation in app
getGramma :: String -> GrammaStruct
getGramma hIn = procLns (lines hIn)

-- | Depending on option given, provides required functionality
doJob :: String -> GrammaStruct -> IO ()
doJob op gramma
        | op == "-i" = showGramma gramma

Thank you.

1
  • Not that the question is wrong, per se, but "wait for the whole input and then print it out" is a pretty anti-modular requirement. I wouldn't be surprised if that requirement disappears or even becomes a problem for you later in the development of this tool. Commented Mar 15, 2015 at 21:28

2 Answers 2

4

The problem here is that getContents uses lazy IO, making the input stream to be processed line-by-line. If you want to force it to read the whole input before starting performing the job you can use the following hack:

...
if length args == 1 then do
    content <- getContents
    length content `seq` return ()   -- force the input to be fully read now
    let inGramma = getGramma content
    doJob (head args) inGramma
    return ()

Alternatively, use evaluate, or look in Hackage for a strict IO module providing a strict getContents. For instance, I just found the strict-io package providing System.IO.Strict.getContents. Using that you should be able to write (untested)

import qualified System.IO.Strict as S
...
if length args == 1 then do
    content <- run S.getContents
    ...
Sign up to request clarification or add additional context in comments.

Comments

1

That's not answering the question but instead of testing the length of the arguments, you can pattern match against it and return the correct content

main = do
    let (opt, contentM) = case getArgs of
              [opt]       -> (op, getContent)
              [opt, file] -> (op, hGetContent file)
              _           -> error ("Invalid count of argument")
    inGramma  <- fmap getGramma contentM
    doJob opt inGramma

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.