{-# LANGUAGE OverloadedStrings, Trustworthy #-}
module Web.Simple.Templates.Language
(
compileTemplate, evaluate, evaluateAST
, valueToText, replaceVar
, module Web.Simple.Templates.Types
) where
import qualified Data.HashMap.Strict as H
import Data.Aeson
import qualified Data.Aeson.Key as K
import qualified Data.Aeson.KeyMap as K
import Data.Maybe
import Data.Scientific
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Vector as V
import qualified Data.Attoparsec.Text as A
import Web.Simple.Templates.Parser
import Web.Simple.Templates.Types
evaluateAST :: FunctionMap
-> Value
-> AST -> Value
evaluateAST :: FunctionMap -> Value -> AST -> Value
evaluateAST FunctionMap
fm Value
global AST
ast =
case AST
ast of
ASTRoot [AST]
asts -> forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (\Value
v AST
iast ->
let val :: Value
val = FunctionMap -> Value -> AST -> Value
evaluateAST FunctionMap
fm Value
global AST
iast
in Text -> Value
String forall a b. (a -> b) -> a -> b
$ Value -> Text
valueToText Value
v forall a. Semigroup a => a -> a -> a
<> Value -> Text
valueToText Value
val)
(Text -> Value
String Text
"") [AST]
asts
ASTLiteral Value
val -> Value
val
ASTFunc Text
ident [AST]
args ->
case forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
H.lookup Text
ident FunctionMap
fm of
Maybe Function
Nothing -> Value
Null
Just Function
func ->
let argVals :: [Value]
argVals = forall a b. (a -> b) -> [a] -> [b]
map (FunctionMap -> Value -> AST -> Value
evaluateAST FunctionMap
fm Value
global) [AST]
args
in Function -> [Value] -> Value
call Function
func [Value]
argVals
ASTVar Text
ident ->
if Text
ident forall a. Eq a => a -> a -> Bool
== Text
"@" then Value
global else
case Value
global of
Object Object
obj -> forall a. a -> Maybe a -> a
fromMaybe Value
Null forall a b. (a -> b) -> a -> b
$ forall v. Key -> KeyMap v -> Maybe v
K.lookup (Text -> Key
K.fromText Text
ident) Object
obj
Value
_ -> Value
Null
ASTIndex AST
objAst [Text]
idents ->
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (\Value
val Text
ident ->
case Value
val of
Object Object
obj -> forall a. a -> Maybe a -> a
fromMaybe Value
Null forall a b. (a -> b) -> a -> b
$ forall v. Key -> KeyMap v -> Maybe v
K.lookup (Text -> Key
K.fromText Text
ident) Object
obj
Value
_ -> Value
Null) (FunctionMap -> Value -> AST -> Value
evaluateAST FunctionMap
fm Value
global AST
objAst) [Text]
idents
ASTArray Vector AST
asts -> Array -> Value
Array forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> Vector a -> Vector b
V.map (FunctionMap -> Value -> AST -> Value
evaluateAST FunctionMap
fm Value
global) Vector AST
asts
ASTIf AST
cond AST
trueBranch Maybe AST
mfalseBranch ->
let condVal :: Value
condVal = FunctionMap -> Value -> AST -> Value
evaluateAST FunctionMap
fm Value
global AST
cond
falseBranch :: AST
falseBranch = forall a. a -> Maybe a -> a
fromMaybe (Value -> AST
ASTLiteral forall a b. (a -> b) -> a -> b
$ Text -> Value
String Text
"") Maybe AST
mfalseBranch
in if Value
condVal forall a. Eq a => a -> a -> Bool
== Value
Null Bool -> Bool -> Bool
|| Value
condVal forall a. Eq a => a -> a -> Bool
== Bool -> Value
Bool Bool
False then
FunctionMap -> Value -> AST -> Value
evaluateAST FunctionMap
fm Value
global AST
falseBranch
else FunctionMap -> Value -> AST -> Value
evaluateAST FunctionMap
fm Value
global AST
trueBranch
ASTFor Maybe Text
mkeyName Text
valName AST
lst AST
body Maybe AST
msep ->
FunctionMap
-> Value -> Maybe Text -> Text -> AST -> AST -> Maybe AST -> Value
astForLoop FunctionMap
fm Value
global Maybe Text
mkeyName Text
valName AST
lst AST
body Maybe AST
msep
astForLoop :: FunctionMap -> Value
-> Maybe Identifier -> Identifier
-> AST -> AST -> Maybe AST -> Value
astForLoop :: FunctionMap
-> Value -> Maybe Text -> Text -> AST -> AST -> Maybe AST -> Value
astForLoop FunctionMap
fm Value
global Maybe Text
mkeyName Text
valName AST
lst AST
body Maybe AST
msep =
case Value
val of
Value
Null -> Text -> Value
String Text
""
Bool Bool
False -> Text -> Value
String Text
""
Array Array
vec ->
Text -> Value
String forall a b. (a -> b) -> a -> b
$ forall {a}. ToJSON a => [(a, Value)] -> Text -> Text
go (forall a b. [a] -> [b] -> [(a, b)]
zip [Int
0..(forall a. Vector a -> Int
V.length Array
vec)] forall a b. (a -> b) -> a -> b
$ forall a. Vector a -> [a]
V.toList Array
vec) forall a. Monoid a => a
mempty
Object Object
obj -> Text -> Value
String forall a b. (a -> b) -> a -> b
$ forall {a}. ToJSON a => [(a, Value)] -> Text -> Text
go (forall v. KeyMap v -> [(Key, v)]
K.toList Object
obj) forall a. Monoid a => a
mempty
Value
v -> FunctionMap -> Value -> AST -> Value
evaluateAST FunctionMap
fm (Value -> Text -> Value -> Value
replaceVar Value
global Text
valName Value
v) AST
body
where sep :: Value
sep = forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Text -> Value
String Text
"") (FunctionMap -> Value -> AST -> Value
evaluateAST FunctionMap
fm Value
global) Maybe AST
msep
val :: Value
val = FunctionMap -> Value -> AST -> Value
evaluateAST FunctionMap
fm Value
global AST
lst
go :: [(a, Value)] -> Text -> Text
go [] Text
accm = Text
accm
go ((a
k,Value
v):[]) Text
accm =
let scope :: Value
scope = Value -> Text -> Value -> Value
replaceVar (forall a. ToJSON a => a -> Value
mreplaceKey a
k) Text
valName Value
v
nv :: Value
nv = FunctionMap -> Value -> AST -> Value
evaluateAST FunctionMap
fm Value
scope AST
body
in Text
accm forall a. Semigroup a => a -> a -> a
<> Value -> Text
valueToText Value
nv
go ((a
k,Value
v):(a, Value)
x1:[(a, Value)]
xs) Text
accm =
let scope :: Value
scope = Value -> Text -> Value -> Value
replaceVar (forall a. ToJSON a => a -> Value
mreplaceKey a
k) Text
valName Value
v
nv :: Value
nv = FunctionMap -> Value -> AST -> Value
evaluateAST FunctionMap
fm Value
scope AST
body
accmN :: Text
accmN =
Text
accm forall a. Semigroup a => a -> a -> a
<> Value -> Text
valueToText Value
nv forall a. Semigroup a => a -> a -> a
<> Value -> Text
valueToText Value
sep
in [(a, Value)] -> Text -> Text
go ((a, Value)
x1forall a. a -> [a] -> [a]
:[(a, Value)]
xs) Text
accmN
mreplaceKey :: ToJSON a => a -> Value
mreplaceKey :: forall a. ToJSON a => a -> Value
mreplaceKey a
v =
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Value
global (\Text
k -> Value -> Text -> Value -> Value
replaceVar Value
global Text
k forall a b. (a -> b) -> a -> b
$ forall a. ToJSON a => a -> Value
toJSON a
v) Maybe Text
mkeyName
replaceVar :: Value -> Identifier -> Value -> Value
replaceVar :: Value -> Text -> Value -> Value
replaceVar (Object Object
orig) Text
varName Value
newVal = Object -> Value
Object forall a b. (a -> b) -> a -> b
$ forall v. Key -> v -> KeyMap v -> KeyMap v
K.insert (Text -> Key
K.fromText Text
varName) Value
newVal Object
orig
replaceVar Value
_ Text
varName Value
newVal = [Pair] -> Value
object [(Text -> Key
K.fromText Text
varName) forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Value
newVal]
evaluate :: AST -> Template
evaluate :: AST -> Template
evaluate AST
ast = (FunctionMap -> Value -> Text) -> Template
Template forall a b. (a -> b) -> a -> b
$ \FunctionMap
fm Value
global ->
Value -> Text
valueToText forall a b. (a -> b) -> a -> b
$ FunctionMap -> Value -> AST -> Value
evaluateAST FunctionMap
fm Value
global AST
ast
valueToText :: Value -> Text
valueToText :: Value -> Text
valueToText Value
val =
case Value
val of
String Text
str -> Text
str
Number Scientific
n -> Scientific -> Text
fromScientific Scientific
n
Bool Bool
True -> Text
"True"
Bool Bool
False -> Text
"False"
Array Array
_ -> Text
"[array]"
Object Object
_ -> Text
"[object]"
Value
Null -> Text
""
fromScientific :: Scientific -> Text
fromScientific :: Scientific -> Text
fromScientific Scientific
n
| Int
e forall a. Ord a => a -> a -> Bool
< Int
0 = String -> Text
T.pack forall a b. (a -> b) -> a -> b
$ forall a. Show a => a -> String
show Scientific
n
| Bool
otherwise = String -> Text
T.pack forall a b. (a -> b) -> a -> b
$ forall a. Show a => a -> String
show forall a b. (a -> b) -> a -> b
$ Scientific -> Integer
coefficient Scientific
n forall a. Num a => a -> a -> a
* Integer
10 forall a b. (Num a, Integral b) => a -> b -> a
^ Int
e
where e :: Int
e = Scientific -> Int
base10Exponent Scientific
n
compileTemplate :: Text -> Either String Template
compileTemplate :: Text -> Either String Template
compileTemplate Text
tmpl = AST -> Template
evaluate forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$>
forall a. Parser a -> Text -> Either String a
A.parseOnly Parser AST
pAST Text
tmpl