{-# LANGUAGE TemplateHaskell, FlexibleContexts, RankNTypes #-} module Text.Hako.Parsing ( parseTemplateFromString ) where import Text.Parsec import Language.Haskell.TH import Language.Haskell.Meta.Parse import Text.Parsec.String import Text.Hako.Html type HakoParser a = Stream s m Char => ParsecT s u m a -- | Hako's main parser, suitable as a quoteExpr. parseTemplateFromString :: String -> ExpQ parseTemplateFromString s = either (error . show) return $ parse template [] s data Template = Template [Dec] [Exp] tjoin :: Template -> Template -> Template tjoin (Template dl el) (Template dr er) = Template (dl ++ dr) (el ++ er) tpack :: Template -> Exp tpack (Template defs exps) = let body = if null exps then emptyLiteralExp else foldl1 expJoin exps in if null defs then body else LetE defs body template :: HakoParser Exp template = do tfs <- many templateFragment return $ tpack $ foldl1 tjoin tfs templateFragment :: HakoParser Template templateFragment = try templateDefFragment <|> try templateForFragment <|> templateExpFragment <|> templateLitFragment "template fragment" templateDefFragment :: HakoParser Template templateDefFragment = do defs <- blockDef return $ Template defs [] templateExpFragment :: HakoParser Template templateExpFragment = do exp <- haskellExpr return $ Template [] [exp] templateForFragment :: HakoParser Template templateForFragment = do exp <- forExpr return $ Template [] [exp] templateLitFragment :: HakoParser Template templateLitFragment = do exp <- literalText return $ Template [] [exp] emptyLiteralExp :: Exp emptyLiteralExp = AppE (ConE . mkName $ "Html") $ LitE $ StringL "" expJoin :: Exp -> Exp -> Exp expJoin a b = AppE (AppE (VarE . mkName $ "<++>") a) b expWrap :: Exp -> Exp expWrap a = AppE (VarE . mkName $ "toHtml") a blockDef :: HakoParser [Dec] blockDef = do string "{def" space leader <- manyTill anyChar $ char '=' inner <- template string "}" let decs = parseDecs $ leader ++ " = " ++ pprint inner case decs of Right d -> return d Left err -> error err forExpr :: HakoParser Exp forExpr = do string "{for" space iteree <- manyTill (try anyChar) $ char '-' string ">" space iter <- manyTill (try anyChar) $ char ':' inner <- template string "}" let expr = parseExp $ "foldl (<++>) (Html \"\") (map (\\" ++ iter ++ " -> " ++ pprint inner ++ ") (" ++ iteree ++ "))" case expr of Right e -> return e Left err -> error err literalText :: HakoParser Exp literalText = do str <- many1 $ noneOf "{}" return $ AppE (ConE . mkName $ "Html") $ LitE $ StringL str haskellExpr :: HakoParser Exp haskellExpr = do e <- haskellExpr' return $ expWrap e haskellExpr' :: HakoParser Exp haskellExpr' = do _ <- char '{' src <- haskellText _ <- char '}' either fail return $ parseExp src haskellText :: HakoParser String haskellText = do parts <- many1 haskellPart return $ concat parts bracedText :: HakoParser String bracedText = do char '{' inner <- haskellText char '}' return $ "{" ++ inner ++ "}" haskellPart :: HakoParser String haskellPart = quotedChar <|> quotedEscapedChar <|> quotedString <|> bracedText <|> haskellOther haskellOther :: HakoParser String haskellOther = many1 $ noneOf "\"'{}" quotedChar :: HakoParser String quotedChar = do char '\'' c <- noneOf "\\" char '\'' return ['\'', c, '\''] quotedEscapedChar :: HakoParser String quotedEscapedChar = do char '\'' char '\\' c <- anyChar char '\'' return ['\'', '\\', c, '\''] quotedString :: HakoParser String quotedString = do char '"' strs <- many quotedStringPart char '"' let str = concat strs return $ "\"" ++ str ++ "\"" where quotedStringPart = singleChar <|> escapedChar singleChar = do { c <- noneOf "\"\\"; return [c] } escapedChar = do { char '\\'; c <- anyChar; return ['\\',c] }