packages feed

toml-parser-2.0.0.0: src/Toml/Semantics/Types.hs

{-# Language PatternSynonyms, DeriveTraversable, TypeFamilies #-}
{-|
Module      : Toml.Semantics.Types
Description : Semantic TOML values
Copyright   : (c) Eric Mertens, 2023
License     : ISC
Maintainer  : emertens@gmail.com

This module provides the type for the semantics of a TOML file.
All dotted keys are resolved in this representation. Each table
is a Map with a single level of keys.

Values are parameterized over an annotation type to allow values
to be attributed to a file location. When values are constructed
programmatically, there might not be any interesting annotations.
In this case a trivial @()@ unit annotation can be used. The
'Value' type-synonym and related pattern synonyms can make using
this case more convenient.

-}
module Toml.Semantics.Types (
    -- * Unlocated value synonyms
    Value,
    Table,

    -- * Annotated values
    Value'(..,
        Integer, Double, Text, Bool,
        ZonedTime, Day, LocalTime, TimeOfDay,
        List, Table),
    Table'(..),

    -- * Utilities
    forgetValueAnns,
    forgetTableAnns,
    valueAnn,
    valueType,
    ) where

import Data.Map (Map)
import Data.String (IsString(fromString))
import Data.Text (Text)
import Data.Time (Day, LocalTime, TimeOfDay, ZonedTime(zonedTimeToLocalTime, zonedTimeZone), timeZoneMinutes)

pattern Integer :: Integer -> Value
pattern Integer x <- Integer' _ x
    where Integer x = Integer' () x

pattern Double :: Double -> Value
pattern Double x <- Double' _ x
    where Double x = Double' () x

pattern List :: [Value] -> Value
pattern List x <- List' _ x
    where List x = List' () x

pattern Table :: Table -> Value
pattern Table x <- Table' _ x
    where Table x = Table' () x

pattern Bool :: Bool -> Value
pattern Bool x <- Bool' _ x
    where Bool x = Bool' () x

pattern Text :: Text -> Value
pattern Text x <- Text' _ x
    where Text x = Text' () x

pattern TimeOfDay :: TimeOfDay -> Value
pattern TimeOfDay x <- TimeOfDay' _ x
    where TimeOfDay x = TimeOfDay' () x

pattern ZonedTime :: ZonedTime -> Value
pattern ZonedTime x <- ZonedTime' _ x
    where ZonedTime x = ZonedTime' () x

pattern LocalTime :: LocalTime -> Value
pattern LocalTime x <- LocalTime' _ x
    where LocalTime x = LocalTime' () x

pattern Day :: Day -> Value
pattern Day x <- Day' _ x
    where Day x = Day' () x

{-# Complete List, Table, Text, Bool, Integer, Double, Day, LocalTime, ZonedTime, TimeOfDay #-}

-- | Semantic TOML value with all table assignments resolved.
data Value' a
    = Integer'   a Integer
    | Double'    a Double
    | List'      a [Value' a]
    | Table'     a (Table' a)
    | Bool'      a Bool
    | Text'      a Text
    | TimeOfDay' a TimeOfDay
    | ZonedTime' a ZonedTime
    | LocalTime' a LocalTime
    | Day'       a Day
    deriving (
        Show        {- ^ Default instance -},
        Read        {- ^ Default instance -},
        Functor     {- ^ Derived          -},
        Foldable    {- ^ Derived          -},
        Traversable {- ^ Derived          -})

-- | Extract the top-level annotation from a value.
valueAnn :: Value' a -> a
valueAnn = \case
    Integer'   a _ -> a
    Double'    a _ -> a
    List'      a _ -> a
    Table'     a _ -> a
    Bool'      a _ -> a
    Text'      a _ -> a
    TimeOfDay' a _ -> a
    ZonedTime' a _ -> a
    LocalTime' a _ -> a
    Day'       a _ -> a

-- | String representation of the kind of value using TOML vocabulary
valueType :: Value' l -> String
valueType = \case
    Integer'   {} -> "integer"
    Double'    {} -> "float"
    List'      {} -> "array"
    Table'     {} -> "table"
    Bool'      {} -> "boolean"
    Text'      {} -> "string"
    TimeOfDay' {} -> "local time"
    LocalTime' {} -> "local date-time"
    Day'       {} -> "locate date"
    ZonedTime' {} -> "offset date-time"

-- | A table with annotated keys and values.
newtype Table' a = MkTable (Map Text (a, Value' a))
    deriving (
        Show        {- ^ Default instance -},
        Read        {- ^ Default instance -},
        Eq          {- ^ Default instance -},
        Functor     {- ^ Derived          -},
        Foldable    {- ^ Derived          -},
        Traversable {- ^ Derived          -})

-- | A 'Table'' with trivial annotations
type Table = Table' ()

-- | A 'Value'' with trivial annotations
type Value = Value' ()

-- | Replaces annotations with a unit.
forgetTableAnns :: Table' a -> Table
forgetTableAnns (MkTable t) = MkTable (fmap (\(_, v) -> ((), forgetValueAnns v)) t)

-- | Replaces annotations with a unit.
forgetValueAnns :: Value' a -> Value
forgetValueAnns =
    \case
        Integer'   _ x -> Integer   x
        Double'    _ x -> Double    x
        List'      _ x -> List      (map forgetValueAnns x)
        Table'     _ x -> Table     (forgetTableAnns x)
        Bool'      _ x -> Bool      x
        Text'      _ x -> Text      x
        TimeOfDay' _ x -> TimeOfDay x
        ZonedTime' _ x -> ZonedTime x
        LocalTime' _ x -> LocalTime x
        Day'       _ x -> Day       x

-- | Nearly default instance except 'ZonedTime' doesn't have an
-- 'Eq' instance. 'ZonedTime' values are equal if their times and
-- time-zones are both equal.
instance Eq a => Eq (Value' a) where
    Integer'   a x == Integer'   b y = a == b && x == y
    Double'    a x == Double'    b y = a == b && x == y
    List'      a x == List'      b y = a == b && x == y
    Table'     a x == Table'     b y = a == b && x == y
    Bool'      a x == Bool'      b y = a == b && x == y
    Text'      a x == Text'      b y = a == b && x == y
    TimeOfDay' a x == TimeOfDay' b y = a == b && x == y
    LocalTime' a x == LocalTime' b y = a == b && x == y
    Day'       a x == Day'       b y = a == b && x == y
    ZonedTime' a x == ZonedTime' b y = a == b && projectZT x == projectZT y
    _              == _              = False

-- Extract the relevant parts to build an 'Eq' instance
projectZT :: ZonedTime -> (LocalTime, Int)
projectZT x = (zonedTimeToLocalTime x, timeZoneMinutes (zonedTimeZone x))

-- | Constructs a TOML string literal.
--
-- @
-- fromString = String
-- @
instance () ~ a => IsString (Value' a) where
    fromString = Text . fromString