packages feed

descript-lang-0.2.0.0: src/Descript/BasicInj/Process/Reduce/NoAnn.hs

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}

-- | Reduction algorithm.
--
-- Doesn't use or preserve annotations.
module Descript.BasicInj.Process.Reduce.NoAnn
  ( interpret
  , reducePhase
  , reduceReg
  ) where

import Descript.BasicInj.Process.Reduce.PropTrans
import Descript.BasicInj.Process.Reduce.Match
import qualified Descript.BasicInj.Data.Value.Reg as Reg
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.Monoid
import Core.Data.Functor
import Data.Foldable
import Data.Proxy
import Data.Maybe
import Data.List
import Core.Data.List
import Core.Data.List.Assoc hiding (Value)
import qualified Data.List.NonEmpty as NonEmpty
import Control.Monad
import Core.Control.Monad.Trans
import Control.Monad.Trans.Writer
import Prelude hiding (mod)

class (GenPart p, Eq (p ()), (PartPropVal p ~ GenValue p)) => NormReducePart p where
  reducePartProps :: PhaseCtx () -> p () -> p ()

instance NormReducePart Reg.Part where
  reducePartProps _ (Reg.PartPrim prim) = Reg.PartPrim prim
  reducePartProps ctx (Reg.PartRecord record)
    = Reg.PartRecord $ reduceRecordProps ctx record

reduceInPartProps :: PhaseCtx ()
                  -> In.Part ()
                  -> WriterT PropTranses [] (In.Part ())
reduceInPartProps _ (In.PartPrim prim) = pure $ In.PartPrim prim
reduceInPartProps _ (In.PartPrimType ptype) = pure $ In.PartPrimType ptype
reduceInPartProps ctx (In.PartRecord record)
  = In.PartRecord <$> reduceInRecordProps ctx record

instance NormReducePart Out.Part where
  reducePartProps _ (Out.PartPrim prim) = Out.PartPrim prim
  reducePartProps ctx (Out.PartRecord record)
    = Out.PartRecord $ reduceOutRecordProps ctx record
  reducePartProps _ (Out.PartPropPath path) = Out.PartPropPath path
  reducePartProps ctx (Out.PartInjApp app)
    = Out.PartInjApp $ reduceInjAppProps ctx app

reduceRecordProps :: PhaseCtx () -> Reg.Record () -> Reg.Record ()
reduceRecordProps = mapPropVals . reduceReg

reduceInRecordProps :: PhaseCtx ()
                    -> In.Record ()
                    -> WriterT PropTranses [] (In.Record ())
reduceInRecordProps ctx (Record () head' props)
  = Record () head' <$> traverse (reduceInRecordProp ctx head') props

reduceInRecordProp :: PhaseCtx ()
                   -> FSymbol ()
                   -> In.Property ()
                   -> WriterT PropTranses [] (In.Property ())
reduceInRecordProp ctx head' (Property () key val)
    = Property () key <$> val'
  where val'
          = censor (subPropTranses pelem)
          $ In.traverseOptVal (reduceInput ctx) val
        pelem = PathElem () key head'

reduceOutRecordProps :: PhaseCtx () -> Out.Record () -> Out.Record ()
reduceOutRecordProps = mapPropVals . reduceOutput

reduceInjAppProps :: PhaseCtx () -> Out.InjApp () -> Out.InjApp ()
reduceInjAppProps = Out.mapInjAppParams . Out.mapInjParamVal . reduceOutput

-- | Interprets the program - reduces its query using its reducers.
interpret :: Depd Program () -> Reg.Value ()
interpret dprog = reduceReg phase0 qval
  where phase0 = reduceAppPhases $ reduceCtx $ dmodule dprog
        qval = queryVal $ dquery dprog

-- | Macro-reduces each of the phases using reducers from the above
-- phase, adding reducers from the above phase along the way, then
-- returns the last phase (which will reduce the query).
reduceAppPhases :: ReduceCtx () -> PhaseCtx ()
reduceAppPhases (ReduceCtx () t xs) = foldl' reduceAppPhase t $ NonEmpty.toList xs

-- | Macro-reduces the second phase using reducers from the first.
-- Then combines the phases.
reduceAppPhase :: PhaseCtx () -> PhaseCtx () -> PhaseCtx ()
reduceAppPhase mctx x = mctx <> reducePhase mctx x

-- | Macro-reduces the second phase using reducers from the first.
reducePhase :: PhaseCtx () -> PhaseCtx () -> PhaseCtx ()
reducePhase mctx (PhaseCtx () xs)
  = PhaseCtx () $ concatMap (reduceReducer mctx) xs

-- | Macro-reduces the input and output (ctx is macros).
reduceReducer :: PhaseCtx () -> Reducer () -> [Reducer ()]
reduceReducer ctx (Reducer () input' output')
  = reduceReducerOut ctx output' <$> reduceInput' ctx input'

-- | Continues reducing the output using effects from the reduced input,
-- then creates the reduced reducer.
reduceReducerOut :: PhaseCtx ()
                 -> Out.Value ()
                 -> (In.Value (), PropTranses)
                 -> Reducer ()
reduceReducerOut ctx output' (newIn, outTranses)
  = Reducer () newIn newOut
  where newOut = reduceOutput ctx $ apPropTranses newIn outTranses output'

-- | Applies the context's reducers to the value, until they can't
-- be applied anymore.
reduceReg :: PhaseCtx () -> Reg.Value () -> Reg.Value ()
reduceReg = reduceNorm

-- | Applies the context's reducers to the output value, until they
-- can't be applied anymore.
reduceOutput :: PhaseCtx () -> Out.Value () -> Out.Value ()
reduceOutput = reduceNorm

-- | Applies the context's reducers to the value, until they can't be
-- applied anymore.
reduceNorm :: (NormReducePart p)
           => PhaseCtx ()
           -> GenValue p ()
           -> GenValue p ()
reduceNorm ctx = reduceRest reduceNorm ctx . reduceProps ctx

-- | Applies the context's reducers to the input value, until they can't
-- be applied anymore.
--
-- This also returns transformers which should be applied to the output.
-- Every time a record in the input matches, the corresponding property
-- in the output is transformed to a union of all the locations of that
-- property in the reducer's output.
reduceInput' :: PhaseCtx ()
            -> In.Value ()
            -> [(In.Value (), PropTranses)]
reduceInput' ctx = runWriterT . reduceInput ctx

reduceInput :: PhaseCtx ()
            -> In.Value ()
            -> WriterT PropTranses [] (In.Value ())
reduceInput ctx = reduceInRest reduceInput ctx <=< reduceInProps ctx

-- | Applies the context's reducers to the value's properties,
-- until they can't be applied anymore.
reduceProps :: (NormReducePart p) => PhaseCtx () -> GenValue p () -> GenValue p ()
reduceProps ctx (Value () parts)
  = Value () $ map (reducePartProps ctx) parts

reduceInProps :: PhaseCtx ()
              -> In.Value ()
              -> WriterT PropTranses [] (In.Value ())
reduceInProps ctx (Value () parts)
  = Value () <$> traverse (reduceInPartProps ctx) parts

-- | Applies the context's reducers to the value's head.
-- If the value reduced, applies the given reducer to reduce it more.
-- Otherwise just returns it as-is.
reduceRest :: (NormReducePart p)
           => (PhaseCtx () -> GenValue p () -> GenValue p ())
           -> PhaseCtx () -> GenValue p () -> GenValue p ()
reduceRest reduceMore ctx value
  = case reduceOnce ctx value of
         Failure () -> value
         Success next -> reduceMore ctx next

reduceInRest :: (PhaseCtx () -> In.Value () -> WriterT PropTranses [] (In.Value ()))
             -> PhaseCtx () -> In.Value () -> WriterT PropTranses [] (In.Value ())
reduceInRest reduceInMore ctx value
  = mapWriterT continue $ reduceInOnce ctx value
  where continue = concatMap (runWriterT . continue') . runResultT
        continue' (Failure ()) = pure value
        continue' (Success (next, effTrs)) = do
          tell effTrs
          reduceInMore ctx next

-- | Applies the context's reducers to the value once, returning a new
-- value if it reduced, or a failure if it couldn't be reduced at all.
reduceOnce :: (NormReducePart p)
           => PhaseCtx ()
           -> GenValue p ()
           -> UResult (GenValue p ())
reduceOnce ctx value
  | next == value = Failure ()
  | otherwise = Success next
  where next = tryReduceOnce ctx value

reduceInOnce :: PhaseCtx ()
             -> In.Value ()
             -> WriterT PropTranses (ResultT () []) (In.Value ())
reduceInOnce ctx value
  = mapWriterT continue $ tryReduceInOnce ctx value
  where continue = ResultT . map continue'
        continue' (next, effTrs)
          | next == value = Failure ()
          | otherwise = Success (next, effTrs)

-- | Applies the context's reducers to the value once. If none of them
-- could be applied (the value didn't reduce), just returns the same value.
tryReduceOnce :: (NormReducePart p)
              => PhaseCtx ()
              -> GenValue p ()
              -> GenValue p ()
tryReduceOnce (PhaseCtx () reducers) value
  = foldl' (flip tryReduceIndiv) value reducers

tryReduceInOnce :: PhaseCtx ()
                -> In.Value ()
                -> WriterT PropTranses [] (In.Value ())
tryReduceInOnce (PhaseCtx () reducers) value
  = foldM (flip tryReduceInIndiv) value reducers

-- | Applies the individual reducer to the value if it can be applied.
-- Otherwise just returns the value.
tryReduceIndiv :: (NormReducePart p)
               => Reducer ()
               -> GenValue p ()
               -> GenValue p ()
tryReduceIndiv reducer value
  = case reduceIndiv reducer value of
         Failure () -> value
         Success next -> next

tryReduceInIndiv :: Reducer ()
                 -> In.Value ()
                 -> WriterT PropTranses [] (In.Value ())
tryReduceInIndiv reducer value
  = mapWriterT continue $ reduceInIndiv reducer value
  where continue = map continue' . runResultT
        continue' (Failure ()) = (value, [])
        continue' (Success (next, effTrs)) = (next, effTrs)

-- | Applies the individual reducer to the value if it can be applied.
-- Otherwise returns a failure.
reduceIndiv :: (NormReducePart p)
            => Reducer ()
            -> GenValue p ()
            -> UResult (GenValue p ())
reduceIndiv reducer
  = fmap (produce $ output reducer)
  . consume (input reducer)

reduceInIndiv :: Reducer ()
              -> In.Value ()
              -> WriterT PropTranses (ResultT () []) (In.Value ())
reduceInIndiv reducer
  = WriterT
  . fmap (produceIn $ output reducer)
  . consumeIn (input reducer)

-- | Tries to match the input value to the given value.
consume :: (NormReducePart p)
        => In.Value ()
        -> GenValue p ()
        -> UResult (Match (GenValue p ()))
consume (Value () inParts) value
  = foldM (flip consumePartInMatch) (emptyMatch value) inParts

consumeIn :: In.Value ()
          -> In.Value ()
          -> UResultT [] (Match (In.Value ()))
consumeIn (Value () inParts) value
  = foldM (flip consumeInPartInMatch) (emptyMatch value) inParts

consumePartInMatch :: (NormReducePart p)
                   => In.Part ()
                   -> Match (GenValue p ())
                   -> UResult (Match (GenValue p ()))
consumePartInMatch = matchAgainF . consumePartInValue

consumeInPartInMatch :: In.Part ()
                     -> Match (In.Value ())
                     -> UResultT [] (Match (In.Value ()))
consumeInPartInMatch = matchAgainF . consumeInPartInValue

consumePartInValue :: (NormReducePart p)
                   => In.Part ()
                   -> GenValue p ()
                   -> UResult (Match (GenValue p ()))
consumePartInValue (In.PartPrim inPrim) (Value () parts)
  | inPart `notElem` parts = Failure ()
  | otherwise
  = Success Match
  { matched = Value () [inPart]
  , leftover = Value () $ inPart `delete` parts
  }
  where inPart = primToPart Proxy inPrim
consumePartInValue (In.PartPrimType inType) (Value () parts)
  = Value () <<$>> consumePrimTypeInParts inType parts
consumePartInValue (In.PartRecord inRec) (Value () parts)
  = Value () <<$>> consumeRecordInParts inRec parts

consumeInPartInValue :: In.Part ()
                     -> In.Value ()
                     -> UResultT [] (Match (In.Value ()))
consumeInPartInValue inPart@(In.PartPrim _) (Value () parts)
  | inPart `notElem` parts = mkUFailureT
  | otherwise
  = mkSuccessT Match
  { matched = Value () [inPart]
  , leftover = Value () $ inPart `delete` parts
  }
consumeInPartInValue (In.PartPrimType inType) (Value () parts)
  = hoist $ Value () <<$>> consumePrimTypeInParts inType parts
consumeInPartInValue (In.PartRecord inRec) (Value () parts)
  = Value () <<$>> consumeInRecordInParts inRec parts

consumePrimTypeInParts :: (GenPart p)
                       => PrimType ()
                       -> [p ()]
                       -> UResult (Match [p ()])
consumePrimTypeInParts _ [] = Failure ()
consumePrimTypeInParts inType (part : parts)
  = case consumePrimTypeInPart inType part of
         Failure ()
            -> mapLeftover (part :)
           <$> consumePrimTypeInParts inType parts
         Success ()
           -> Success Match
            { matched = [part]
            , leftover = parts
            }

consumePrimTypeInPart :: (GenPart p)
                      => PrimType ()
                      -> p ()
                      -> UResult ()
consumePrimTypeInPart inType part
  = case partToPrim part of
         Nothing -> Failure ()
         Just prim
           | prim `isPrimInstance` inType -> Success ()
           | otherwise -> Failure ()

consumeRecordInParts :: (NormReducePart p)
                     => In.Record ()
                     -> [p ()]
                     -> UResult (Match [p ()])
consumeRecordInParts _ [] = Failure ()
consumeRecordInParts inRec (part : parts)
  = case consumeRecordInPart inRec part of
         Failure ()
            -> mapLeftover (part :)
           <$> consumeRecordInParts inRec parts
         Success (Match partMatch partLeftover)
           -> Success Match
            { matched = maybeToList partMatch
            , leftover = partLeftover ?: parts
            }

consumeInRecordInParts :: In.Record ()
                       -> [In.Part ()]
                       -> UResultT [] (Match [In.Part ()])
consumeInRecordInParts _ [] = mkUFailureT
consumeInRecordInParts inRec (part : parts)
  = bindStackOuter continue $ consumeInRecordInPart inRec part
  where continue (Failure ())
            = mapLeftover (part :)
          <$> consumeInRecordInParts inRec parts
        continue (Success (Match partMatch partLeftover))
          = mkSuccessT Match
          { matched = maybeToList partMatch
          , leftover = partLeftover ?: parts
          }

consumeRecordInPart :: (NormReducePart p)
                    => In.Record ()
                    -> p ()
                    -> UResult (Match (Maybe (p ())))
consumeRecordInPart inRec part
  = case partToRec part of
         Nothing -> Failure ()
         Just record -> recToPart Proxy <<<$>>> consumeRecord inRec record

consumeInRecordInPart :: In.Record ()
                      -> In.Part ()
                      -> UResultT [] (Match (Maybe (In.Part ())))
consumeInRecordInPart _ (In.PartPrim _) = mkUFailureT
consumeInRecordInPart _ (In.PartPrimType _) = mkUFailureT
consumeInRecordInPart inRec (In.PartRecord record)
  = In.PartRecord <<<$>>> consumeInRecord inRec record

consumeRecord :: (NormReducePart p)
              => In.Record ()
              -> PartRecord p ()
              -> UResult (Match (Maybe (PartRecord p ())))
consumeRecord (Record () inRecHead inRecProps) (Record () recHead recProps)
        | inRecHead /= recHead = Failure ()
        | otherwise
        = Record () recHead
  <<<$>>> bimapMatch Just justIfNonEmptyList
      <$> consumeProperties inRecProps recProps

consumeInRecord :: In.Record ()
                -> In.Record ()
                -> UResultT [] (Match (Maybe (In.Record ())))
consumeInRecord (Record () inRecHead inRecProps) (Record () recHead recProps)
        | inRecHead /= recHead = mkUFailureT
        | otherwise
        = Record () recHead
  <<<$>>> bimapMatch Just justIfNonEmptyList
      <$> consumeInProperties inRecProps recProps

consumeProperties :: (NormReducePart p)
                  => [In.Property ()]
                  -> [PartProperty p ()]
                  -> UResult (Match [PartProperty p ()])
consumeProperties _ [] = Success $ pure []
consumeProperties inProps (prop : props)
  = case consumePropertiesInProperty inProps prop of
         Failure () -> Failure ()
         Success match
            -> addPropValMatch prop match
           <$> consumeProperties inProps props

consumeInProperties :: [In.Property ()]
                    -> [In.Property ()]
                    -> UResultT [] (Match [In.Property ()])
consumeInProperties _ [] = mkSuccessT $ pure []
consumeInProperties inProps (prop : props)
  = bindStackOuter continue $ consumeInPropertiesInProperty inProps prop
  where continue (Failure ()) = mkUFailureT
        continue (Success match)
            = addPropValMatch prop match
          <$> consumeInProperties inProps props

consumePropertiesInProperty :: (NormReducePart p)
                            => [In.Property ()]
                            -> PartProperty p ()
                            -> UResult (Match (Maybe (GenValue p ())))
consumePropertiesInProperty inProps (Property () propKey propVal)
  = case glookupForce propKey inProps of
         In.NothingValue
           -> Success Match
            { matched = Just propVal
            , leftover = Nothing
            }
         In.JustValue inPropVal
            -> bimapMatch Just justIfNonEmptyVal
           <$> consume inPropVal propVal

consumeInPropertiesInProperty :: [In.Property ()]
                              -> In.Property ()
                              -> UResultT [] (Match (Maybe (In.OptValue ())))
consumeInPropertiesInProperty inProps (Property () propKey propVal)
  = case glookupForce propKey inProps of
         In.NothingValue
           -> mkSuccessT Match
            { matched = Just propVal
            , leftover = Nothing
            }
         In.JustValue inPropVal
            -> bimapMatch Just justIfNonEmptyOptVal
           <$> consumeInOpt inPropVal propVal

consumeInOpt :: In.Value ()
             -> In.OptValue ()
             -> UResultT [] (Match (In.OptValue ()))
consumeInOpt _ In.NothingValue = ResultT [Failure ()]
{- Should the case be this instead?
  [ -- consumeIn input input
    Success Match
    { matched = In.JustValue input'
    , leftover = mempty -- JustValue $ Value () []
    }
  , Failure ()
  ]
-}
consumeInOpt input' (In.JustValue x)
  = In.JustValue <<$>> consumeIn input' x

addPropValMatch :: GenProperty v ()
                -> Match (Maybe (v ()))
                -> Match [GenProperty v ()]
                -> Match [GenProperty v ()]
addPropValMatch (Property () propKey _) propVal props
  = (?:) <$> prop <*> props
  where prop = Property () propKey <<$>> propVal

justIfNonEmptyOptVal :: In.OptValue () -> Maybe (In.OptValue ())
justIfNonEmptyOptVal In.NothingValue = Just In.NothingValue
justIfNonEmptyOptVal (In.JustValue x) = In.JustValue <$> justIfNonEmptyVal x

justIfNonEmptyVal :: GenValue v () -> Maybe (GenValue v ())
justIfNonEmptyVal x
  | isEmpty x = Nothing
  | otherwise = Just x

justIfNonEmptyList :: [a] -> Maybe [a]
justIfNonEmptyList x
  | null x = Nothing
  | otherwise = Just x

-- | Resolves the property paths in the output value using the given
-- value, and combines the result with the given value.
produce :: (NormReducePart p)
        => Out.Value ()
        -> Match (GenValue p ())
        -> GenValue p ()
produce output' match
  = leftover match <> resolve output' (matched match)

produceIn :: Out.Value ()
          -> Match (In.Value ())
          -> (In.Value (), PropTranses)
produceIn output' match
  = (produceInMain output' match, transProps output' $ matched match)

-- | Resolves the property paths in the output value using the given
-- value, and combines the result with the given value.
produceInMain :: Out.Value () -> Match (In.Value ()) -> In.Value ()
produceInMain output' match
  = leftover match <> resolveIn' output' (matched match)

-- | Resolves all property paths in the output value using the given value.
-- Replaces all paths with the corresponding properties in the given
-- value.
resolve :: (NormReducePart p)
        => Out.Value ()
        -> GenValue p ()
        -> GenValue p ()
resolve (Value () outParts) value
  = mconcat $ map resolvePart outParts
  where resolvePart (Out.PartPrim prim)
          = singletonValue $ primToPart Proxy prim
        resolvePart (Out.PartRecord record)
          = singletonValue
          $ recToPart Proxy
          $ mapPropVals (`resolve` value)
            record
        resolvePart (Out.PartPropPath path) = resolvePropPath path value
        resolvePart (Out.PartInjApp app) = resolveInjApp app value

resolveIn' :: Out.Value () -> In.Value () -> In.Value ()
resolveIn' output' value
  = case resolveIn output' $ In.JustValue value of
         In.NothingValue -> error "Bad macro - reduces input to free bind"
         In.JustValue x -> x

resolveIn :: Out.Value ()
          -> In.OptValue ()
          -> In.OptValue ()
resolveIn (Value () outParts) value
  = mconcat $ map resolvePart outParts
  where resolvePart (Out.PartPrim prim)
          = In.JustValue $ singletonValue $ primToPart Proxy prim
        resolvePart (Out.PartRecord record)
          = In.JustValue
          $ singletonValue
          $ recToPart Proxy
          $ mapPropVals (`resolveIn` value)
            record
        resolvePart (Out.PartPropPath path) = resolveInPropPath path value
        resolvePart (Out.PartInjApp app) = In.JustValue $ resolveInInjApp app value

-- | Resolves the property path using the given value.
-- Replaces it with the corresponding property in the given value.
resolvePropPath :: (NormReducePart p)
                => PropPath ()
                -> GenValue p ()
                -> GenValue p ()
resolvePropPath (PropPath () xs) = resolveSubpath $ NonEmpty.toList xs

resolveInPropPath :: PropPath ()
                  -> In.OptValue ()
                  -> In.OptValue ()
resolveInPropPath (PropPath () xs) = resolveInSubpath $ NonEmpty.toList xs

resolveSubpath :: (NormReducePart p)
               => [PathElem ()]
               -> GenValue p ()
               -> GenValue p ()
resolveSubpath [] = id
resolveSubpath (x : xs) = resolveSubpath xs . resolveElem x

resolveInSubpath :: [PathElem ()]
                 -> In.OptValue ()
                 -> In.OptValue ()
resolveInSubpath [] = id
resolveInSubpath (x : xs) = resolveInSubpath xs . resolveInElem x

resolveElem :: (NormReducePart p)
            => PathElem ()
            -> GenValue p ()
            -> GenValue p ()
resolveElem (PathElem () keyRef' headRef')
  = forceLookupProp keyRef' . forceRecWithHead headRef'

resolveInElem :: PathElem ()
              -> In.OptValue ()
              -> In.OptValue ()
resolveInElem _ In.NothingValue
  = error "Bad macro - references property of free bind"
resolveInElem (PathElem () keyRef' headRef') (In.JustValue val)
  = forceLookupProp keyRef' $ forceRecWithHead headRef' val

-- | Finds the injected function, resolves the arguments, then applies
-- the function with every possible primitive combination until it
-- returns a result.
resolveInjApp :: (NormReducePart p)
              => Out.InjApp ()
              -> GenValue p ()
              -> GenValue p ()
resolveInjApp app x = applyInj func params
  where func = forceLookupFunc $ Out.funcId app
        params = map ((`resolve` x) . Out.injParamVal) $ Out.params app

resolveInInjApp :: Out.InjApp ()
                -> In.OptValue ()
                -> In.Value ()
resolveInInjApp app x = applyInj func params
  where func = forceLookupFunc $ Out.funcId app
        params
          = mapMaybe (In.optValToMaybeVal . (`resolveIn` x) . Out.injParamVal)
          $ Out.params app

-- Applies the function with every possible primitive combination given
-- the arguments until it returns a result. Fails if no combinations work.
applyInj :: (GenPart p) => InjFunc -> [GenValue p ()] -> GenValue p ()
applyInj func params
  = case tryApplyInj func params of
         Failure () -> error "Injected function couldn't be applied to parameters"
         Success out -> out

-- Applies the function with every possible primitive combination given
-- the arguments until it returns a result.
tryApplyInj :: (GenPart p)
            => InjFunc
            -> [GenValue p ()]
            -> UResult (GenValue p ())
tryApplyInj = applyInjUsing []

applyInjUsing :: (GenPart p)
              => [Prim ()]
              -> InjFunc
              -> [GenValue p ()]
              -> UResult (GenValue p ())
applyInjUsing revSelParams func []
  = case func selParams of
         Nothing -> Failure ()
         Just out -> Success $ singletonValue $ primToPart Proxy out
  where selParams = reverse revSelParams
applyInjUsing revSelParams func (nextParam : restParams)
  = asum $ map applyInjUsingNext $ primParts nextParam
  where applyInjUsingNext nextSelParam = applyInjUsing (nextSelParam : revSelParams) func restParams

transProps :: Out.Value () -> In.Value () -> PropTranses
transProps output' = map (`transPath` output') . pathsInVal

pathsInVal :: In.Value () -> [PropPath ()]
pathsInVal (Value () parts) = concatMap pathsInPart parts

pathsInPart :: In.Part () -> [PropPath ()]
pathsInPart (In.PartPrim _) = []
pathsInPart (In.PartPrimType _) = []
pathsInPart (In.PartRecord record) = pathsInRecord record

pathsInRecord :: In.Record () -> [PropPath ()]
pathsInRecord (Record () head' props) = concatMap (pathsInProp head') props

pathsInProp :: FSymbol ()
            -> In.Property ()
            -> [PropPath ()]
pathsInProp head' (Property () key val)
  = immPath elem' : map (subPath elem') (pathsInOptVal val)
  where elem' = PathElem () key head'

pathsInOptVal :: In.OptValue () -> [PropPath ()]
pathsInOptVal In.NothingValue = []
pathsInOptVal (In.JustValue x) = pathsInVal x

transPath :: PropPath () -> Out.Value () -> PropTrans
transPath inPath = PropTrans inPath . transOutsInVal inPath

transOutsInVal :: PropPath () -> Out.Value () -> [SubPropPath ()]
transOutsInVal inPath (Value () parts) = concatMap (transOutsInPart inPath) parts

transOutsInPart :: PropPath () -> Out.Part () -> [SubPropPath ()]
transOutsInPart _ (Out.PartPrim _) = []
transOutsInPart inPath (Out.PartRecord record)
  = transOutsInRecord inPath record
transOutsInPart inPath (Out.PartPropPath path)
  = transOutsInPath inPath path
transOutsInPart inPath (Out.PartInjApp app)
  = transOutsInInjApp inPath app

transOutsInRecord :: PropPath () -> Out.Record () -> [SubPropPath ()]
transOutsInRecord inPath (Record () head' props)
  = concatMap (transOutsInProp inPath head') props

transOutsInProp :: PropPath () -> FSymbol () -> Out.Property () -> [SubPropPath ()]
transOutsInProp inPath head' (Property () key val)
  = map (elem' :) $ transOutsInVal inPath val
  where elem' = PathElem () key head'

-- | Name is misleading out of context - the old property path will
-- transform into the location of this property path if both paths are
-- equal.
transOutsInPath :: PropPath () -> PropPath () -> [SubPropPath ()]
transOutsInPath inPath path
  | inPath == path = [[]]
  | otherwise = []

transOutsInInjApp :: PropPath () -> Out.InjApp () -> [SubPropPath ()]
transOutsInInjApp _ _
  = error "Macros deconstructing injected applications is unsupported. \
          \Wrap the injected application in a regular record, then \
          \deconstruct that record instead."