module Text.Strapped.Parser
( parseTemplate
) where
import Control.Applicative ((<*>))
import Control.Monad
import Data.Monoid
import qualified Data.Text.Lazy as T
import Blaze.ByteString.Builder as B
import Blaze.ByteString.Builder.Char.Utf8 as B
import Text.Parsec
import Text.Parsec.String
import qualified Text.Parsec.Token as P
import Text.Parsec.Language (emptyDef)
import Text.Strapped.Types
tagStart :: GenParser Char st String
tagStart = string "{$"
tagEnd :: GenParser Char st String
tagEnd = string "$}"
wordString :: GenParser Char st String
wordString = many1 $ oneOf "_" <|> alphaNum
pathString :: GenParser Char st String
pathString = many1 $ oneOf "_./" <|> alphaNum
peekChar :: Char -> GenParser Char st ()
peekChar = void . try . lookAhead . char
tryTag :: GenParser Char st a -> GenParser Char st ()
tryTag = void . try . tag
tag :: GenParser Char st a -> GenParser Char st a
tag p = between (tagStart >> spaces) (spaces >> tagEnd) p <?> "Tag"
parseFloat :: GenParser Char st Double
parseFloat = do sign <- option 1 (do s <- oneOf "+-"
return $ if s == '-' then1.0 else 1.0)
x <- P.float $ P.makeTokenParser emptyDef
return $ sign * x
parseInt :: GenParser Char st Integer
parseInt = do sign <- option 1 (do s <- oneOf "+-"
return $ if s == '-' then1 else 1)
x <- P.integer $ P.makeTokenParser emptyDef
return $ sign * x
parseContent :: GenParser Char st a -> GenParser Char st [ParsedPiece]
parseContent end = do
decls <- many (try $ spaces >> parseDecl)
spaces
extends <- optionMaybe (try $ spaces >> parseInherits)
case (extends) of
Just (e, epos) -> do
includes <- manyTill parseIsIgnoreSpace end
return $ (decls) ++ [ParsedPiece (Inherits e includes) epos]
_ -> do
ps <- manyTill parsePiece end
return $ decls ++ ps
where parseIsIgnoreSpace = do {spaces; b <- parseIsBlock; spaces; return b}
parseBlock :: GenParser Char st ParsedPiece
parseBlock = do
pos <- getPosition
blockName <- tag (string "block" >> spaces >> wordString) <?> "Block tag"
blockContent <- parseContent (tryTag $ string "endblock")
return $ ParsedPiece (BlockPiece blockName blockContent) pos
parseRaw :: GenParser Char st ParsedPiece
parseRaw = do
pos <- getPosition
tag (string "raw") <?> "Raw tag"
c <- anyChar
s <- manyTill anyChar (tryTag (string "endraw"))
return $ ParsedPiece (StaticPiece (B.fromString $ c:s)) pos
parseComment :: GenParser Char st ParsedPiece
parseComment = do
pos <- getPosition
tag (string "comment") <?> "Comment tag"
c <- anyChar
s <- manyTill anyChar (tryTag (string "endcomment"))
return $ ParsedPiece (StaticPiece mempty) pos
parseFor :: GenParser Char st ParsedPiece
parseFor = do
pos <- getPosition
(newVarName, exp) <- (tagStart >> spaces >> string "for" >> argParser) <?> "For tag"
blockContent <- parseContent (tryTag $ string "endfor")
return $ ParsedPiece (ForPiece newVarName exp blockContent) pos
where argParser = do
spaces
v <- wordString
spaces >> (string "in") >> spaces
func <- parseExpression (try $ spaces >> tagEnd)
return (v, func)
parseDecl :: GenParser Char st ParsedPiece
parseDecl = do {spaces; decl <- parserDecl; spaces; return decl} <?> "Let tag"
where parserDecl = do
pos <- getPosition
tagStart >> spaces
string "let" >> spaces
varName <- wordString
spaces >> string "=" >> spaces
func <- parseExpression (try $ spaces >> tagEnd)
return $ ParsedPiece (Decl varName func) pos
parseIsBlock = do
blockName <- tag (string "isblock" >> spaces >> wordString) <?> "Isblock tag"
blockContent <- parseContent (tryTag $ string "endisblock")
return (blockName, blockContent)
parseInclude :: GenParser Char st ParsedPiece
parseInclude = do
pos <- getPosition
tag (parserInclude pos) <?> "Include tag"
where parserInclude pos = do
string "include" >> spaces
includeName <- pathString
return $ ParsedPiece (Include includeName) pos
parseInherits = do {pos <- getPosition; mtag <- tag (string "inherits" >> spaces >> pathString); return (mtag, pos)} <?> "Inherits tag"
parseFunc :: GenParser Char st ParsedPiece
parseFunc = parserFunc <?> "Call tag"
where parserFunc = do
pos <- getPosition
string "${" >> spaces
exp <- parseExpression (try $ spaces >> string "}")
return $ ParsedPiece (FuncPiece exp) pos
parseExpression :: GenParser Char st a -> GenParser Char st ParsedExpression
parseExpression end = manyPart <?> "Expression"
where parseGroup = try parens <|> parseAtomic
parseAtomic = do
pos <- getPosition
exp <- try parseList <|>
try (parseString '\"') <|>
try (parseString '\'') <|>
try (parseFloat >>= (return . FloatExpression)) <|>
try (parseInt >>= (return . IntegerExpression)) <|>
literal
return $ ParsedExpression exp pos
parens = (string "(" >> spaces) >> parseExpression (try $ spaces >> string ")")
parseList = between (string "[" >> spaces)
(spaces >> string "]")
(sepBy (spaces >> parseGroup) (string ","))
>>= (return . ListExpression)
manyPart = do
pos <- getPosition
pieces <- manyTill (spaces >> parseGroup) end
return $ ParsedExpression (Multipart pieces) pos
parseString esc = parseStringContents esc >>= (return . StringExpression)
literal = wordString >>= (return . LookupExpression)
parseStringContents :: Char -> GenParser Char st String
parseStringContents esc = between (char esc) (char esc) (many chars)
where chars = (try escaped) <|> noneOf [esc]
escaped = char '\\' >> choice (zipWith escapedChar codes replacements)
escapedChar code replacement = char code >> return replacement
codes = ['b', 'n', 'f', 'r', 't', '\\', '\"', '\'', '/']
replacements = ['\b', '\n', '\f', '\r', '\t', '\\', '\"', '\'', '/']
parseStatic :: GenParser Char st ParsedPiece
parseStatic = do
pos <- getPosition
c <- anyChar
s <- manyTill anyChar (peekChar '{' <|> peekChar '$' <|> eof)
return $ ParsedPiece (StaticPiece (B.fromString $ c:s)) pos
parseNonStatic :: GenParser Char st ParsedPiece
parseNonStatic = try parseComment
<|> try parseRaw
<|> try parseBlock
<|> try parseFor
<|> try parseInclude
<|> parseFunc
parsePiece :: GenParser Char st ParsedPiece
parsePiece = (try parseNonStatic <|> parseStatic)
parsePieces :: GenParser Char st [ParsedPiece]
parsePieces = parseContent eof
parseToTemplate :: GenParser Char st Template
parseToTemplate = (parseContent eof) >>= (return . Template)
parseTemplate :: String -> String -> Either ParseError Template
parseTemplate s tmplN = parse parseToTemplate tmplN s