HereDocs

HereDocs.hs

Copyright © 2007 Dave Bayer. Subject to a BSD-style license.

This module is part of the Annote project.

module HereDocs (hereDocs) where

HereDocs provides a here document facility in Haskell using Template Haskell, which is invoked by the GHC option -fth.


To include here documents in a Haskell source file, one first needs to invoke Template Haskell, for example using

{-# OPTIONS_GHC -fth #-}

To include here documents found in the current source file, one can use the idiom

$(hereDocs $ let [e] = [] in e)

This passes a pattern match failure to hereDocs, from which it can determine the name of the current source file. This construction is preferable to passing an assert failure, because asserts require the module Control.Exception, and are removed in optimized code.

One can instead name the file where the here documents can be found:

$(hereDocs "Strings.hs")

Here is an example of a here document that can be embedded in a Haskell source file:

{-
ruby = <<RUBY
#!/usr/bin/env ruby
hello = <<EOF
Ruby is not
   an acceptable Lisp
EOF
puts hello
RUBY
-}

This here document begins with the line ruby = <<RUBY and is terminated by the line RUBY. It is contained within a Haskell comment, so that it is not seen during later compilation. Here documents can instead be placed in separate files, in which case they do not need to be inside comments.

hereDocs will bind a string consisting of this Ruby script to the variable ruby. Note that the Ruby script contains its own here document hello, with the same syntax but with the different terminator EOF. hereDocs will not create a variable hello; it does not extract nested here documents.

This code was announced in the Haskell Cafe thread

Compile-time here document facility

Don Stewart responded with details on cabalising:

How to write a Haskell program
Haskell Hacking: a journal of Haskell programming


Control.Exception provides support for raising and catching exceptions.

import Control.Exception (Exception(PatternMatchFail),catchJust,evaluate)

Language.Haskell.TH.Syntax provides abstract syntax definitions for Template Haskell.

import Language.Haskell.TH.Syntax
    (Q,Dec(..),Exp(..),Type(..),Body(..),Lit(..),Pat(..),runIO,mkName)

getDoc

getDoc is called when the remaining lines txt of a source file begin with a here document, terminated by the line eof. It returns a pair consisting of the here document, and the rest of the source file.

getDoc :: String → [String] → (String,[String])
getDoc eof txt =
    let (doc,rest) = break (== eof) txt
    in  (unlines doc, drop 1 rest)

makeVal

makeVal creates a Template Haskell expression binding the string value doc to the variable var.

makeVal :: String → String → [Dec]
makeVal var doc = let name = mkName var in
    [SigD name (ConT (mkName "String")),
    ValD (VarP name) (NormalB (LitE (StringL doc))) []]

scanSrc

scanSrc scans the remaining lines of a source file for here documents, accumulating the results in vals. When it is done, it lifts the results to the Q monad.

scanSrc :: [Dec] → [String] → Q [Dec]
scanSrc vals [] = return vals
scanSrc vals (x:xt) = case words x of
    [var, "=", ('<':'<':eof)] →
        let (doc,rest) = getDoc eof xt
            val = makeVal var doc
        in  scanSrc (vals ++ val) rest
    _ → scanSrc vals xt

patterns

patterns is modeled after the exception predicate assertions defined in Control.Exception. Unlike assert failures, pattern match failures are reported even in optimized code.

patterns :: Exception → Maybe String
patterns (PatternMatchFail e) = Just e
patterns _ = Nothing

hereDocs

hereDocs is the exported function for this module. If src generates an assert error, then hereDocs extracts the name of the source file from this error. Otherwise, src is used as the name of the source file, which is processed by scanSrc.

hereDocs :: FilePath → Q [Dec]
hereDocs src =
    let fin = catchJust patterns (evaluate src) (return.takeWhile (/= ':'))
    in  runIO (fin >>= readFile >>= return . lines) >>= scanSrc []