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
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)
getDocgetDoc 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)
makeValmakeVal 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))) []]
scanSrcscanSrc 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
patternspatterns 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
hereDocshereDocs 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 []