packages feed

rtk-0.11: StrQuote.hs

{- Taken from http://www.haskell.org/haskellwiki/Poor_man's_here_document -}
{-# LANGUAGE QuasiQuotes, TemplateHaskell #-}
{-# OPTIONS_GHC -Wno-type-defaults #-}
module StrQuote(str) where

import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Language.Haskell.Exts.Parser
import Language.Haskell.Exts.Syntax
import Data.String.Utils
import Data.List

getCodeToRunHelper :: (Eq t1, Num t1, Num t2) => [Char] -> t1 -> t2 -> Maybe t2
getCodeToRunHelper [] _ _ = Nothing
getCodeToRunHelper (')' : _) 0 result = Just $ result + 1
getCodeToRunHelper (')' : rest) bcounter result = getCodeToRunHelper rest (bcounter - 1) (1 + result)
getCodeToRunHelper ('(' : rest) bcounter result = getCodeToRunHelper rest (bcounter + 1) (1 + result)
getCodeToRunHelper (_ : rest) bcounter result = getCodeToRunHelper rest bcounter (1 + result)

getCodeToRun :: Num a => String -> Maybe a
getCodeToRun "" = Nothing
getCodeToRun ('(' : rest) = getCodeToRunHelper rest 0 2
getCodeToRun _ = Nothing

processVarAndAfter :: [Char] -> ([Char], String)
processVarAndAfter varAndAfter =
  case getCodeToRun $ drop 1 varAndAfter of
    Nothing ->
      let mbSpaceInd = findIndex (\c -> (c == ' ') || (c == '\n')) varAndAfter
          mbTildInd = findIndex ((==) '~') varAndAfter
      in
        case mbSpaceInd of
          Nothing ->
            case mbTildInd of
              Nothing -> (varAndAfter, "")
              Just tildInd ->
                  let (var, tildAndAfter) = splitAt tildInd varAndAfter in (var, drop 1 tildAndAfter)
          Just spaceInd ->
            case mbTildInd of
              Nothing -> splitAt spaceInd varAndAfter
              Just tildInd ->
                if tildInd < spaceInd
                  then let (var, tildAndAfter) = splitAt tildInd varAndAfter in (var, drop 1 tildAndAfter)
                  else splitAt spaceInd varAndAfter
    Just ind -> splitAt ind varAndAfter

splitStringByVarsHelper :: String -> [[Char]] -> [[Char]]
splitStringByVarsHelper [] result = result
splitStringByVarsHelper inputStr result =
  case elemIndex '?' inputStr of
    Nothing -> inputStr : result
    Just ind ->
      let (beforeVar, varAndAfter) = splitAt ind inputStr
          (var, afterVar) = processVarAndAfter varAndAfter
        in splitStringByVarsHelper afterVar (var : beforeVar : result)

translateQName :: QName () -> Language.Haskell.TH.Name
translateQName qname =
    case qname of
      UnQual () (Ident () s) -> mkName s
      UnQual () (Symbol () s) -> mkName s
      Qual () _ (Ident () s) -> mkName s
      Qual () _ (Symbol () s) -> mkName s
      Special () (UnitCon ()) -> mkName "()"
      Special () (ListCon ()) -> mkName "[]"
      Special () (FunCon ()) -> mkName "->"
      Special () (TupleCon () _ n) -> mkName $ "(" ++ replicate (n-1) ',' ++ ")"
      Special () (Cons ()) -> mkName ":"
      Special () (UnboxedSingleCon ()) -> mkName "(# #)"
      Special () (ExprHole ()) -> mkName "_"

translateQOp :: Quote m => QOp () -> m Language.Haskell.TH.Exp
translateQOp qop =
    case qop of
      QVarOp () qname -> varE $ translateQName qname
      QConOp () qname -> conE $ translateQName qname

convertToTHHelper :: Quote m => Language.Haskell.Exts.Syntax.Exp () -> m Language.Haskell.TH.Exp
convertToTHHelper (Paren () e) = convertToTHHelper e
convertToTHHelper (App () e1 e2) = appE (convertToTHHelper e1) (convertToTHHelper e2)
convertToTHHelper (InfixApp () e1 qop e2) = uInfixE (convertToTHHelper e1) (translateQOp qop) (convertToTHHelper e2)
convertToTHHelper (Var () qnm) = varE $ translateQName qnm
convertToTHHelper (Lit () (String () s _)) = stringE s
convertToTHHelper e = stringE ("<unsupported expression: " ++ show e ++ ">")

convertToTH :: Quote m => ParseResult (Language.Haskell.Exts.Syntax.Exp ()) -> m Language.Haskell.TH.Exp
convertToTH result =
    case result of
      ParseOk expr -> convertToTHHelper expr
      ParseFailed loc errMsg -> stringE ("<" ++ errMsg ++ ":" ++ show loc ++ ">")

splitStringByVars :: Quote m => [Char] -> m Language.Haskell.TH.Exp
splitStringByVars inputString =
  let result = splitStringByVarsHelper (replace "|~]" "|]" inputString) []
  in foldr (\s prevExpr ->
             case s of
               "?()" -> [|$prevExpr ++ $(stringE "<empty expr>")|]
               '?' : '(' : rest ->
                      let lifted = convertToTH $ fmap (fmap (const ())) $ parseExp $ '(' : rest
                        in [|$prevExpr ++ $lifted|]
               "?" -> [|$prevExpr ++ $(stringE "<empty var name>")|]
               '?' : rest ->
                      [|$prevExpr ++ $(varE $ mkName rest)|]
               _ -> [|$prevExpr ++ $(stringE s)|])
           (stringE "") result

str :: QuasiQuoter
str = QuasiQuoter { quoteExp = splitStringByVars, quotePat = litP . stringL, quoteType = \_ -> return ListT, quoteDec = \_ -> return [] }