packages feed

setdown-0.1.0.0: PerformOperations.hs

module PerformOperations (runSimpleDefinitions) where

import Control.Arrow (first)
import Control.Applicative
import Control.Monad.State.Lazy
import Context
import SetData
import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.IO as T
import qualified Data.Map as M
import qualified Data.UUID.V4 as UUID

data ComputeState = CS
   { expressionToFile :: M.Map BaseExpression FilePath
   , definitionMap :: M.Map Identifier SimpleDefinition
   , csContext :: Context
   }

runSimpleDefinitions :: Context -> SimpleDefinitions -> [(FilePath, FilePath)] -> IO [(SimpleDefinition, FilePath)]
runSimpleDefinitions context defs sortedFileMapping = fst <$> runStateT (computeSimpleDefinitions defs) cs
   where
      cs = CS
         { expressionToFile = setupExpressionsToFile sortedFileMapping 
         , definitionMap = toDefinitionMap defs
         , csContext = context
         }
      
toDefinitionMap :: SimpleDefinitions -> M.Map Identifier SimpleDefinition
toDefinitionMap = M.fromList . fmap (\x -> (sdId x, x))

setupExpressionsToFile :: [(FilePath, FilePath)] -> M.Map BaseExpression FilePath
setupExpressionsToFile = M.fromList . fmap (first BaseFileExpression)

computeSimpleDefinitions :: SimpleDefinitions -> StateT ComputeState IO [(SimpleDefinition, FilePath)]
computeSimpleDefinitions = mapM csd
   where
      csd :: SimpleDefinition -> StateT ComputeState IO (SimpleDefinition, FilePath)
      csd sd = do
         resultFile <- computeSimpleDefinition sd
         return (sd, resultFile)

-- Always return the file that should be used for the next computation
computeSimpleDefinition :: SimpleDefinition -> StateT ComputeState IO FilePath
computeSimpleDefinition (SimpleDefinition ident (SimpleUnaryExpression be) _) = do
   newFile <- computeBaseExpression be
   mapIdToFile ident newFile
   return newFile
computeSimpleDefinition (SimpleDefinition ident (SimpleBinaryExpression op left right) _) = do
   leftFile <- computeBaseExpression left
   rightFile <- computeBaseExpression right
   ctx <- csContext <$> get
   resultFile <- lift $ fileSetOperation ctx op leftFile rightFile
   mapIdToFile ident resultFile
   return resultFile

computeBaseExpression :: BaseExpression -> StateT ComputeState IO FilePath
computeBaseExpression be@(BaseFileExpression fp) = do 
   expressionMap <- expressionToFile <$> get
   case M.lookup be expressionMap of
      Just sortedFile -> return sortedFile
      Nothing -> fail $ "Could not find a sorted file for: " ++ fp
computeBaseExpression be@(BaseIdentifierExpression ident) = do
   cs <- get
   case M.lookup be (expressionToFile cs) of
      Just preComputedFile -> return preComputedFile
      Nothing -> case M.lookup ident (definitionMap cs) of
         Nothing -> fail $ "Could not find a definition for the identifier: " ++ T.unpack ident
         Just def -> computeSimpleDefinition def

mapIdToFile :: Monad a => Identifier -> FilePath -> StateT ComputeState a ()
mapIdToFile ident fp = modify (\currentState -> currentState
   { expressionToFile = M.insert (BaseIdentifierExpression ident) fp (expressionToFile currentState)
   })

fileSetOperation :: Context -> Operator -> FilePath -> FilePath -> IO FilePath
fileSetOperation ctx ot leftFp rightFp = do
   leftContents <- T.lines <$> T.readFile leftFp 
   rightContents <- T.lines <$> T.readFile rightFp 
   let mergedContents = linesSetOperation (operatorTools ot) leftContents rightContents 
   randomFilename <- randomFilenameInOutput ctx
   T.writeFile randomFilename . T.unlines $ mergedContents
   return randomFilename
   
randomFilenameInOutput :: Context -> IO FilePath
randomFilenameInOutput ctx = inOutput ctx . show <$> UUID.nextRandom

linesSetOperation :: OperatorTools T.Text -> [T.Text] -> [T.Text] -> [T.Text]
linesSetOperation ot = go
   where 
      go :: [T.Text] -> [T.Text] -> [T.Text]
      go [] [] = []
      go xs [] = if otKeepRemainderLeft ot then xs else []
      go [] xs = if otKeepRemainderRight ot then xs else []
      go left@(l:ls) right@(r:rs) = 
         if (otCompare ot) l r 
            then chosen : go (dropWhileChosen left) (dropWhileChosen right)
            else case compare l r of
               LT -> go ls   right
               EQ -> go ls   rs
               GT -> go left rs
         where
            chosen = (otChoose ot) l r
            dropWhileChosen = dropWhile (== chosen)
      
data (Eq a, Ord a) => OperatorTools a = OT
   { otCompare :: a -> a -> Bool
   , otChoose  :: a -> a -> a
   , otKeepRemainderLeft :: Bool
   , otKeepRemainderRight :: Bool
   } 

operatorTools :: Ord a => Operator -> OperatorTools a
operatorTools And          = OT (==)            const False False -- fst or snd, it does not matter they are equal
operatorTools Or           = OT (const2 True)   min True True
operatorTools Difference   = OT (<)             const True False 

const2 :: a -> b -> c -> a
const2 = const . const