packages feed

descript-lang-0.2.0.0: src/Descript/BasicInj/Data/Value/Out.hs

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE OverloadedStrings #-}

module Descript.BasicInj.Data.Value.Out
  ( Property
  , Record
  , InjParam (..)
  , InjApp (..)
  , Part (..)
  , Value
  , immPathVal
  , mapInjAppParams
  , mapInjParamVal
  , traverseInjParamVal
  , idxPropKeys
  , fullProduceProp
  ) where

import Descript.BasicInj.Data.Value.Gen
import Descript.BasicInj.Data.Atom
import Descript.Misc
import Data.Semigroup as S
import Data.Monoid as M
import Data.String

-- | An output property.
type Property an = GenProperty (GenValue Part) an

-- | An output record.
type Record an = GenRecord (GenValue Part) an

-- | A parameter of an injected function.
data InjParam an
  = InjParam
  { injParamAnn :: an
  , injParamVal :: Value an
  } deriving (Eq, Ord, Read, Show, Functor, Foldable, Traversable)

-- | An application of an injected function.
data InjApp an
  = InjApp
  { injAppAnn :: an
  , funcId :: InjSymbol an
  , params :: [InjParam an]
  } deriving (Eq, Ord, Read, Show, Functor, Foldable, Traversable)

-- | An output part.
data Part an
  = PartPrim (Prim an)
  | PartRecord (Record an)
  | PartPropPath (PropPath an)
  | PartInjApp (InjApp an)
  deriving (Eq, Ord, Read, Show, Functor, Foldable, Traversable)

-- | An output value.
type Value an = GenValue Part an

instance (Semigroup an, Monoid an) => Monoid (InjParam an) where
  mempty
    = InjParam
    { injParamAnn = mempty
    , injParamVal = mempty
    }

  InjParam xAnn xVal `mappend` InjParam yAnn yVal
    = InjParam
    { injParamAnn = xAnn M.<> yAnn
    , injParamVal = xVal M.<> yVal
    }

instance (Semigroup an) => Semigroup (InjParam an) where
  InjParam xAnn xVal <> InjParam yAnn yVal
    = InjParam
    { injParamAnn = xAnn S.<> yAnn
    , injParamVal = xVal S.<> yVal
    }

instance GenPart Part where
  type PartPropVal Part = GenValue Part

  partToPrim (PartPrim prim) = Just prim
  partToPrim _ = Nothing

  partToRec (PartRecord record) = Just record
  partToRec _ = Nothing

  primToPart _ = PartPrim
  recToPart _ = PartRecord

  mergeAddPart (PartPrim prim) parts = PartPrim prim : parts
  mergeAddPart (PartRecord record) parts = mergeAddRecord record parts
  mergeAddPart (PartPropPath path) parts = PartPropPath path : parts
  mergeAddPart (PartInjApp app) parts = mergeAddInjApp app parts

instance Ann Part where
  getAnn (PartPrim x) = getAnn x
  getAnn (PartRecord x) = getAnn x
  getAnn (PartPropPath x) = getAnn x
  getAnn (PartInjApp x) = getAnn x

instance Ann InjApp where
  getAnn = injAppAnn

instance Ann InjParam where
  getAnn (InjParam ann _) = ann

instance Printable Part where
  aprintRec sub (PartPrim prim) = sub prim
  aprintRec sub (PartRecord record) = sub record
  aprintRec sub (PartPropPath path) = sub path
  aprintRec sub (PartInjApp app) = sub app

instance Printable InjApp where
  aprintRec sub app = sub (funcId app) M.<> paramsPrinted
    where paramsPrinted = "[" M.<> pintercal ", " paramPrinteds M.<> "]"
          paramPrinteds = zipWith (paramPrint sub) idxPropKeys $ params app

instance (Show an) => Summary (Part an) where
  summaryRec = pprintSummaryRec

instance (Show an) => Summary (InjApp an) where
  summaryRec = pprintSummaryRec

-- | Refers to the immediate property corresponding to the path element.
immPathVal :: PathElem () -> Value ()
immPathVal = singletonValue . PartPropPath . immPath

mapInjAppParams :: (InjParam an -> InjParam an) -> InjApp an -> InjApp an
mapInjAppParams f (InjApp ann funcId' params')
  = InjApp ann funcId' $ map f params'

-- | Transforms the value in the injected parameter.
mapInjParamVal :: (Value an -> Value an) -> InjParam an -> InjParam an
mapInjParamVal f (InjParam ann x) = InjParam ann $ f x

-- | Transforms the value in the injected parameter with side effects.
traverseInjParamVal :: (Functor w)
                    => (Value an -> w (Value an))
                    -> InjParam an
                    -> w (InjParam an)
traverseInjParamVal f (InjParam ann x) = InjParam ann <$> f x

mergeAddInjApp :: (Semigroup an) => InjApp an -> [Part an] -> [Part an]
mergeAddInjApp app [] = [PartInjApp app]
mergeAddInjApp app (x : xs)
  = case tryMergeInjAppWithPart app x of
         Failure () -> x : mergeAddInjApp app xs
         Success newApp -> PartInjApp newApp : xs

tryMergeInjAppWithPart :: (Semigroup an)
                       => InjApp an
                       -> Part an
                       -> UResult (InjApp an)
tryMergeInjAppWithPart xApp (PartInjApp yApp) = tryMergeInjApps xApp yApp
tryMergeInjAppWithPart _ _ = Failure ()

tryMergeInjApps :: (Semigroup an)
                => InjApp an
                -> InjApp an
                -> UResult (InjApp an)
tryMergeInjApps (InjApp xAnn xFuncId xParams) (InjApp yAnn yFuncId yParams)
  | xFuncId /@= yFuncId = Failure ()
  | otherwise = Success InjApp
  { injAppAnn = xAnn S.<> yAnn
  , funcId = xFuncId `eappend` yFuncId
  , params = zipWith (S.<>) xParams yParams
  }

-- | Each of these keys in an injected function application corresponds
-- to a parameter at its position.
idxPropKeys :: [Symbol ()]
idxPropKeys = map (Symbol () . pure) ['a'..'z']

-- | A property with the given key and a path to itself (with the given
-- record head) as its value.
fullProduceProp :: FSymbol () -> Symbol () -> Property ()
fullProduceProp head' key = Property () key val
  where val = immPathVal $ PathElem () key head'


paramPrint :: (Monoid r, IsString r)
           => (Value an -> r)
           -> Symbol ()
           -> InjParam an
           -> r
paramPrint sub label (InjParam _ val)
     = fromString (pprintStr label)
  M.<> ": "
  M.<> sub val