{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} module Text.HSmarty.Parser.Smarty where import Text.HSmarty.Parser.Util import Text.HSmarty.Types import Control.Applicative import Data.Attoparsec.Text import Data.Char import qualified Data.Aeson as A import qualified Data.Attoparsec.Expr as E import qualified Data.Text as T parseSmarty :: MonadFail m => FilePath -> T.Text -> m Smarty parseSmarty fp t = either (\err -> fail ("Failed to parse " <> fp <> ": " <> err)) mk $ parseOnly pRoot t where mk exprs = return $ Smarty fp exprs pRoot :: Parser [SmartyStmt] pRoot = stripSpace (many1 pStmt) <* endOfInput pStmt :: Parser SmartyStmt pStmt = SmartyComment <$> pComment <|> SmartyText <$> pLiteral <|> SmartyIf <$> pIf <|> SmartyForeach <$> pForeach <|> SmartyCapture <$> pCapture <|> SmartyScope <$> pScope <|> SmartyFun <$> pFunDef <|> braced (char '{') (char '}') (SmartyPrint <$> pExpr <*> many pPrintDirective) <|> SmartyText <$> takeWhile1 (/='{') pPrintDirective :: Parser PrintDirective pPrintDirective = char '|' *> pName pExpr :: Parser Expr pExpr = E.buildExpressionParser opTable pValExpr pValExpr :: Parser Expr pValExpr = braced (char '(') (char ')') pExpr <|> ExprVar <$> pVar <|> ExprLit <$> pLit <|> ExprFun <$> pFunCall pLit :: Parser A.Value pLit = A.String <$> stringP <|> A.Bool <$> boolP <|> A.Number <$> scientific pVar :: Parser Variable pVar = Variable <$> (char '$' *> pName) <*> many pVarPath <*> optional pVarIndex <*> optional pVarProp where pVarProp = char '@' *> pName pVarIndex = braced (char '[') (char ']') pExpr pVarPath = char '.' *> pName pName :: Parser T.Text pName = identP (\c -> isAlpha c || c == '_') (\c -> isAlphaNum c || c == '_') pLiteral :: Parser T.Text pLiteral = (pOpen "literal") *> (T.pack <$> manyTill anyChar (pClose "literal")) pComment :: Parser T.Text pComment = (string "{*") *> (T.pack <$> manyTill anyChar (string "*}")) pFunCall :: Parser FunctionCall pFunCall = FunctionCall <$> pName <*> many1 pArg where pArg = (,) <$> (space_ *> pName <* (stripSpace $ char '=')) <*> pExpr pOpen :: T.Text -> Parser T.Text pOpen t = string $ T.concat [ "{", t, "}" ] pOpenExpr :: T.Text -> Parser Expr pOpenExpr t = (string (T.concat [ "{", t]) *> space_) *> pExpr <* char '}' pClose :: T.Text -> Parser T.Text pClose t = string $ T.concat [ "{/", t, "}" ] pLet :: Parser Let pLet = Let <$> (string "{$" *> pName <* char '=') <*> pExpr <* char '}' pScope :: Parser Scope pScope = Scope <$> (pOpen "scope" *> many pStmt <* pClose "scope") pCapture :: Parser Capture pCapture = Capture <$> ((string "{capture name=" *> optSpace_) *> stringP) <*> optional (optSpace_ *> string "assign=" *> pName) <*> (optSpace_ *> char '}' *> many pStmt <* pClose "capture") pFunDef :: Parser FunctionDef pFunDef = FunctionDef <$> ((string "{function name=" *> space_) *> stringP) <*> (many pArg <* char '}') <*> (many pStmt <* pClose "function") where pArg = (,) <$> (space_ *> pName <* stripSpace (char '=')) <*> pExpr pIf :: Parser If pIf = If <$> pBranches <*> optional (pOpen "else" *> many pStmt) <* pClose "if" where pBranch ty = (,) <$> pOpenExpr ty <*> many pStmt pBranches = (:) <$> pBranch "if" <*> (many $ pBranch "elseif") pForeach :: Parser Foreach pForeach = Foreach <$> ((string "{foreach" *> space_) *> pExpr <* (space_ <* (string "as") <* space_)) <*> optional (char '$' *> pName <* (stripSpace $ string "=>")) <*> ((stripSpace (char '$' *> pName)) <* char '}') <*> many pStmt <*> optional (pOpen "foreachelse" *> many pStmt) <* pClose "foreach" opTable :: [[E.Operator T.Text Expr]] opTable = [ [ prefix (string "not" *> space_) $ arg1 BinNot , prefix (char '!') $ arg1 BinNot ] , [ sym "*" (arg2 BinMul) E.AssocLeft , sym "/" (arg2 BinDiv) E.AssocLeft ] , [ sym "+" (arg2 BinPlus) E.AssocLeft , sym "-" (arg2 BinMinus) E.AssocLeft ] , [ sym "<" (arg2 BinSmaller) E.AssocNone , sym ">" (arg2 BinLarger) E.AssocNone , sym "<=" (arg2 BinSmallerEq) E.AssocNone , sym ">=" (arg2 BinLargerEq) E.AssocNone ] , [ sym "==" (arg2 BinEq) E.AssocNone , sym "!=" (arg2 (\x y -> BinNot $ ExprBin $ BinEq x y )) E.AssocNone ] , [ wsym "and" (arg2 BinAnd) E.AssocLeft , wsym "or" (arg2 BinOr) E.AssocLeft , sym "&&" (arg2 BinAnd) E.AssocLeft , sym "||" (arg2 BinOr) E.AssocLeft ] ] where arg1 fun x = ExprBin $ fun x arg2 fun x y = ExprBin $ fun x y binary op fun assoc = E.Infix (fun <$ op <* optSpace_) assoc prefix op fun = E.Prefix (fun <$ op <* optSpace_) sym s = binary (stripSpace $ string s) wsym w = binary (between optSpace_ space_ $ string w)