{-# LANGUAGE BangPatterns #-} module Parse where import Control.Monad.State.Strict (State(), runState, get, put) import Control.Monad (foldM) import Data.Map (Map, empty, insertWith', alter, (!)) import Numeric (readSigned, readFloat) import Prelude hiding (lines, words, drop, length) import Data.ByteString.Lazy.Char8 (ByteString, pack, unpack, lines, words, isPrefixOf, drop, length) import Types data Parse = Parse { symbols :: !(Map ByteString ByteString) -- intern symbols to save RAM , totals :: !(Map ByteString Double ) -- compute running totals } deriving (Read, Show, Eq, Ord) parse0 :: Parse parse0 = Parse{ symbols = empty, totals = empty } parse :: ByteString -> Run parse s = let ls = lines s (hs, ss) = splitAt 4 ls [job, date, smpU, valU] = zipWith header [sJOB, sDATE, sSAMPLE_UNIT, sVALUE_UNIT] hs (frames, parse1) = flip runState parse0 . mapM parseFrame . chunkSamples $ ss in Run { hprJob = job , hprDate = date , hprSampleUnit = smpU , hprValueUnit = valU , hprFrames = frames , hprTotals = totals parse1 } header :: ByteString -> ByteString -> ByteString header name h = if name `isPrefixOf` h then pack . read . unpack . drop (length name + 1) $ h else error $ "Parse.header: expected " ++ unpack name chunkSamples :: [ByteString] -> [[ByteString]] chunkSamples [] = [] chunkSamples (x:xs) | sBEGIN_SAMPLE `isPrefixOf` x = let (ys, zs) = break (sEND_SAMPLE `isPrefixOf`) xs in case zs of [] -> [] -- discard incomplete sample (_:ws) -> (x:ys) : chunkSamples ws | otherwise = [] -- expected BEGIN_SAMPLE or EOF... parseFrame :: [ByteString] -> State Parse Frame parseFrame [] = error "Parse.parseFrame: empty" parseFrame (l:ls) = do let time = sampleTime sBEGIN_SAMPLE l samples <- foldM inserter empty ls return Frame { hpfTime = time , hpfSamples = samples } inserter :: Map ByteString Double -> ByteString -> State Parse (Map ByteString Double) inserter !m s = do let [k,vs] = words s !v = readDouble vs p <- get let symbols' = alter (intern k) k (symbols p) totals' = alter (accum v) k (totals p) put $! p{ symbols = symbols', totals = totals' } p' <- get return $! insertWith' (+) (symbols p' ! k) v m intern :: ByteString -> Maybe ByteString -> Maybe ByteString intern s Nothing = Just s intern _ js = js accum :: Double -> Maybe Double -> Maybe Double accum x Nothing = Just x accum x (Just y) = Just $! x + y sampleTime :: ByteString -> ByteString -> Double sampleTime name h = if name `isPrefixOf` h then readDouble . drop (length name + 1) $ h else error $ "Parse.sampleTime: expected " ++ unpack name ++ " but got " ++ unpack h readDouble :: ByteString -> Double readDouble s = case readSigned readFloat (unpack s) of ((x,_):_) -> x _ -> error $ "Parse.readDouble: no parse " ++ unpack s sJOB, sDATE, sSAMPLE_UNIT, sVALUE_UNIT, sBEGIN_SAMPLE, sEND_SAMPLE :: ByteString sJOB = pack "JOB" sDATE = pack "DATE" sSAMPLE_UNIT = pack "SAMPLE_UNIT" sVALUE_UNIT = pack "VALUE_UNIT" sBEGIN_SAMPLE = pack "BEGIN_SAMPLE" sEND_SAMPLE = pack "END_SAMPLE"