packages feed

hakyllbars-1.0.0.0: src/Hakyllbars/Ast.hs

module Hakyllbars.Ast
  ( Template (..),
    Block (..),
    getBlockName,
    getBlockPos,
    ApplyBlock (..),
    DefaultBlock (..),
    Expression (..),
    getExpressionPos,
  )
where

import Data.Binary
import qualified Data.ByteString.Char8 as Char8
import Data.List.NonEmpty (NonEmpty)
import Data.Yaml (ToJSON (..), object, (.=))
import Data.Yaml.Pretty (defConfig, encodePretty)
import GHC.Generics (Generic)
import Hakyllbars.Common
import Text.Parsec hiding (getPosition)
import Text.Parsec.Pos

showJSON :: (ToJSON a) => a -> String
showJSON = Char8.unpack . encodePretty defConfig . toJSON

data Template = Template [Block] FilePath
  deriving (Generic)

instance Binary Template where
  get = Template <$> get <*> get

  put (Template blocks pos) = do
    put blocks
    put pos

instance Writable Template where
  write _ _ = return ()

instance ToJSON Template

instance Show Template where
  show = showJSON

data Block
  = TextBlock String SourcePos
  | ExpressionBlock Expression SourcePos
  | CommentBlock String SourcePos
  | ChromeBlock Expression [Block] SourcePos
  | AltBlock
      (NonEmpty ApplyBlock)
      (Maybe DefaultBlock)
      SourcePos
  deriving (Generic)

getBlockName :: Block -> String
getBlockName = \case
  TextBlock {} -> "TextBlock"
  ExpressionBlock {} -> "ExpressionBlock"
  CommentBlock {} -> "CommentBlock"
  ChromeBlock {} -> "ChromeBlock"
  AltBlock {} -> "AltBlock"

getBlockPos :: Block -> SourcePos
getBlockPos = \case
  TextBlock _ pos -> pos
  ExpressionBlock _ pos -> pos
  CommentBlock _ pos -> pos
  ChromeBlock _ _ pos -> pos
  AltBlock _ _ pos -> pos

getBlockTag :: Block -> Int
getBlockTag = \case
  TextBlock {} -> 1
  ExpressionBlock {} -> 2
  CommentBlock {} -> 3
  ChromeBlock {} -> 4
  AltBlock {} -> 5

instance Binary Block where
  get = do
    tag <- get :: Get Int
    f <- case tag of
      1 -> TextBlock <$> get
      2 -> ExpressionBlock <$> get
      3 -> CommentBlock <$> get
      4 -> ChromeBlock <$> get <*> get
      5 -> AltBlock <$> get <*> get
      _ -> error $ "Unrecognized block tag " ++ show tag
    binaryPos <- get :: Get BinaryPos
    return $ f (unBinaryPos binaryPos)

  put block = do
    put $ getBlockTag block
    case block of
      TextBlock text _ -> put text
      ExpressionBlock expression _ -> put expression
      CommentBlock text _ -> put text
      ChromeBlock expression blocks _ -> put expression >> put blocks
      AltBlock blocks default' _ -> put blocks >> put default'
    put $ BinaryPos (getBlockPos block)

instance ToJSON Block where
  toJSON = \case
    TextBlock text pos ->
      object
        [ "type" .= ("TextBlock" :: String),
          "text" .= text,
          "pos" .= show pos
        ]
    ExpressionBlock expression pos ->
      object
        [ "type" .= ("ExpressionBlock" :: String),
          "expression" .= expression,
          "pos" .= show pos
        ]
    CommentBlock text pos ->
      object
        [ "type" .= ("CommentBlock" :: String),
          "text" .= text,
          "pos" .= show pos
        ]
    ChromeBlock expression blocks _ ->
      object
        [ "type" .= ("ChromeBlock" :: String),
          "expression" .= expression,
          "blocks" .= blocks
        ]
    AltBlock blocks default' _ ->
      object
        [ "type" .= ("AltBlock" :: String),
          "blocks" .= blocks,
          "default" .= default'
        ]

instance Show Block where
  show = showJSON

newtype BinaryPos = BinaryPos SourcePos
  deriving (Show)

unBinaryPos :: BinaryPos -> SourcePos
unBinaryPos (BinaryPos pos) = pos

instance Binary BinaryPos where
  get = BinaryPos <$> (newPos <$> get <*> get <*> get)
  put binaryPos = do
    let pos = unBinaryPos binaryPos
    put $ sourceName pos
    put $ sourceLine pos
    put $ sourceColumn pos

data ApplyBlock = ApplyBlock Expression [Block] SourcePos
  deriving (Generic)

instance Binary ApplyBlock where
  get = do
    f <- ApplyBlock <$> get <*> get
    binaryPos <- get :: Get BinaryPos
    return $ f (unBinaryPos binaryPos)

  put (ApplyBlock expression blocks pos) = do
    put expression
    put blocks
    put $ BinaryPos pos

instance ToJSON ApplyBlock where
  toJSON (ApplyBlock expression blocks pos) =
    object
      [ "type" .= ("ApplyBlock" :: String),
        "expression" .= expression,
        "blocks" .= blocks,
        "pos" .= show pos
      ]

instance Show ApplyBlock where
  show = showJSON

data DefaultBlock = DefaultBlock [Block] SourcePos
  deriving (Generic)

instance Binary DefaultBlock where
  get = do
    f <- DefaultBlock <$> get
    binaryPos <- get :: Get BinaryPos
    return $ f (unBinaryPos binaryPos)

  put (DefaultBlock blocks pos) = do
    put blocks
    put $ BinaryPos pos

instance ToJSON DefaultBlock where
  toJSON (DefaultBlock blocks pos) =
    object
      [ "type" .= ("DefaultBlock" :: String),
        "blocks" .= blocks,
        "pos" .= show pos
      ]

instance Show DefaultBlock where
  show = showJSON

data Expression
  = NameExpression String SourcePos -- name, id
  | StringExpression String SourcePos -- "a string of text"
  | IntExpression Int SourcePos -- 123, 456, 2
  | DoubleExpression Double SourcePos -- 0.2, 1.45
  | BoolExpression Bool SourcePos -- true, false
  | ApplyExpression Expression Expression SourcePos -- fn arg
  | AccessExpression Expression Expression SourcePos -- target.field
  | FilterExpression Expression Expression SourcePos -- arg | fn
  | ContextExpression [(String, Expression)] SourcePos -- { name0: value0, name1: value1 }
  | ListExpression [Expression] SourcePos -- [a, b, c]
  deriving (Generic)

getExpressionPos :: Expression -> SourcePos
getExpressionPos = \case
  NameExpression _ pos -> pos
  StringExpression _ pos -> pos
  IntExpression _ pos -> pos
  DoubleExpression _ pos -> pos
  BoolExpression _ pos -> pos
  ApplyExpression _ _ pos -> pos
  AccessExpression _ _ pos -> pos
  FilterExpression _ _ pos -> pos
  ContextExpression _ pos -> pos
  ListExpression _ pos -> pos

getExpressionTag :: Expression -> Int
getExpressionTag = \case
  NameExpression {} -> 1
  StringExpression {} -> 2
  IntExpression {} -> 3
  DoubleExpression {} -> 4
  BoolExpression {} -> 5
  ApplyExpression {} -> 6
  AccessExpression {} -> 7
  FilterExpression {} -> 8
  ContextExpression {} -> 9
  ListExpression {} -> 10

instance Binary Expression where
  get = do
    tag <- get :: Get Int
    f <- case tag of
      1 -> NameExpression <$> get
      2 -> StringExpression <$> get
      3 -> IntExpression <$> get
      4 -> DoubleExpression <$> get
      5 -> BoolExpression <$> get
      6 -> ApplyExpression <$> get <*> get
      7 -> AccessExpression <$> get <*> get
      8 -> FilterExpression <$> get <*> get
      9 -> ContextExpression <$> get
      10 -> ListExpression <$> get
      _ -> error $ "Unrecognized expression tag " ++ show tag
    binaryPos <- get :: Get BinaryPos
    return $ f (unBinaryPos binaryPos)

  put expression = do
    put $ getExpressionTag expression
    case expression of
      NameExpression value _ -> put value
      StringExpression value _ -> put value
      IntExpression value _ -> put value
      DoubleExpression value _ -> put value
      BoolExpression value _ -> put value
      ApplyExpression fn arg _ -> put fn >> put arg
      AccessExpression target field _ -> put target >> put field
      FilterExpression arg fn _ -> put arg >> put fn
      ContextExpression pairs _ -> put pairs
      ListExpression values _ -> put values
    put $ BinaryPos (getExpressionPos expression)

instance ToJSON Expression where
  toJSON = \case
    NameExpression value pos ->
      object
        [ "type" .= ("NameExpression" :: String),
          "value" .= value,
          "pos" .= show pos
        ]
    StringExpression value pos ->
      object
        [ "type" .= ("StringExpression" :: String),
          "value" .= value,
          "pos" .= show pos
        ]
    IntExpression value pos ->
      object
        [ "type" .= ("IntExpression" :: String),
          "value" .= value,
          "pos" .= show pos
        ]
    DoubleExpression value pos ->
      object
        [ "type" .= ("DoubleExpression" :: String),
          "value" .= value,
          "pos" .= show pos
        ]
    BoolExpression value pos ->
      object
        [ "type" .= ("BoolExpression" :: String),
          "value" .= value,
          "pos" .= show pos
        ]
    ApplyExpression fn arg pos ->
      object
        [ "type" .= ("ApplyExpression" :: String),
          "fn" .= fn,
          "arg" .= arg,
          "pos" .= show pos
        ]
    AccessExpression target field pos ->
      object
        [ "type" .= ("AccessExpression" :: String),
          "target" .= target,
          "field" .= field,
          "pos" .= show pos
        ]
    FilterExpression arg fn pos ->
      object
        [ "type" .= ("FilterExpression" :: String),
          "arg" .= arg,
          "fn" .= fn,
          "pos" .= show pos
        ]
    ContextExpression pairs pos ->
      object
        [ "type" .= ("ContextExpression" :: String),
          "pairs" .= pairs,
          "pos" .= show pos
        ]
    ListExpression values pos ->
      object
        [ "type" .= ("ListExpression" :: String),
          "values" .= values,
          "pos" .= show pos
        ]

instance Show Expression where
  show = showJSON