descript-lang-0.2.0.0: src/Descript/BasicInj/Process/Reduce/PropTrans.hs
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TupleSections #-}
module Descript.BasicInj.Process.Reduce.PropTrans
( PropTranses
, PropTrans (..)
, apPropTranses
, subPropTranses
) where
import qualified Descript.BasicInj.Data.Value.In as In
import qualified Descript.BasicInj.Data.Value.Out as Out
import Descript.BasicInj.Data
import Descript.Misc
import Data.Maybe
import Data.List
import Data.List.NonEmpty (NonEmpty (..))
import Control.Applicative
-- | Replaces properties within a value.
type PropTranses = [PropTrans]
-- | Replaces every occurrence of a property path with a union of the
-- new subpaths. If a subpath is empty, it's replaced by the entire
-- input value with immediate properties.
data PropTrans
= PropTrans
{ propTransOld :: PropPath ()
, propTransNews :: [SubPropPath ()]
} deriving (Show)
-- | Merges the transformations, then applies them all.
apPropTranses :: (TaintAnn an)
=> In.Value ()
-> PropTranses
-> Out.Value an
-> Out.Value an
apPropTranses in' transes x
= foldl' (flip $ apPropTrans in') x $ mergeTranses transes
-- | Adds outputs from more general transes to more specific ones.
-- For example:
--
-- >>> mergeTranses ['a>c>d -> ', 'a -> z', 'a>b -> y']
-- ['a>c>d -> z>c>d', 'a -> z', 'a>b -> y | z>b']
mergeTranses :: PropTranses -> PropTranses
mergeTranses = foldl' (flip mergeAddTrans) []
-- | Adds outputs from more general transes to the new trans, and adds
-- outputs from the new trans to more specific ones.
mergeAddTrans :: PropTrans -> PropTranses -> PropTranses
mergeAddTrans x [] = [x]
mergeAddTrans x (y : ys) = y' : mergeAddTrans x' ys
where (x', y') = tryMergeTrans x y
-- | If one of the transes is a prefix of the other, returns both so
-- the longer one has the smaller one's outputs. Otherwise returns both
-- unaffected.
tryMergeTrans :: PropTrans -> PropTrans -> (PropTrans, PropTrans)
tryMergeTrans x y = (x, y) `fromMaybe` mergeTrans x y
-- | If one of the transes is a prefix of the other, returns both so
-- the longer one has the smaller one's outputs.
mergeTrans :: PropTrans -> PropTrans -> Maybe (PropTrans, PropTrans)
mergeTrans x y
= (x, ) <$> mergeTrans1Way x y
<|> (, y) <$> mergeTrans1Way y x
-- | If the first transes is a prefix of the second, returns the second
-- so it includes the first. Otherwise returns 'Nothing'.
mergeTrans1Way :: PropTrans -> PropTrans -> Maybe PropTrans
mergeTrans1Way (PropTrans xOld xNews) (PropTrans yOld yNews)
= case xOld `stripPrefixPath` yOld of
Nothing -> Nothing
Just yRest -> Just $ PropTrans yOld $ yNews ++ xNews'
where xNews' = map (++ yRest) xNews
{-
= case old `stripPrefixPath` path_ of
Nothing -> singletonValue $ Out.PartPropPath path
Just suf_ -> ann' <$ mconcat (map (subPathVal in' . (++ suf_)) news)
where path_ = remAnns path
ann' = taint $ getAnn path
-}
-- | Assumes there is a transformation for every reasonable path, and
-- the transformations were all merged (more general transformation
-- outputs, specified, were added to more specific outputs).
apPropTrans :: (TaintAnn an)
=> In.Value ()
-> PropTrans
-> Out.Value an
-> Out.Value an
apPropTrans in' trans (Value ann parts)
= reconValue ann parts $ map (apPropTransToPart in' trans) parts
apPropTransToPart :: (TaintAnn an)
=> In.Value ()
-> PropTrans
-> Out.Part an
-> Out.Value an
apPropTransToPart _ _ (Out.PartPrim prim) = singletonValue $ Out.PartPrim prim
apPropTransToPart in' trans (Out.PartRecord record)
= singletonValue $ Out.PartRecord $ apPropTransToRecord in' trans record
apPropTransToPart in' trans (Out.PartPropPath path)
= apPropTransToPath in' trans path
apPropTransToPart in' trans (Out.PartInjApp app)
= singletonValue $ Out.PartInjApp $ apPropTransToInjApp in' trans app
apPropTransToRecord :: (TaintAnn an)
=> In.Value ()
-> PropTrans
-> Out.Record an
-> Out.Record an
apPropTransToRecord in' trans (Record ann head' props)
-- No need for reRecord because prop counts, and thus annotation, will
-- always stay the same.
= Record ann head' $ map (apPropTransToProp in' trans) props
apPropTransToProp :: (TaintAnn an)
=> In.Value ()
-> PropTrans
-> Out.Property an
-> Out.Property an
apPropTransToProp in' trans (Property ann key val)
-- No need for reProperty because value printability, and thus
-- annotation, will always stay the same.
= Property ann key $ apPropTrans in' trans val
apPropTransToInjApp :: (TaintAnn an)
=> In.Value ()
-> PropTrans
-> Out.InjApp an
-> Out.InjApp an
apPropTransToInjApp in' trans (Out.InjApp ann funcId' params')
= Out.InjApp ann funcId' $ map (apPropTransToInjParam in' trans) params'
apPropTransToInjParam :: (TaintAnn an)
=> In.Value ()
-> PropTrans
-> Out.InjParam an
-> Out.InjParam an
apPropTransToInjParam in' trans (Out.InjParam ann val)
= Out.InjParam ann $ apPropTrans in' trans val
apPropTransToPath :: (TaintAnn an)
=> In.Value ()
-> PropTrans
-> PropPath an
-> Out.Value an
apPropTransToPath in' (PropTrans old news) path
-- Assumes there is a transformation for every reasonable path, and the
-- transformations were all merged. Otherwise would need to check prefix
-- instead of full path.
| path /@= old = singletonValue $ Out.PartPropPath path
| otherwise = ann' <$ mconcat (map (subPathVal in') news)
where ann' = taint $ getAnn path
-- | An output value which refers to the given sub-path, given the
-- corresponding input. If the sub-path is empty, the value will refer
-- to the entire input (via 'fullOut'). Otherwise it will just contain a
-- single property path.
subPathVal :: In.Value () -> SubPropPath () -> Out.Value ()
subPathVal in' [] = fullOut in'
subPathVal _ (x : xs) = singletonValue $ Out.PartPropPath $ PropPath () $ x :| xs
-- | This output will re-produce everything which was consumed by the
-- input. It's semantically equivalent to an empty property path, but
-- those don't exist. Note that a top-level primitive type has no full
-- output (you can't re-produce it), so that will raise an error.
fullOut :: In.Value () -> Out.Value ()
fullOut (Value () parts) = Value () $ map fullOutPart parts
fullOutPart :: In.Part () -> Out.Part ()
fullOutPart (In.PartPrim prim) = Out.PartPrim prim
fullOutPart (In.PartPrimType _)
= error "Top-level primitive type has no full output - you can't \
\reproduce it in an output value, because it's not a single \
\value and it doesn't correspond to a property path."
fullOutPart (In.PartRecord record) = Out.PartRecord $ fullOutRecord record
fullOutRecord :: In.Record () -> Out.Record ()
fullOutRecord (Record () head' props)
= Record () head' $ map (fullOutProp head') props
fullOutProp :: FSymbol () -> In.Property () -> Out.Property ()
fullOutProp head' (Property () key _) = Property () key $ Out.immPathVal elem'
where elem' = PathElem () key head'
-- | Prepends the element to all property paths in all transformations.
subPropTranses :: PathElem () -> PropTranses -> PropTranses
subPropTranses = map . subPropTrans
-- | Prepends the element to the input and all output paths.
subPropTrans :: PathElem () -> PropTrans -> PropTrans
subPropTrans x (PropTrans old news)
= PropTrans
{ propTransOld = subPath x old
, propTransNews = map (x :) news
}