packages feed

authoring-0.3: src/Text/Authoring/TH.hs

{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}

{-|

This module provides quasi-quotes 'rawQ' and 'escQ' for writing papers. 
The quasi-quotes will generate authoring monads, so you can use them in authoring 
context like follows.

> paragraph = do
>   let takahashi2007 = citep ["isbn:9784130627184"] 
>       val = 3e6
>   [rawQ| The dielectric strength of air is $ #{val} $ V/m @{takahashi2007}.  |]

We support antiquote syntax:

>  #{val}

for embedding values ('Show' instance is required), and

>  @{...}

for embedding authring monads.

-}

module Text.Authoring.TH (rawQ, escQ) where


import Control.Applicative
import Data.Char (isSpace)
import Data.Monoid
import Data.Text (pack)
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Text.Trifecta
import Text.Trifecta.Delta
import Text.Parser.LookAhead
import Text.PrettyPrint.ANSI.Leijen as Pretty hiding (line, (<>), (<$>), empty, string)
import Safe (readMay)
import System.IO

import Text.Authoring.Combinator.Writer (raw, esc)


-- | Quote with LaTeX special characters escaped. 
escQ = rawQ {quoteExp =  parseE (QQConfig { escaper = appE (varE 'esc)})}

-- | Quote without escaping any special characters.                                                           
rawQ :: QuasiQuoter
rawQ = QuasiQuoter { 
  quoteExp = parseE (QQConfig { escaper = appE (varE 'raw)}),
  quotePat  = error "Don't use Authoring QuasiQuotes in expression context" ,  
  quoteType = error "Don't use Authoring QuasiQuotes in expression context" ,  
  quoteDec  = error "Don't use Authoring QuasiQuotes in expression context" 
  }

data QQConfig = QQConfig 
  { escaper :: ExpQ -> ExpQ }


parseE :: QQConfig -> String -> ExpQ
parseE cfg str = do
  let res = parseString parseLang (Columns 0 0) str
  case res of
    Failure xs -> do 
      runIO $ do
        displayIO stdout $ renderPretty 0.8 80 $ xs <> linebreak
        putStrLn "Due to parse failure entire quote will be processed as a string."
      joinE $ map (cvtE cfg) $ [StrPart str]
    Success x -> joinE $ map (cvtE cfg) x


cvtE :: QQConfig -> Component -> ExpQ
cvtE cfg (StrPart x)    = escaper cfg $ appE (varE 'pack) $ stringE x
cvtE cfg (EmbedShow x)  = escaper cfg $ appE (varE 'pack) $ appE (varE 'showJoin) (varE $ mkName $ trim x)
cvtE _   (EmbedMonad x) = (varE $ mkName $ trim x)                           

trim :: String -> String
trim = dropWhile isSpace . reverse . dropWhile isSpace . reverse

showJoin :: Show a => a -> String
showJoin x = maybe sx id rsx
  where 
    sx :: String
    sx = show x
    rsx :: Maybe String
    rsx = readMay sx

joinE :: [ExpQ] -> ExpQ
joinE = foldl ap [e| return () |] 
  where
    ap a b = appE (appE (varE '(>>) ) a ) b

data Component 
  = StrPart    String
  | EmbedMonad String
  | EmbedShow  String deriving (Eq,Show)

parseLang :: Parser [Component]
parseLang = (many $ choice [try parseEmbedMonad, try parseEmbedShow, parseStrPart]) <* eof

parseStrPart :: Parser Component
parseStrPart = StrPart <$> go <?> "String Part"
  where
    go = do
      notFollowedBy $  choice [string "#{", string "@{"]
      h <- anyChar
      t <- manyTill anyChar (lookAhead $ choice [string "#{", string "@{", eof >> return ""])
      return $ h:t

parseEmbedMonad :: Parser Component
parseEmbedMonad = EmbedMonad <$> between (string "@{") (string "}") (some $ noneOf "}")
          <?> "Embed MonadAuthoring @{...}"


parseEmbedShow :: Parser Component
parseEmbedShow = EmbedShow <$> between (string "#{") (string "}") (some $ noneOf "}")
          <?> "Embed an instance of Show #{...}"