module Text.EDE.Internal.Eval where
import Control.Applicative
import Control.Monad
import Control.Monad.Reader
import Data.Aeson hiding (Result(..))
import Data.Foldable (foldlM)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as Map
import qualified Data.List.NonEmpty as NonEmpty
import Data.Monoid
import Data.Scientific (base10Exponent)
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.Buildable as Build
import Data.Text.Format (Format)
import Data.Text.Format.Params (Params)
import Data.Text.Lazy.Builder (Builder)
import Data.Text.Lazy.Builder.Scientific
import Text.EDE.Internal.HOAS
import Text.EDE.Internal.Types
import Text.Trifecta.Delta
data Env = Env
{ _templates :: HashMap Text Exp
, _quoted :: HashMap Text Binding
, _values :: HashMap Text Value
}
type Context = ReaderT Env Result
render :: HashMap Text Exp
-> HashMap Text Binding
-> Exp
-> HashMap Text Value
-> Result Builder
render ts fs e o = runReaderT (eval e >>= nf) (Env ts fs o)
where
nf (BVal v) = build (delta e) v
nf _ = lift $ Failure
"unable to evaluate partially applied template to normal form."
eval :: Exp -> Context Binding
eval (ELit _ l) = return (quote l)
eval (EVar _ v) = quote <$> variable v
eval (EFun d i) = do
q <- Map.lookup i <$> asks _quoted
maybe (throwError' d "binding {} doesn't exist." [i])
return
q
eval (EApp d a b) = do
x <- eval a
y <- eval b
binding d x y
eval (ELet _ k rhs bdy) = do
q <- eval rhs
v <- lift (unquote q)
bind (Map.insert k v) (eval bdy)
eval (ECase d p ws) = go ws
where
go [] = return (quote (String mempty))
go ((a, e):as) =
case a of
PWild -> eval e
PVar v -> eval (EVar d v) >>= cond e as
PLit l -> eval (ELit d l) >>= cond e as
cond e as y@(BVal Bool{}) = do
x <- predicate p
if x == y then eval e else go as
cond e as y@BVal{} = do
x <- eval p
if x == y then eval e else go as
cond _ as _ = go as
eval (ELoop _ i v bdy) = eval v >>= lift . unquote >>= loop
where
d = delta bdy
loop :: Collection -> Context Binding
loop (Col l xs) = snd <$> foldlM iter (1, quote (String mempty)) xs
where
iter (n, p) x = do
shadowed n
q <- bind (Map.insert i (context n x)) (eval bdy)
r <- binding d p q
return (n + 1, r)
shadowed n = do
m <- asks _values
maybe (return ())
(\x -> throwError' d "binding {} shadows variable {} :: {}, {}"
[Text.unpack i, show x, typeOf x, show n])
(Map.lookup i m)
context n (k, x) = object $
[ "value" .= x
, "length" .= l
, "index" .= n
, "index0" .= (n 1)
, "remainder" .= (l n)
, "remainder0" .= (l n 1)
, "first" .= (n == 1)
, "last" .= (n == l)
, "odd" .= (n `mod` 2 == 1)
, "even" .= (n `mod` 2 == 0)
] ++ key k
key (Just k) = ["key" .= k]
key Nothing = []
eval (EIncl d i) = do
ts <- asks _templates
case Map.lookup i ts of
Just e -> eval e
Nothing -> throwError' d "template {} is not in scope: [{}]"
[i, Text.intercalate "," $ Map.keys ts]
bind :: (Object -> Object) -> Context a -> Context a
bind f = withReaderT (\x -> x { _values = f (_values x) })
variable :: Var -> Context Value
variable (Var is) = asks _values >>= go (NonEmpty.toList is) [] . Object
where
go [] _ v = return v
go (k:ks) r v = do
m <- nest v
maybe (throwError' undefined "binding {} doesn't exist." [fmt (k:r)])
(go ks (k:r))
(Map.lookup k m)
where
nest :: Value -> Context Object
nest (Object o) = return o
nest x =
throwError' undefined "variable {} :: {} doesn't supported nested accessors."
[fmt (k:r), typeOf x]
fmt = Text.unpack . Text.intercalate "."
predicate :: Exp -> Context Binding
predicate x = do
r <- runReaderT (eval x) <$> ask
lift $ case r of
Success q
| BVal Bool{} <- q -> Success q
Success q
| BVal Null <- q -> Success (quote False)
Success _ -> Success (quote True)
Failure _
| EVar{} <- x -> Success (quote False)
Failure e -> Failure e
binding :: Delta -> Binding -> Binding -> Context Binding
binding d x y =
case (x, y) of
(BVal l, BVal r) -> quote <$> liftM2 (<>) (build d l) (build d r)
_ -> lift (qapply x y)
build :: Delta -> Value -> Context Builder
build _ Null = return mempty
build _ (String t) = return (Build.build t)
build _ (Bool True) = return "true"
build _ (Bool False) = return "false"
build _ (Number n)
| base10Exponent n == 0 = return (formatScientificBuilder Fixed (Just 0) n)
| otherwise = return (scientificBuilder n)
build d x =
throwError' d "unable to render literal {}\n{}" [typeOf x, show x]
throwError' :: Params ps => Delta -> Format -> ps -> Context a
throwError' _ f = lift . throwError f