packages feed

fortran-src-extras-0.3.1: src/Language/Fortran/Extras/JSON/Helpers.hs

{-# LANGUAGE OverloadedStrings #-}

{- | Helpers for defining Aeson instances for fortran-src types.

To work around dependency awkwardness, we have to write an unusual concrete
context @'ToJSON' 'SrcSpan'@. It seems to work fine.
-}

module Language.Fortran.Extras.JSON.Helpers
  ( toJSONAnnoMerge
  , toJSONAnnoTaggedObj
  , jcProd, jcProdDrop
  , jcSum, jcSumDrop
  , jcEnum, jcEnumDrop
  , tja, gtj, gte
  ) where

import Data.Aeson hiding ( Value )
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Types as Aeson
import Language.Fortran.Util.Position ( SrcSpan )
import Data.Text ( Text )

import GHC.Generics ( Generic, Rep )

-- | Shortcut for writing a 'toJSON' definition for a fortran-src AST node type,
--   intended to be used for sum types.
--
-- Flat/concise version which merges all keys into the same object.
toJSONAnnoMerge
    :: (ToJSON a, ToJSON SrcSpan)
    => Text -> a -> SrcSpan -> [Aeson.Pair] -> Aeson.Value
toJSONAnnoMerge t a ss m = object $
  [ "anno"     .= a
  , "span"     .= ss
  , "tag"      .= t ] <> m

-- | Shortcut for writing a 'toJSON' definition for a fortran-src AST node type,
--   intended to be used for sum types.
--
-- "Safe" version which approximates Aeson's default 'Data.Aeson.TaggedObject'
-- sum encoding strategy, but with two extra fields extracted out.
toJSONAnnoTaggedObj
    :: (ToJSON a, ToJSON SrcSpan)
    => Text -> a -> SrcSpan -> [Aeson.Pair] -> Aeson.Value
toJSONAnnoTaggedObj t a ss m = object
  [ "anno"     .= a
  , "span"     .= ss
  , "tag"      .= t
  , "contents" .= object m ]

-- | Shortcut for selected fortran-src AST node type 'toJSON' strategy.
tja :: (ToJSON a, ToJSON SrcSpan)
    => Text -> a -> SrcSpan -> [Aeson.Pair] -> Aeson.Value
tja = toJSONAnnoMerge

-- | Base Aeson generic deriver config for product types.
jcProd :: (String -> String) -> Aeson.Options
jcProd f = Aeson.defaultOptions
  { Aeson.rejectUnknownFields = True
  , Aeson.fieldLabelModifier = Aeson.camelTo2 '_' . f
  }

jcProdDrop :: String -> Aeson.Options
jcProdDrop x = jcProd (drop (length x))

-- | Base Aeson generic deriver config for sum types.
jcSum :: (String -> String) -> String -> String -> Aeson.Options
jcSum f tag contents = Aeson.defaultOptions
  { Aeson.rejectUnknownFields = True
  , Aeson.constructorTagModifier = Aeson.camelTo2 '_' . f
  , Aeson.sumEncoding = Aeson.TaggedObject
    { Aeson.tagFieldName = tag
    , Aeson.contentsFieldName = contents
    }
  }

jcSumDrop :: String -> Aeson.Options
jcSumDrop x = jcSum (drop (length x)) "tag" "contents"

-- | Base Aeson generic deriver config for enum types (no fields in any cons).
jcEnum :: (String -> String) -> Aeson.Options
jcEnum f = Aeson.defaultOptions
  { Aeson.rejectUnknownFields = True
  , Aeson.constructorTagModifier = Aeson.camelTo2 '_' . f
  }

jcEnumDrop :: String -> Aeson.Options
jcEnumDrop = jcEnum . drop . length

-- | Shortcut for common function 'genericToJSON'
gtj :: (Generic a, Aeson.GToJSON' Aeson.Value Aeson.Zero (Rep a)) => Aeson.Options -> a -> Aeson.Value
gtj = genericToJSON

-- | Shortcut for common function 'genericToEncoding'
gte :: (Generic a, Aeson.GToJSON' Aeson.Encoding Aeson.Zero (Rep a)) => Aeson.Options -> a -> Aeson.Encoding
gte = genericToEncoding