Tomer
Tomer

Reputation: 1229

Converting literate Haskell (.lhs) to Haskell (.hs)

Is there an easy way to convert a literate Haskell file (.lhs) to a regular Haskell (.hs) source file?

I thought there might be a GHC option, but the GHC manual doesn't seem to have much information on literate programs or the .lhs format. The word "literate" doesn't even appear in the index!

The Literate programming link on the Wiki includes links to scripts that convert between "bird" and "\begin{code}..\end{code}" styles or convert .lhs to TeX format, but that's it.

Upvotes: 8

Views: 3241

Answers (2)

buggymcbugfix
buggymcbugfix

Reputation: 320

Just hack together a quick script like the one below :) This converts the literate parts to line-comments.

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}

import Control.Monad (forM_)
import System.FilePath.Glob (glob)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import System.FilePath (replaceExtension)
import Development.Shake.Command

unLiterateHaskell :: T.Text -> T.Text
unLiterateHaskell = T.unlines . map processLine . T.lines

processLine :: T.Text -> T.Text
processLine (T.stripEnd -> txt) = case T.uncons txt of
  Nothing -> txt
  Just ('>', txt) -> T.drop 1 txt -- may need to adjust here depending on style
  Just{} -> "-- " <> txt

main :: IO ()
main = do
  files <- glob "**/*.lhs"
  forM_ files $ \f -> do
    src <- T.readFile f
    T.writeFile (replaceExtension f "hs") (unLiterateHaskell src)
    Exit _ <- cmd ("mv" :: String) f (f <> ".bak")
    pure ()

Upvotes: 0

K. A. Buhr
K. A. Buhr

Reputation: 50884

GHC itself uses a standalone C program called unlit to process .lhs files. You can probably find it installed somewhere in your GHC installation. If you run it, it shows a few command line options without explanation:

$ cd ~/.stack/programs/x86_64-linux/ghc-8.6.4/lib/ghc-8.6.4/bin
$ ./unlit
usage: unlit [-q] [-n] [-c] [-#] [-P] [-h label] file1 file2

Digging into the source code, it looks like the options are:

-q   "quiet": ignore certain errors, so permit missing empty lines before and after
     "bird" style and accept completely empty programs
-n   (default) "noisy": opposite of -q, so be noisy about errors
-c   "crunch": don't leave blank lines where literate comments are removed
-#   delete lines that start with "#!"
-P   suppress CPP line number pragma, but only matters if `-h` is supplied
-h   File name for an initial `#line` pragma

So, the command line:

$ /path/to/unlit -c myfile.lhs myfile.hs

will probably do a good job converting myfile.lhs:

This is a literate program

> main :: IO ()

using both code styles
\begin{code}
  main = putStrLn "hello"
\end{code}

to an "illiterate" program myfile.hs:

  main :: IO ()
  main = putStrLn "hello"

For "bird" style, it actually replaces the '>' character with a space and leaves the rest of the indentation in place, so for my example above, both lines in myfile.hs are indented with two spaces, which could be a drawback.

Upvotes: 10

Related Questions