ginger-0.8.0.0: src/Text/Ginger/Optimizer.hs
{-#LANGUAGE GeneralizedNewtypeDeriving #-}
{-#LANGUAGE FlexibleInstances #-}
{-#LANGUAGE MultiParamTypeClasses #-}
{-#LANGUAGE OverloadedStrings #-}
{-#LANGUAGE ScopedTypeVariables #-}
{-#LANGUAGE FlexibleContexts #-}
-- | A syntax tree optimizer
module Text.Ginger.Optimizer
( Optimizable (..) )
where
import Text.Ginger.AST
import Text.Ginger.GVal
import Text.Ginger.Run
import Control.Monad.Identity
import Data.Default
import Control.Monad.State (execState, evalState)
import Control.Monad.Writer (Writer, execWriter, tell)
import qualified Data.HashMap.Strict as HashMap
import Data.Maybe (fromMaybe)
import Control.Applicative
import Data.Text (Text)
import qualified Data.Aeson as JSON
import Data.Semigroup as Semigroup
class Optimizable a where
optimize :: a -> a
instance Optimizable (Template a) where
optimize = optimizeTemplate
instance Optimizable (Statement a) where
optimize = optimizeStatement
instance Optimizable (Block a) where
optimize = optimizeBlock
instance Optimizable (Macro a) where
optimize = optimizeMacro
instance Optimizable (Expression a) where
optimize = optimizeExpression
optimizeTemplate t =
t { templateBody = optimize $ templateBody t
, templateBlocks = optimize <$> templateBlocks t
, templateParent = optimize <$> templateParent t
}
-- = MultiS p [Statement a] -- ^ A sequence of multiple statements
-- | ScopedS p Statement a -- ^ Run wrapped statement in a local scope
-- | LiteralS p Html -- ^ Literal output (anything outside of any tag)
-- | InterpolationS p Expression a -- ^ {{ expression }}
-- | IfS p Expression a Statement a Statement a -- ^ {% if expression %}statement{% else %}statement{% endif %}
-- | ForS p (Maybe VarName) VarName Expression a Statement a -- ^ {% for index, varname in expression %}statement{% endfor %}
-- | SetVarS p VarName Expression a -- ^ {% set varname = expr %}
-- | DefMacroS p VarName Macro a -- ^ {% macro varname %}statements{% endmacro %}
-- | BlockRefS p VarName
-- | PreprocessedIncludeS p Template a -- ^ {% include "template" %}
-- | NullS p -- ^ The do-nothing statement (NOP)
optimizeStatement (MultiS p items) =
case optimizeStatementList items of
[] -> NullS p
[x] -> x
xs -> MultiS p xs
optimizeStatement (InterpolationS p e) =
InterpolationS p (optimize e)
optimizeStatement s@(IfS p c t f) =
let c' = optimize c
t' = optimize t
f' = optimize f
in case compileTimeEval c' of
Just gv -> case asBoolean gv of
True -> t
False -> f
_ -> s
optimizeStatement s = s
optimizeBlock (Block b) = Block $ optimize b
optimizeMacro (Macro args body) = Macro args (optimize body)
optimizeStatementList =
mergeLiterals .
cullNulls .
fmap optimize
cullNulls :: [Statement a] -> [Statement a]
cullNulls = filter (not . isNullS)
where
isNullS (NullS _) = True
isNullS _ = False
mergeLiterals :: [Statement a] -> [Statement a]
mergeLiterals [] = []
mergeLiterals [x] = [x]
mergeLiterals (x@(LiteralS p1 a):y@(LiteralS p2 b):xs) = mergeLiterals $ (LiteralS p1 $ a <> b):xs
mergeLiterals (x:xs) = x:mergeLiterals xs
-- data Expression a
-- = StringLiteralE p Text -- ^ String literal expression: "foobar"
-- | NumberLiteralE p Scientific -- ^ Numeric literal expression: 123.4
-- | BoolLiteralE p Bool -- ^ Boolean literal expression: true
-- | NullLiteralE p -- ^ Literal null
-- | VarE p VarName -- ^ Variable reference: foobar
-- | ListE p [Expression a] -- ^ List construct: [ expr, expr, expr ]
-- | ObjectE p [(Expression a, Expression a)] -- ^ Object construct: { expr: expr, expr: expr, ... }
-- | MemberLookupE p Expression a Expression a -- ^ foo[bar] (also dot access)
-- | CallE p Expression a [(Maybe Text, Expression a)] -- ^ foo(bar=baz, quux)
-- | LambdaE p [Text] Expression a -- ^ (foo, bar) -> expr
-- | TernaryE p Expression a Expression a Expression a -- ^ expr ? expr : expr
-- deriving (Show)
data Purity = Pure | Impure
deriving (Show, Eq, Enum, Read, Ord, Bounded)
bothPure :: Purity -> Purity -> Purity
bothPure Pure Pure = Pure
bothPure _ _ = Impure
instance Semigroup.Semigroup Purity where
(<>) = bothPure
instance Monoid Purity where
mempty = Pure
mappend = (<>)
pureExpression :: Expression a -> Purity
pureExpression (StringLiteralE p _) = Pure
pureExpression (NumberLiteralE p _) = Pure
pureExpression (NullLiteralE p) = Pure
pureExpression (ListE p items) = mconcat . map pureExpression $ items
pureExpression (ObjectE p pairs) =
mconcat [ bothPure (pureExpression k) (pureExpression v)
| (k, v) <- pairs
]
pureExpression (LambdaE _ args body) = pureExpression body
pureExpression (TernaryE _ cond yes no) =
pureExpression cond <> pureExpression yes <> pureExpression no
pureExpression (MemberLookupE _ k v) =
pureExpression k <> pureExpression v
pureExpression (CallE _ (VarE _ name) args) =
pureFunction name <> mconcat (map (pureExpression . snd) args)
pureExpression _ = Impure
pureFunction name
| name `elem` pureFunctionNames = Pure
| otherwise = Impure
pureFunctionNames =
[ "raw"
, "abs"
, "any"
, "all"
, "capitalize"
, "ceil"
, "center"
, "concat"
, "contains"
, "default"
, "dictsort"
, "difference"
, "e"
, "equals"
, "escape"
, "filesizeformat"
, "filter"
, "floor"
, "format"
, "greater"
, "greaterEquals"
, "int"
, "int_ratio"
, "iterable"
, "length"
, "less"
, "lessEquals"
, "modulo"
, "nequals"
, "num"
, "product"
, "ratio"
, "replace"
, "round"
, "show"
, "slice"
, "sort"
, "str"
, "sum"
, "truncate"
, "urlencode"
]
optimizeExpression :: Expression a -> Expression a
optimizeExpression = preEvalExpression . expandConstExpressions . optimizeSubexpressions
preEvalExpression :: Expression a -> Expression a
preEvalExpression e = fromMaybe e $ do
compileTimeEval e >>= gvalToExpression (annotation e)
gvalToExpression :: forall a m
. a -> GVal m -> Maybe (Expression a)
gvalToExpression p g =
(jsonLiteral =<< asJSON g) <|>
(ObjectE p <$> (recurseDict =<< asDictItems g)) <|>
(ListE p <$> (mapM (gvalToExpression p) =<< asList g))
where
jsonLiteral :: JSON.Value -> Maybe (Expression a)
jsonLiteral (JSON.Bool b) = Just (BoolLiteralE p b)
jsonLiteral (JSON.String s) = Just (StringLiteralE p s)
jsonLiteral (JSON.Null) = Just (NullLiteralE p)
jsonLiteral (JSON.Number n) = Just (NumberLiteralE p n)
jsonLiteral _ = Nothing
recurseDict :: [(Text, GVal m)] -> Maybe [(Expression a, Expression a)]
recurseDict = mapM $ \(key, val) -> do
let key' = StringLiteralE p key
val' <- gvalToExpression p val
return (key', val')
expandConstExpressions :: Expression a -> Expression a
expandConstExpressions e@(TernaryE p c t f) =
case compileTimeEval c of
Just gv -> case asBoolean gv of
True -> optimizeExpression t
False -> optimizeExpression f
_ -> e
expandConstExpressions e = e
optimizeSubexpressions (ListE p xs) = ListE p (map optimize xs)
optimizeSubexpressions (ObjectE p xs) = ObjectE p [ (optimize k, optimize v) | (k, v) <- xs ]
optimizeSubexpressions (MemberLookupE p k m) = MemberLookupE p (optimize k) (optimize m)
optimizeSubexpressions (CallE p f args) = CallE p (optimize f) [(n, optimize v) | (n, v) <- args]
optimizeSubexpressions (LambdaE p args body) = LambdaE p args (optimize body)
optimizeSubexpressions (TernaryE p c t f) = TernaryE p (optimize c) (optimize t) (optimize f)
optimizeSubexpressions e = e
isConstExpression :: Expression a -> Bool
isConstExpression (StringLiteralE p _) = True
isConstExpression (BoolLiteralE p _) = True
isConstExpression (NullLiteralE p) = True
isConstExpression (ListE p xs) = all isConstExpression xs
isConstExpression (ObjectE p xs) = all (\(k,v) -> isConstExpression k && isConstExpression v) xs
isConstExpression (MemberLookupE p k m) = isConstExpression k && isConstExpression m
isConstExpression e = False
compileTimeEval :: Expression p -> Maybe (GVal Identity)
compileTimeEval (StringLiteralE p s) = Just . toGVal $ s
compileTimeEval (NumberLiteralE p n) = Just . toGVal $ n
compileTimeEval (BoolLiteralE p b) = Just . toGVal $ b
compileTimeEval (NullLiteralE p) = Just def
compileTimeEval e = case pureExpression e of
Pure -> do
let tpl =
-- We're erasing source code positions here,
-- because we don't have any use for them anyway.
Template
(InterpolationS () (fmap (const ()) e))
HashMap.empty
Nothing
Just . toGVal . runCT $ tpl
Impure -> Nothing
newtype Collected = Collected [GVal Identity]
deriving (Semigroup.Semigroup, Monoid)
instance ToGVal m Collected where
toGVal = collectedToGVal
collectedToGVal :: Collected -> GVal m
collectedToGVal (Collected []) = def
collectedToGVal (Collected (x:_)) = marshalGVal x
runCT :: Template () -> Collected
runCT = runGinger ctContext
ctContext :: GingerContext () (Writer Collected) Collected
ctContext = makeContext' ctLookup ctEncode Nothing
ctLookup :: VarName -> GVal m
ctLookup = const def
ctEncode :: GVal m -> Collected
ctEncode g = Collected [marshalGVal g]