module Marvin.Interpolate
( is
, iq
, interpolateInto
) where
import Control.Monad
import Control.Monad.State as S
import Data.Either
import Data.List (intercalate)
import Data.Monoid
import Language.Haskell.Meta.Parse.Careful
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Text.Parsec
import Util
type Parsed = [Either String String]
escapeChar :: Char
escapeChar = '~'
type ParseM = Parsec String Int
parser :: ParseM Parsed
parser = manyTill (parseInterpolation <|> parseString) eof
parseString :: ParseM (Either String String)
parseString = do
chunk <- many $ satisfy (/= '#')
fmap (Right . (chunk ++)) $ (eof >> return "") <|> (lookAhead (try (char '#' >> anyChar)) >>= endOrEscape) <|> fmap return anyChar
where
endOrEscape :: Char -> ParseM String
endOrEscape '{' = return ""
endOrEscape '#' = count 2 anyChar >> return "#"
endOrEscape ']' = count 2 anyChar >> return "]"
endOrEscape _ = fail ""
parseInterpolation :: ParseM (Either String String)
parseInterpolation = try (string "#{") >> (Left <$> parseExpr)
where
parseExpr = do
chunk <- many $ noneOf ['}', '"', '\'', '{']
fmap (chunk ++) $ (eof >> error "eof in interpolation") <|> (anyChar >>= continue)
continue :: Char -> ParseM String
continue '{' = modifyState succ >> fmap ('{':) parseExpr
continue '}' = do
s <- getState
if s == 0
then return ""
else ('}':) <$> (modifyState succ >> parseExpr)
continue '\"' = ('"':) <$> parseStr
continue '\'' = do
char '\''
inner <- ((:) <$> char '\\' <*> fmap return anyChar) <|> fmap return anyChar
char '\''
return $ '\'':inner ++ "'"
parseStr = do
chunk <- many $ noneOf ['"', '\\']
fmap (chunk ++) $ (eof >>= fail "eof in string literal")
<|> (anyChar >>= continueStr)
where
continueStr '"' = ('"':) <$> parseExpr
continueStr '\\' = do
escaped <- anyChar
(\a -> '\\':escaped:a) <$> parseStr
evalExprs :: Parsed -> [Either Exp String]
evalExprs l = evalState (mapM stitch l) decls
where
strDecls = lefts l
decls = case partitionEithers $ map parseExp strDecls of
([], d) -> d
(errs, _) -> error $ intercalate "\n" errs
stitch :: Either a b -> S.State [c] (Either c b)
stitch (Right str) = return $ Right str
stitch (Left _) = do
(name:rest) <- get
put rest
return $ Left name
interpolateInto :: Exp -> String -> Exp
interpolateInto converter str =
foldl f (LitE (StringL "")) interleaved
where
parsed = either (error . show) id $ runParser parser 0 "inline" str
interleaved = evalExprs parsed
f expr bit = AppE (VarE 'mappend) expr `AppE` bitExpr
where
bitExpr = case bit of
Right str -> LitE (StringL str)
Left expr2 -> AppE converter expr2
is :: String -> Q Exp
is = return . interpolateInto (VarE 'id)
iq :: QuasiQuoter
iq = mqq { quoteExp = is }